Skip to content

feat: 14 more in-game test scenarios, quests included (#632, part 2) - #87

Merged
Mosch0512 merged 18 commits into
mainfrom
feature/in-game-tests-2
Sep 29, 2026
Merged

Mosch0512 merged 18 commits into
mainfrom
feature/in-game-tests-2

Conversation

@Mosch0512

@Mosch0512 Mosch0512 commented Sep 28, 2026 •

Copy link
Copy Markdown
Owner

Part 2 of sven-n#632: seven more in-game test scenarios, seven quest scenarios, and a tester window that stays usable during a run. Builds on part 1 (fork PR #79, merged into the fork's main; upstream sven-n#652).

Scenarios

Category Scenario Clients What happens (all through clicks and keys)
Player Interactions chat 2 Enter opens the chat box, the line is typed, Enter sends it; the other player answers. Both lines show in both chat logs.
Player Interactions party 5 The leader presses D, clicks Party and right-clicks each of four players; each accepts with Enter. All five show the same party of five, leader first.
Player Interactions personal-shop 2 The seller opens its shop from the inventory, puts a jewel in with two clicks, types its price and the title, and opens the shop. The buyer opens it with D, Buy and a right-click on the seller, clicks the jewel and confirms. The jewel and 9,000,000 zen change owner.
Player Interactions trade-inventory-full 2 Three trades that can't complete: the second player has no room, then the first, then neither. Each closes with "inventory full" on both sides, and both keep the same items and zen.
NPC Interactions (new) npc-shop 1 Talks to Hanzo with a click on him. Sells a potion stack by dragging it onto the shop, and a Jewel of Bless (a dialog asks, Enter confirms), each for the tooltip's price; buys the cheapest item that fits, for its price.
NPC Interactions repair 1 Repairs the worn gloves with the inventory's repair button, and the boots at Hanzo with Repair all. Durability goes back to max and zen goes down.
Game Behaviour equip-all-slots 1 For each of the twelve slots, drags the item into the inventory and right-clicks it back on.

Quest scenarios (new category Quests)

All go through clicks on the NPC and on the answers of its quest dialog. The quest accounts of OpenMU's test data (first class, 100,000,000 zen, quest items prepared) stand next to Sebina and Devin in Devias.

Scenario Character What happens
quest-blade-knight quest1Dk Sebina's "Find the Scroll of the Emperor" (+10 points) and "Three Treasures of MU" (+10, Dark Knight → Blade Knight); then one point goes to strength with the character window's "+".
quest-soul-master quest1Dw The same two quests: Dark Wizard → Soul Master.
quest-muse-elf quest1Elf The same two quests: Elf → Muse Elf.
quest-hero-status-wizard quest3Dw Soul Master, then Marlon's "Gain Hero Status": one point more per level, paid at once for the 180 levels above 220.
quest-hero-status-elf quest3Elf The same for a Muse Elf, which also learns Infinity Arrow.
quest-combo quest2Dk Blade Knight, hero status, then "Secret of Dark Stone": the combo, still there after logging in again.
quest-evidence-of-strength quest3Dl Apostle Devin's first quest towards the third class: +20 points.
  • Finding Marlon: he wanders between Devias, Lorencia, Noria and Atlans, so the test visits his spots until he's there.
  • Not covered: the third class change itself. Devin's next quests need 60 kills of monsters with 75,000 to 90,000 HP in the Balgass Barracks, plus the Dark Elf (1.5 million HP).
  • Socket additions:
    • ui: window npc_quest; elements npc_quest.answer.N, npc_quest.complete, npc_quest.close and character.stat.*.
    • state: class_name, level_up_points, stats, combo, quests, and npc_quest (the page, its text, and each answer with what a click on it does).
    • Events: quest (a quest's new state, and each reward).
  • Accounts: personal-shop now uses the quest accounts' Dark Lords, because the quest scenarios change the Dark Knights' class.

Tester window

  • The test list takes the free height, with one line per test (eight fit at once, where two did before). The log sits below a divider that can be dragged.
  • The list stays scrollable during a run.
  • The running test is highlighted and scrolled into view, and a "Now" line shows it with its current step.

Control socket

  • nearby → pixel: where a player or NPC is drawn, projected with the world camera kept from the frame that casts the mouse ray. The g_Camera that state sees is the item-preview camera. click-ui on that pixel talks to an NPC or targets a player.
  • ui:
    • windows: chat_input, party, command, my_shop, purchase_shop;
    • elements: inventory.repair, inventory.my_shop, npc_shop.repair, npc_shop.repair_all, my_shop.title, my_shop.open, my_shop.close, command.trade, command.purchase, command.party.
  • slot-pixel: grids npc_shop, my_shop, purchase_shop.
  • state:
    • items carry max_durability, repair_price (while a click would repair), and sell_price (while an NPC shop is open), as the tooltips show them;
    • npc_shop, my_shop, purchase_shop, repair_mode.
  • Events: party invited, and party result for an invitation that formed no party.

Client changes outside the socket

  • Editor: the editor's input blocker, the "Open Editor" hover check and the chat box's editor check no longer drop keys or clicks the control socket injects. They follow the real pointer, which has nothing to do with an injected click.
  • CNewUIMyInventory: one check for when its repair and shop buttons show, instead of two copies.
  • Accessors: the NPC shop, the personal shop and the command window expose their grid, buttons and title field. All of it is read only by the control socket.

Tester

  • Shared steps: Meeting (walk to a spot or to a player, find where a character is drawn), Trading, Npcs (walk up to an NPC, talk with a click), Keys.PressUntilAsync, ItemSlots.FreeArea.
  • Screenshots: a step can name the clients it pictures; a failed step pictures all of them.
  • Key retry: an injected Enter was occasionally lost on the second client, about one run in six; the cause isn't known yet. Keys.PressUntilAsync presses a key again when nothing happened within 2 s. That's safe because a handled key shows its effect at once. Each extra press is noted in the log.

Findings

  • Repair price: the client shows a higher price than OpenMU 0.9.10 charges. Gloves: 1,700 shown, 1,400 charged. Repair all: 720 shown, 620 charged. CalcRepairCost uses a factor of 3.5 where ItemPriceCalculator.CalculateRepairPrice uses 3.0. The test logs both and doesn't fail on it.
  • Full-inventory message: after a trade fails on a full inventory, both sides read "your inventory is full". OpenMU sends the same result code to both, so the client can't tell whose inventory it was.
  • Item data: "Pendant of Lighting" in the item data is a typo for "Lightning".

Tested

  • Windows: all 16 scenarios passed together on a fresh server (about 10 minutes); before the quests, all nine passed together, twice, on a freshly recreated OpenMU 0.9.10 demo server (Release x64 editor build, about 4.5 minutes). Every new scenario also passed on its own.
  • Unit tests: ctest (Debug) 339/339.
  • Static checks: clang-format 21.1.8 on the changed lines and cppcheck 2.13 (CI flags) are clean.
  • Report size: a run of all nine scenarios writes a 38 MB report, over GitHub's 25 MB limit for attachments. A run of a few scenarios, or a lower JPEG quality in the window, stays under it.

A scenario can now click an NPC or a player the way a player does:
`nearby` gives each player, monster and NPC a `pixel` at the middle of
the box the mouse picks it by, or null while it is not drawn.

The projection uses the world camera as it is when the mouse ray is cast
(Frames::RecordWorldCamera in MainScene), because later in the frame
other cameras draw the item previews; g_Camera at the time of `state` put
every character at the same depth. Clicking the pixel of the vault keeper
in Lorencia opens the vault.
The editor drops the frame's Enter and mouse buttons while ImGui wants the
mouse, and the left button while the real pointer rests on the "Open
Editor" button; the chat box does not open on Enter then either. ImGui
follows the real pointer, so a player's mouse over a test client's window
could swallow a key or click the control socket injected. Those never went
through ImGui: the checks now let them pass (Synthetic::IsIdle, a constant
true without the control socket).
- `chat` (Player Interactions): two players meet in Lorencia; one opens
  the chat box with Enter, types a line and sends it with Enter, the other
  answers the same way, and both lines show in both chat logs.
- The walking and trading steps of the trade scenario moved into Meeting
  and Trading, for the scenarios to come; GameClient has ZenAsync and
  CountAsync.
- Keys.PressUntilAsync presses a key until its effect shows, at most three
  times: an injected Enter was lost now and then on the second client (the
  cause is not known yet), and each extra press is noted in the log.
- `ui` also reports chat_input, party, command, my_shop and purchase_shop.
- `npc-shop` (new category NPC Interactions): test7Dk walks up to Hanzo
  the Blacksmith and talks to him with a click on him; drags a potion
  stack onto his shop (sold at once) and a Jewel of Bless (a dialog asks,
  Enter confirms), each for the selling price its tooltip shows; then
  buys the cheapest of his goods that fits, for its price.
- slot-pixel grid `npc_shop`; `ui` elements inventory.repair,
  inventory.my_shop, npc_shop.repair and npc_shop.repair_all while shown.
- state: `npc_shop` (repair_shop, tax_rate, goods with their price, tax
  included), `repair_mode`, `max_durability` per item, and `sell_price`
  per inventory item while an NPC shop is open.
- CNewUIMyInventory has one check for when its repair and shop buttons
  show (render and click used two copies); CNewUINPCShop exposes its grid
  and repair buttons.
- Npcs (walk up to an NPC, talk with a click on its pixel) and
  ItemSlots.FreeArea, shared with the Icarus scenario.
- `repair` (NPC Interactions): test9Dk opens the inventory, presses its
  repair button and clicks the worn gloves; then talks to Hanzo the
  Blacksmith with a click and presses Repair all for the boots. Each
  repair brings the durability back to its maximum and costs zen; what it
  cost and what the client showed go into the log.
- state: `repair_price` on each worn item while a click would repair it
  (the tooltip's own ConvertRepairGold), and npc_shop.repair_all_price.

The client shows a higher price than OpenMU 0.9.10 charges (gloves 1,700
shown, 1,400 charged; Repair all 720 shown, 620 charged): CalcRepairCost
uses 3.5 * sqrt(base) * sqrt(sqrt(base)) where OpenMU's
ItemPriceCalculator.CalculateRepairPrice uses 3.0.
testgm2Sum wears something in all twelve equipment slots. Slot by slot,
the item is dragged off onto a free area of the inventory with two clicks
and put back on with a right-click on it; each move is checked in the
equipment and the inventory.
- `party` (Player Interactions): five clients. The leader opens the
  command window with D; for each of four players it clicks Party and
  right-clicks the player where its client draws it, and the player
  accepts the invitation with Enter. After each join the party lists of
  both show the new size with the leader first; at the end all five show
  the same party of five.
- `party` events: `invited` (with the inviter's name) and `result` for
  the server's answer to an invitation that formed no party (denied,
  full, other_party, ...).
- `ui` elements command.trade, command.purchase and command.party while
  the command window is open.
- A step can name the clients it pictures, so five clients do not put
  five screenshots into every step; a failed step pictures all of them.
- Meeting.PixelOfAsync finds where a client draws a player or an NPC.
test7Dw and test8Dw have no free 2x2 area. Three trades that cannot
complete:
1. the first offers a 2x2 item the second has no room for;
2. the second offers one the first has no room for;
3. both: the first offers a 2x2 item, the second more single items than
   the first then has free squares, taken on alternate squares so that no
   2x2 area of its own frees up.

Each time both confirm, the trade closes for both with "inventory full",
and both keep the same items in the same slots and the same zen.
- `personal-shop` (Player Interactions): quest1Dk opens the inventory,
  presses its shop button, puts a Jewel of Bless into the shop with two
  clicks, types its price into the dialog, types the shop's title and
  opens the shop (a dialog asks, Enter confirms). quest2Dk opens it
  through the command window (D, Buy, a right-click on the seller), clicks
  the jewel and confirms with Enter: the jewel and 9,000,000 zen change
  owner and the seller's shop is empty.
- state: `my_shop` (open, title, goods with prices) and `purchase_shop`
  (seller, title, goods with prices, or null); slot-pixel grids `my_shop`
  and `purchase_shop` (slots 204 and up, as the server numbers them);
  `ui` elements my_shop.title, my_shop.open and my_shop.close.
- in-game-tests.md: all nine scenarios with their categories and test
  accounts, why test items start worn, the shared helpers, what is not
  covered yet.
- control-socket.md: the new `ui` elements, `slot-pixel` grids, state
  fields (shops, repair and sell prices, max_durability, repair_mode),
  party events, and that `type` enters text.
- Steps where only one player acts picture only that player: 20 fewer
  screenshots in a run of all scenarios.
…s more

- The list takes the window's free height, and each test is one line
  (the description in full on hover); the log sits below a divider that
  can be dragged. Eight tests and the three categories fit at once in the
  default window, where two did before.
- A run no longer disables the list: it scrolls and folds as before, only
  the checkboxes and the wait and quality fields are locked.
- The running test's row is highlighted, its category opens and the row
  scrolls into view when it starts; a "Now" line under the overall bar
  shows the test and its current step.
- "Open report folder" also works during a run.
The quest scenarios to come change the quest Dark Knights' class; the
Dark Lords take no class change quest, stand on the same tile in Devias
and have the same zen and jewels.
Seven scenarios in the new category Quests, all through clicks on the NPC
and on the answers of its quest dialog:
- quest-blade-knight, quest-soul-master, quest-muse-elf: Sebina's "Find
  the Scroll of the Emperor" and "Three Treasures of MU" turn a Dark
  Knight, Dark Wizard and Elf into their second class (+10 level-up points
  each); the Blade Knight then spends a point on strength with the
  character window's "+".
- quest-hero-status-wizard, quest-hero-status-elf: a level 400 Soul Master
  and Muse Elf find Marlon (he wanders between Devias, Lorencia, Noria and
  Atlans) and gain hero status: one more point per level, which the server
  pays for the 180 levels above 220 at once; the Muse Elf learns Infinity
  Arrow.
- quest-combo: a Blade Knight gains hero status and Marlon's combo, which
  is still there after logging in again.
- quest-evidence-of-strength: Apostle Devin's first quest towards the
  third class (+20 points).

Control socket:
- `ui`: window npc_quest; elements npc_quest.answer.N (the rows its
  answers are clicked in, as CNewUINPCQuest hits them), npc_quest.complete,
  npc_quest.close, and the character window's character.stat.* buttons.
- state: class_name, level_up_points, stats, combo, the seven legacy
  `quests` with their state, and `npc_quest` (quest, page, text, need_zen,
  answers with what a click on each does).
- `quest` events: a quest's new state (decoded by the client from A1/A2)
  and each reward of A3.
@Mosch0512 Mosch0512 changed the title feat: seven more in-game test scenarios (#632, part 2) feat: 14 more in-game test scenarios, quests included (#632, part 2) Sep 29, 2026
Marlon wanders between Devias, Lorencia, Noria and Atlans, so the quest
scenarios looked on all four spots, about a minute each time. The test
server now logs his moves: docker-compose.yml sets OpenMU's
WanderingMerchantsPlugIn to Debug and puts the log properties, with the
game server's scope, into each line. The tester reads the container's log
(TestServer.WanderingNpcAsync) for his last spawn on game server 0, the
one behind the published port, warps to that map and walks up to him:
13 to 30 s instead of 45 to 70 s. A freshly started server places him
some 20 s after start, so the tester waits up to 30 s for that line; on
another server, or when he is not where the log says, it searches as
before.
Every category header in the window (Player Interactions, NPC
Interactions, Game Behaviour, Quests) has a Check all / Uncheck all
button for its tests, next to the one for all tests; it follows the
category's checkboxes, is locked during a run, and a click on it does not
fold the category.

@Mosch0512 Mosch0512 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of 67e2094: 15 points, each in an inline comment. Nothing was run for this review; each point comes from reading the code.

Bugs, most serious first:

  1. MainWindow.axaml: the log divider resizes the Run bar, not the list or the log.
  2. PersonalShopScenario.cs: a slow server makes the price step press Enter again, which opens the chat box.
  3. RepairScenario.cs: the equipment is read right after login, before it may have arrived.
  4. Quests.cs: the reward wait doesn't check whose reward it is.
  5. ControlState.cpp: repair_price is reported for an item value of -1, where the tooltip shows no price.
  6. ControlTaps.cpp: the amount of the class-change rewards is the server's class code.
  7. ControlCommandsUi.cpp: the unknown-grid error leaves out the shop grids.
  8. ControlState.cpp: one quest state has two names, none in state and unknown in the events.
  9. MainWindow.axaml.cs: a run saves folded categories as unfolded.

Cleanup: the Marlon lookup rereads the whole container log; three duplications (the pixel projection, the grid fit, the stat-button count); the checks for injected input in three places; QuestScenarios.cs holds six classes.

Comment thread tools/InGameTests/Gui/MainWindow.axaml Outdated
Comment thread tools/InGameTests/Scenarios/PersonalShopScenario.cs
Comment thread tools/InGameTests/Scenarios/RepairScenario.cs
Comment thread tools/InGameTests/Scenarios/Quests.cs Outdated
Comment thread src/source/App/Control/ControlState.cpp Outdated
Comment thread src/source/App/Control/ControlState.cpp Outdated
Comment thread tools/InGameTests/Scenarios/TradeInventoryFullScenario.cs
Comment thread src/source/UI/NewUI/Character/NewUICharacterInfoWindow.cpp Outdated
Comment thread src/source/UI/NewUI/Widgets/NewUIChatInputBox.cpp Outdated
Comment thread tools/InGameTests/Scenarios/QuestScenarios.cs Outdated
Bugs:
- The window's divider sat between the Run bar and the log, so it
  resized the bar; the Run bar moved above the list, and the divider is
  between the list and the log.
- personal-shop pressed Enter again when the server was slow to put the
  priced jewel into the shop, which opened the chat box: the step now
  presses until the dialog closes and waits for the price.
- repair read the equipment right after login; it waits for it now.
- The quest reward wait matches the character's own name: the client
  records the rewards of every player in view.
- `repair_price` is left out where ItemValue is -1 (a Dark Lord pet
  without its data), as the tooltip shows no price then.
- The class-change rewards carry no `amount`: the packet's number is the
  server's class code; the new class is in `class`.
- The unknown-grid error names the shop grids too.
- A quest state outside the known ones reads `unknown` in `state` as in
  the events.
- A run no longer saves the categories it unfolded to show its test as
  unfolded.
- The Marlon lookup reads only the last 20,000 log lines and waits for his
  line only while the container is younger than a minute; an older test
  server without the line (set up before its compose file logged it) gets
  no 30 s wait.

Cleanup:
- CameraProjection::WorldToWindowPixel is the projection `nearby` uses,
  next to the mouse ray it mirrors.
- ItemSlots.FreeArea takes any grid, for the trade window and the NPC shop.
- CNewUICharacterInfoWindow::StatButtonCount is the one count of the "+"
  buttons.
- Synthetic::IsInjecting is the one test the editor's input checks use.
- One file per quest scenario.
@Mosch0512
Mosch0512 changed the base branch from feature/in-game-tests to main September 29, 2026 21:11
@Mosch0512 Mosch0512 closed this Sep 29, 2026
@Mosch0512 Mosch0512 reopened this Sep 29, 2026
@Mosch0512
Mosch0512 merged commit 24a6830 into main Sep 29, 2026
39 of 40 checks passed
@Mosch0512
Mosch0512 deleted the feature/in-game-tests-2 branch September 29, 2026 22:58
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.

1 participant