Skip to content

feat(personal-shop): prices in items and account coins - #710

Merged
sven-n merged 5 commits into
mainfrom
claude/openmu-1186-packet-nuget-dphx9w
Oct 14, 2026
Merged

sven-n merged 5 commits into
mainfrom
claude/openmu-1186-packet-nuget-dphx9w

Conversation

@sven-n

@sven-n sven-n commented Oct 11, 2026 •

Copy link
Copy Markdown
Owner

Summary

Client side of MUnique/OpenMU#1186. With it, a seller can price a personal-shop item in items (e.g. jewels) or account coins as well as zen, whenever the server allows it.

MUnique.OpenMU.Network.Packets 0.9.11 is now published on NuGet (from MUnique/OpenMU#1190). Its packet XML is byte-identical to the local build the bindings were generated from. Regenerating from the published package gives no changes.

Commit 1: build(network): packet definitions 0.9.11

  • Bumps OpenMuPacketsVersion and OPENMU_PACKETS_VERSION to 0.9.11.
  • Regenerates the packet bindings from it.
  • The new definitions also include castle siege changes that landed in OpenMU after 0.9.10: the tax type and the guild command are now enums. Their 3 call sites now pass CastleSiegeTaxType and CastleSiegeGuildCommandType, with the same values as before. ToggleCastleGateRequest only renamed its parameter to isOpen; the client already sends 1 for "open".

The feature commits (feat(personal-shop): …, the review fixes, a merge of main and the currency menu)

  • GameLogic::PersonalShop (PersonalShopCurrency.h/.cpp):
    • describes a price as an amount, a kind (zen, item, account coin) and the server's PriceItemType;
    • decodes and encodes the three price fields, which the item list and the set-price packet share;
    • stores the allowed price types and orders them for the currency menu: zen, the account coins and the common jewels (Bless, Soul, Chaos, Life, Creation, Guardian, Harmony) first, then the other items in the server's order.
    • GetCurrencyName (PersonalShopCurrencyName.cpp) gives the currency name: the client's item name, or the coin name. It has its own file so the tests of the protocol logic don't need the game's item and text tables.
  • Network (WSclient):
    • handles C1 3F 0A (the allowed price types) and resets them at the character list;
    • reads item and coin prices. Before this change, the purchase list rejected the whole shop when an item had MoneyPrice == 0, which is what an item price looks like;
    • handles the new buy result LackOfPriceCurrency (the server sends the message);
    • keeps the price type when an item moves inside the shop.
  • Price table: CPersonalItemPriceTable stores the full price. The int overloads stay for zen.
  • UI: the price dialog is ShowPersonalShopItemValueDialog in MyShopInventory.cpp, a GenericDialogConfig dialog since the RmlUi migration.
    • When the server allows more than zen, the dialog names the currency in its own line ("Currency: Jewel of Bless").
    • Its "Currency" button opens a GenericMenuDialog with the common currencies. The other items the server allows are in a second menu, behind an "Other items" button.
    • A long menu takes two columns so it stays on the screen. Choosing a currency, or closing the menu, returns to the price dialog and keeps the typed amount.
    • Zen prices below the item value still ask for a confirmation.
    • Zen is still sent with the original 3F 01 packet, so it works with every server; other prices use 3F 09.
  • Tooltips: items and pets show e.g. Selling Price: 12 Jewel of Bless. The "short of zen" warning appears only for zen prices.
  • Control socket: shop goods with a non-zen price also report their price_item_type.
  • Translations (en, de):
    • new: W Coin(C), Goblin Points, Currency, Currency: %ls, Other items;
    • reused: the existing W Coin(P);
    • item names come from the item data.
  • Tests and docs:
    • tests/gamelogic/test_personal_shop_currency.cpp: decoding, an encode/decode round trip, and the order of the currency menu;
    • docs/personal-shop.md (usage);
    • docs/control-socket.md (the new field).

Related issues

MUnique/OpenMU#1186. Server side: MUnique/OpenMU#1190 (packets, merged) and MUnique/OpenMU#1191 (feature).

Checklist

  • I have read and followed docs/CODING_RULES.md.
  • I have built the project locally (see docs/build/README.md).
    • In the environment where I made this change, the CMake configure stopped because GitHub archive downloads (FetchContent) were blocked, so the client was not built or run there.
    • What I could check:
      • ResxGen runs without errors on the resx files.
      • The protocol code and its doctest compile with g++ (-Wall -Wextra) and pass.
      • The dialog and menu code compiles with g++ against stubs of the dialog configs.
      • The changed lines pass the CI's clang-format 21.1.8 check.
      • The dialog and both menus were previewed in both themes with the RmlUi preview tool (feat(tools): preview RmlUi documents in a browser #721).
    • The earlier version, before the RmlUi merge, was built and tested in-game by @eduardosmaniotto. The RmlUi port and the currency menu still need a build and an in-game check.
  • Changes are focused on one concern; the diff is as small as it reasonably can be. The version bump is a separate commit.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Y7XufeAZ22z7ocXAXUqUsE

sven-n and others added 2 commits October 11, 2026 12:24
Builds against MUnique.OpenMU.Network.Packets 0.9.11, which adds the
player shop currency packets (MUnique/OpenMU#1186), and regenerates the
packet bindings from it.

The castle siege tax type and guild command are enums in the new
definitions, so their call sites pass the enum values now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y7XufeAZ22z7ocXAXUqUsE
When the server allows other prices than zen for personal shops
(MUnique/OpenMU#1186), for example in jewels or account coins, the
client:

- remembers the allowed price types (C1 3F 0A), which the server sends
  on world entry and before a shop list;
- offers a currency button in the price input box, which switches
  between zen and the allowed price types, and sends other prices than
  zen with the extended set-price packet (C3 3F 09), whose price fields
  are encoded like the ones of the shop item list; zen still uses the
  original packet;
- reads the item and coin prices of the extended shop item list instead
  of refusing the whole shop when an item has no zen price;
- shows the price with its currency (item name or coin) in the item and
  pet tooltips;
- leaves the "not enough items/coins" message to the server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y7XufeAZ22z7ocXAXUqUsE
@sven-n
sven-n force-pushed the claude/openmu-1186-packet-nuget-dphx9w branch from bfa8a4a to 8f364e3 Compare October 11, 2026 12:25
@sven-n sven-n changed the title feat(personal-shop): prices in jewels and account coins feat(personal-shop): prices in items and account coins Oct 11, 2026
@eduardosmaniotto

eduardosmaniotto commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Branch claude/openmu-1186-packet-nuget-dphx9w doesn't build (3 errors)

1. ResxGen fails: W Coin(P) vs WCoin (P) both slug to WCoinP

ResxGen aborts:

Group 'Game': keys 'W Coin(P)' and 'WCoin (P)' both slug to identifier 'WCoinP'.

The commit adds WCoin (P) to Game.en.resx / Game.de.resx, but W Coin(P)
already exists (Game.en.resx:11016). Both slug to WCoinP
(tools/ResxGen/Naming.cs), which ResxLoader.cs rejects.

New code in PersonalShopCurrency.cpp needs I18N::Game::WCoinP, so the build
can't succeed until this is resolved. Note the old key also carries
legacy_id=3144.

Fix: either drop the new WCoin (P) entries and reuse the existing
W Coin(P) (code compiles unchanged; optionally update its <value> to the
new spelling), or give the new keys genuinely different wording (any
same-letter variant still collides) and update PersonalShopCurrency.cpp.

2. CALLBACK_RESULT does not name a type in PersonalShopPriceMsgBox.cpp:62

PersonalShopPriceMsgBox.cpp:62:1: error: 'CALLBACK_RESULT' does not name a type

CALLBACK_RESULT is declared inside namespace SEASON3B
(NewUIMessageBox.h:17). A return type written before the qualified
declarator-id isn't in class scope yet, so unqualified lookup fails on g++.
MSVC accepts it, which is why it went unnoticed. Every other file doing this
has using namespace SEASON3B;; the new file doesn't.

Fix: qualify the return type:

SEASON3B::CALLBACK_RESULT SEASON3B::CPersonalShopPriceMsgBox::LButtonUp(...)

3. test_personal_shop_currency fails to link (g_hFont, g_hWnd, …)

Linking the new test binary fails with hundreds of undefined references to
engine globals (g_hFont, g_hFontBold, g_hWnd, g_pUIManager, …) from UI
translation units inside libMuClient.a.

Chain: the test links raw MuClient → pulls PersonalShopCurrency.cpp.o →
its GetCurrencyName references ItemAttribute → pulls the huge
ZzzInfomation.cpp.o → cascade pulls all of UI → needs globals that live
only in Winmain.cpp, which is deliberately excluded from the lib and linked
into Main alone (src/CMakeLists.txt:112-123). Existing tests don't hit
this because the TUs they pull never cascade into UI globals.

Fix: split the TU — keep pure protocol logic (Decode/Encode/allowed-types)
in PersonalShopCurrency.cpp and move GetCurrencyName (the only function
needing ItemAttribute/I18N) to its own TU, sharing the protocol constants
via the header. The test link then stays small; Main (whole-archive) is
unaffected.

@eduardosmaniotto

Copy link
Copy Markdown
Contributor

I tested this branch locally after fixing the 3 issues with the build, and everything seems to be working fine:

Original client doesn't show custom currency items (expected):
original-client-ok

MuMain custom prices:
mumain-price

MuMain no balance message:
mumain-no-balance

MuMain custom currency purchase successful:
image

- Drop the new "WCoin (P)" translation, which collides with the existing
  "W Coin(P)" key (both become I18N::Game::WCoinP) and made ResxGen abort.
  Reuse the existing key and name the new one "W Coin(C)" alike.
- Qualify the return type of CPersonalShopPriceMsgBox::LButtonUp, which
  g++ doesn't find outside of the SEASON3B namespace.
- Move GetCurrencyName to its own translation unit. It needs the item
  and text tables of the game, which pulled UI globals of Winmain.cpp into
  the tests of the protocol logic. The protocol constants are shared
  through the header now.
- Format the changed lines with clang-format 21, as the CI checks them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y7XufeAZ22z7ocXAXUqUsE

sven-n commented Oct 12, 2026

Copy link
Copy Markdown
Owner Author

Thanks for testing this against the original client too, @eduardosmaniotto! All three issues are fixed in 9ab1ad0:

  1. ResxGen collision: the new WCoin (P) key is gone, and the existing W Coin(P) (I18N::Game::WCoinP) is used instead. I renamed the new coin key to W Coin(C) so the two match. The generated identifiers are still WCoinC and GoblinPoints. Without the fix, ResxGen aborts with the duplicate error. With it, ResxGen runs cleanly.
  2. CALLBACK_RESULT on g++: the definition is now SEASON3B::CALLBACK_RESULT SEASON3B::CPersonalShopPriceMsgBox::LButtonUp(...).
  3. Test linking: GetCurrencyName now lives in its own PersonalShopCurrencyName.cpp. PersonalShopCurrency.cpp keeps only the protocol logic and has no references to ItemAttribute or I18N. The protocol constants (PriceItemTypeGroupShift, PriceItemTypeNumberMask, FirstAccountCoinPriceItemType, AccountCoinCount, IsAccountCoin) now live in the header.

I also formatted the PR's changed lines with clang-format 21.1.8, the version the CI quality job uses on changed lines, because several of them failed that check.

Checked locally with g++ -Wall -Wextra: the currency tests link against the protocol unit alone and pass. A full client build still isn't possible in my environment.


Generated by Claude Code

@sven-n
sven-n marked this pull request as ready for review October 14, 2026 08:55
Conflicts with the UI reorganization and the RmlUi migration of the
message boxes:

- The personal shop price input is now the GenericDialogConfig dialog of
  MyShopInventory.cpp, so CPersonalShopPriceMsgBox is gone. When the
  server allows other prices than zen, the dialog's secondary button
  names the currency and shows the dialog again with the next allowed
  one, keeping the typed amount. Zen prices below the item value still
  ask for a confirmation.
- The castle siege guild command cast moved to UI/Combat/SiegeWarCommander.cpp.
- The shop item lists keep the decoded prices with main's new
  UI::Shop::AddBrowsedShopItem and item list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y7XufeAZ22z7ocXAXUqUsE
A long currency name didn't fit the dialog's secondary button. The
dialog now names the currency in its own line ("Currency: Jewel of
Bless"), and its "Currency" button opens a menu of the allowed
currencies instead of switching to the next one:

- the common ones first: zen, the account coins and the jewels (Bless,
  Soul, Chaos, Life, Creation, Guardian, Harmony);
- the other items the server allows behind an "Other items" button, in
  the order of the server.

A long menu takes two columns, so it stays on the screen. Choosing a
currency or closing the menu returns to the price dialog and keeps the
typed amount.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y7XufeAZ22z7ocXAXUqUsE
@sven-n
sven-n merged commit 112cece into main Oct 14, 2026
3 checks passed
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.

2 participants