Skip to content

feat: Add Kanturu Refinery Tower event (resubmission of #719) - #923

Merged
sven-n merged 10 commits into
masterfrom
claude/pr-719-review-resubmit-as9xf0
Sep 8, 2026
Merged

feat: Add Kanturu Refinery Tower event (resubmission of #719)#923
sven-n merged 10 commits into
masterfrom
claude/pr-719-review-resubmit-as9xf0

Conversation

@sven-n

@sven-n sven-n commented Aug 30, 2026

Copy link
Copy Markdown
Member

Resubmission of the stale PR #719 by @apraxico, rebased onto the current master and combined with the fixes @foxtnaider contributed on his kanturu-fixes branch, plus a review pass on top.

Credit for the implementation goes to @apraxico; credit for making it build and actually run goes to @foxtnaider, who tested the result end-to-end on a Season 6 server.

What it adds

The server-side logic for the Kanturu Refinery Tower mini-game (map 39), following the plug-in patterns of BloodCastle and ChaosCastle:

File Purpose
KanturuContext Event game loop: waves → Maya's hands → Nightmare → Tower of Refinement
IKanturuEventViewPlugIn / KanturuStates View plug-in interface and the client state enums
KanturuEventViewPlugIn Sends the 0xD1 packets to the client
KanturuGatewayPlugIn Gateway Machine NPC which opens the entry dialog
KanturuStartPlugIn / KanturuStartConfiguration / KanturuGameServerState Periodic start of the event
KanturuEnterRequestHandlerPlugIn / KanturuInfoRequestHandlerPlugIn / KanturuGroupHandlerPlugIn Handlers for the 0xD1 client packets
KanturuInitializer Seeds the MiniGameDefinition
AddKanturuDataUpdatePlugIn / AddKanturuMapContentUpdatePlugIn Bring the configuration and the map content into existing databases

Modified: MiniGameType (new Kanturu value), GameContext (creates a KanturuContext), GameConfigurationInitializer, KanturuEvent (bosses and wave spawns), the server-to-client packet definitions and the player messages.

Fixes carried over from the follow-up work

  • The seven Kanturu <Packet> elements were direct children of the root, outside <Packets>. GenerateExtensions.xslt only iterates the children of <Packets>, so the generator emitted nothing for them and the SendKanturuXxxAsync methods had been added to the generated ConnectionExtensions.cs by hand. A clean build overwrote them, which is the CS1061 build failure reported on feat: Add Kanturu Refinery Tower event implementation #719. The elements are now inside <Packets> and everything is generated.
  • GameContext had no case MiniGameType.Kanturu, so the event fell through to a plain MiniGameContext: it opened the map, accepted players and then did nothing.
  • The missing ObjClassH/ObjClassL arguments on SendKanturuMayaWideAreaAttackAsync (CS7036).

Review pass in this PR

  • KanturuStartConfiguration.TowerOfRefinementDuration was write-only: GameContext constructs the context without the optional parameter, so the hard-coded one-hour default always won. The context now reads the value from the start plug-in's configuration.
  • All 36 analyzer warnings the new files introduced are resolved (SA1201/1204/1210/1501/1515/1611/1623/1117, VSTHRD200, CS1574), including the six CS1591 warnings in the generated packet code, which are fixed by documenting the KanturuStateChange.StateType enum values in the packet definition.
  • The 16 event wave spawns were maintained twice — once in KanturuEvent, once in AddKanturuMapContentUpdatePlugIn. They are now shared, and the monster numbers are named constants.
  • The new GameServer plug-ins use localized PlugInResources names and descriptions like the other plug-ins there.
  • The state enums moved out of the view plug-in interface file, the byte order mark of the two touched Season 6 files is restored, and the stale Nightmare coordinates in the map comments are corrected.

Verification

  • dotnet build src/Startup/MUnique.OpenMU.Startup.csproj — clean, from a fresh checkout, with the packet generator running: 0 errors, no warnings from the new files, and the generated files are byte-identical to what is committed.
  • dotnet build ... -p:ci=true (what the CI workflow runs) — 0 errors.
  • dotnet test tests/MUnique.OpenMU.Persistence.Initialization.Tests — 12 passed, 2 skipped.
  • In-game testing of the event itself was done by @foxtnaider (Maya rises, all three phases, both hands, Nightmare with its teleports, victory screen, tower opens; two characters, full clear) — see the screenshots on #719. I have not run the client myself.

Closes #719.


Generated by Claude Code

claude and others added 7 commits August 30, 2026 22:26
Resubmits the work of PR #719 (by @apraxico) together with the fixes
contributed by @foxtnaider on top of the current master.

Contents:
- KanturuContext with the eight event phases, Maya hands, Nightmare
  boss and the Tower of Refinement phase
- View plug-in, packet handlers and packet definitions for the D1
  packet group
- Periodic start plug-in and configuration
- Season 6 initializer, map spawn definitions and configuration
  update plug-ins for existing databases

Fixes taken from the follow-up work:
- Kanturu packets are defined inside <Packets> so the XSLT generator
  emits the connection extensions instead of them being hand-written
- GameContext creates a KanturuContext for MiniGameType.Kanturu

Co-Authored-By: apraxico <80994201+apraxico@users.noreply.github.com>
Co-Authored-By: foxtnaider <46683504+foxtnaider@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G6STKnRbNYW5JPSuNXNFx4
Follow-up on the resubmitted work of #719:

- Resolve all analyzer warnings the new files introduced (SA1201,
  SA1204, SA1210, SA1501, SA1515, SA1525, SA1611, SA1623, SA1117,
  VSTHRD200, CS1574).
- Apply the configured KanturuStartConfiguration.TowerOfRefinementDuration:
  it was only settable, the context always used its hard-coded default,
  because GameContext creates the context without the optional parameter.
  The context now reads the value from the start plug-in's configuration.
- Move the Kanturu state enums into their own file, so the view plug-in
  interface file only contains the interface.
- Document the KanturuStateChange.StateType enum values in the packet
  definition, which removes six CS1591 warnings from the generated code.
- Share the event wave spawns between the map initializer and
  AddKanturuMapContentUpdatePlugIn instead of maintaining two copies of
  the same 16 spawn areas, and name the monster numbers.
- Use localized plug-in names/descriptions (PlugInResources) for the
  new GameServer plug-ins, like all other plug-ins there.
- Restore the byte order mark of the two touched Season 6 files and fix
  the Nightmare spawn coordinates in the map comments.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G6STKnRbNYW5JPSuNXNFx4
ServerToClientPacketTests.cs is generated from ServerToClientPackets.xml
and checked in. As long as the Kanturu packets were outside <Packets>,
the generator skipped them, so the file didn't contain their tests.
Now that they are inside, it does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G6STKnRbNYW5JPSuNXNFx4
The comment line ended with a semicolon and started with a method name,
so SonarCSharp_S125 flagged it as commented out code (the one issue
Codacy reported on this PR). Same meaning, different wording.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G6STKnRbNYW5JPSuNXNFx4
Addresses the review of the Kanturu event.

Own sub-namespace, one type per file:
All Kanturu game logic moved into MUnique.OpenMU.GameLogic.MiniGames.Kanturu.
The four client state enums, which shared one file, now have their own.

A configuration instead of hardcoded values:
The run of the event is described by a KanturuEventDefinition, which is
configured at the KanturuStartPlugIn and can therefore be changed in the
admin panel without code changes. It holds the intro, the ordered list of
phases and the settings of the Tower of Refinement.

Each KanturuPhaseDefinition describes what the context previously had
hardcoded in its game loop and in the switch of OnMonsterDied: the state
which is sent to the clients, the spawn wave, the monsters which count
towards the kill target, the time limit, the messages and the standby time
after the phase. A KanturuPhaseKind selects how a phase is executed, so the
three repeating shapes - monster wave, transition and boss fight - each
exist once instead of once per phase. The boss fight gets its teleport
positions and health thresholds from KanturuNightmareDefinition, the
barrier areas and the Elphis coordinates come from the definition too.

The game loop is now a foreach over the phases, and OnMonsterDied is a
lookup in the current phase instead of a nine case switch.
KanturuEventDefinition.Default describes the original season 6 event, so
the behaviour is unchanged.

Opened NPC check:
The gateway plug-in now sets LeavesDialogOpen, so the player stays assigned
to the Gateway Machine while its dialog is open. The 0xD1/0x00 and 0xD1/0x01
handlers check that assignment, like the castle siege handlers do, so the
packets can't be used without talking to the NPC first. On a successful
entry the assignment is cleared, because the client closes the dialog when
it changes the map.

Also adds the byte order mark to the Kanturu files which still missed it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G6STKnRbNYW5JPSuNXNFx4
The phases and the boss fight referenced their monsters by number, which
required the numbers to be looked up by hand. They now hold MonsterDefinition
references instead, so the admin panel can offer the monsters for selection.

The custom configuration of a plug-in is serialized with the
ByDataSourceReferenceHandler, so the references are stored as the id of the
monster and resolved from the game configuration when they are read - the
same way the NpcChatCommandPlugIn references its merchant.

Because ISupportDefaultCustomConfiguration.CreateDefaultConfig has no game
configuration, it can't resolve the monsters. KanturuEventDefinition.Default
therefore became CreateDefault(GameConfiguration), and the data
initialization seeds the configuration of the start plug-in with it, like it
already does for the BlessJewelConsumeHandlerPlugIn. For databases which have
no configuration yet, the context falls back to CreateDefault with the game
configuration it runs on, so the event works either way.

The spawned monsters are compared to the configured definitions by their
number, because the definition of a spawned monster can be a different
instance than the configured one.

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

didiconcs commented Sep 4, 2026

Copy link
Copy Markdown

I tested this independently in a clean, isolated worktree (checked out 010c55a, the current tip of this branch), separate from my own working copy, so I could reproduce your verification steps end to end.

Build

  • dotnet build src/Persistence/SourceGenerator/... then dotnet build src/Startup/MUnique.OpenMU.Startup.csproj -c Debug: 0 errors. 186 pre-existing warnings, none of them touching any Kanturu file (I grepped the full build log for "Kanturu" to confirm).
  • I went a step further on the packet-generation claim specifically, since that's the root cause of the original feat: Add Kanturu Refinery Tower event implementation #719 failure: I ran the build without -p:ci=true (so the PreBuild target's XslTransformation/npx pipeline actually executes) and diffed the regenerated ConnectionExtensions.cs, ServerToClientPackets.cs and ServerToClientPacketsRef.cs against what's committed, byte-identical, git status clean. So the "generated files are byte-identical to what's committed" claim isn't just asserted, it reproduces.
  • dotnet test tests/MUnique.OpenMU.Persistence.Initialization.Tests: 0 failed, 12 passed, 2 skipped, matches your numbers exactly.
  • dotnet test tests/MUnique.OpenMU.Network.Packets.Tests --filter Kanturu: 9/9 passed.

The three problems from #719, confirmed fixed and wired end to end, not just patched locally:

  1. Moonstone Pendant requirement. KanturuEvent.CreateMapAttributeRequirements() calls CreateRequirement(Stats.MoonstonePendantEquipped, 1), and the item itself (Jewelery.cs, item 38) attaches an ItemBasePowerUpDefinition that sets that exact attribute to 1 when equipped. The generic GameMapDefinitionExtensions.TryGetRequirementError (pre-existing, unmodified) reads player.Attributes[requirement.Attribute], so this is enforced through the same mechanism other maps already use, not something bolted on only for display.
  2. GameContext mini-game factory. case MiniGameType.Kanturu: miniGameContext = new KanturuContext(...) is present and correctly placed before the default fallback to plain MiniGameContext.
  3. XML packet placement. All seven <Packet> Kanturu elements are inside <Packets>...</Packets> in ServerToClientPackets.xml (verified with a line-range check), which is exactly what GenerateExtensions.xslt needs to emit the SendKanturuXxxAsync extension methods, and the regeneration test above confirms it actually does.

Other things I checked while reading the diff:

  • KanturuStartConfiguration/KanturuEventDefinition.TowerOfRefinementDuration is genuinely read now (KanturuContext.RunTowerOfRefinementAsync at the _definition.TowerOfRefinementDuration line), so the "was write-only" fix holds up.
  • The UpdateVersion enum entries (AddKanturuData = 112, AddKanturuMapContent = 113) are appended at the end, not inserted in the middle, good, that's the safe way to extend this enum without shifting existing migration ids.
  • KanturuContext is noticeably more careful about concurrency than its siblings (BloodCastleContext only wraps a score counter in Interlocked); the Kanturu implementation guards the boss-teleport/health-check race and the barrier-open race with explicit Interlocked flags and comments explaining why. Nice attention to detail for code that runs across the game loop task and the OnMonsterDied event handler concurrently.
  • AddKanturuMapContentUpdatePlugIn's KanturuMapContentSeeder subclassing KanturuEvent to reuse EventWaveSpawns/CreateMonsters instead of re-implementing the wave data is a good way to avoid the "maintained twice" problem the PR description mentions, and the comment explaining why it can't just call the regular CreateMonsterSpawns() path (deterministic-id collisions with the laser traps) is genuinely useful for future readers.

Minor nitpicks, nothing blocking:

  • The diff to UpdateVersion.cs turns two previously-empty blank lines into lines with 4 trailing spaces (lines 552/567 in the new version), that's the source of the two SA1028 warnings in the build log. Trivial, but since the PR description specifically calls out "no warnings from the new files," worth a one-line fix since UpdateVersion.cs itself is a touched file.
  • KanturuEventViewPlugIn.Convert/ConvertChange are two near-identical one-line casts (KanturuStateInfo.StateType vs KanturuStateChange.StateType, otherwise the same body). Not worth blocking on, just noting in case you want to fold it into one generic helper.
  • KanturuGatewayPlugIn.SendKanturuStateInfoAsync's canEnter flag is derived purely from the event phase/timing, not from whether the player actually has the pendant equipped, so the client's Enter button can appear enabled for a player without the pendant, who then gets rejected by the map requirement check on WarpToAsync. That's consistent with how other OpenMU events surface this (the requirement message shows after the failed attempt, not proactively in the dialog), so I'm not asking for a change, just flagging it as the existing UX pattern rather than something new this PR should have handled differently.

I didn't manage to spin up the server end-to-end (no Docker/Postgres on my test machine) to click through Game Configuration → Mini Games in the AdminPanel, so I can't personally confirm the visual/admin side beyond what @foxtnaider already documented with screenshots on #719. Everything I could verify independently checks out: clean build, byte-identical codegen, full test suite, and a manual read of the three previously-reported bugs plus the surrounding code. Nice work pulling this together from two separate contributions.

Resolves the UpdateVersion conflict: master added ConfigureCastleSiegeLifeStone
as 112, so the two Kanturu updates move to 113 and 114. They are only
referenced by name, so the renumbering doesn't affect anything else.

Also removes the indentation of the two otherwise empty lines around them,
which SA1028 reported.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G6STKnRbNYW5JPSuNXNFx4
Convert and ConvertChange were two near-identical one line casts, as
didiconcs noticed in the review. Each had a single caller, so they are
inlined instead of folded into one generic helper - a generic conversion
between the two unrelated packet enums would have to go through object and
box the value.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G6STKnRbNYW5JPSuNXNFx4
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 7, 2026

Copy link
Copy Markdown

Deploying openmudocs with  Cloudflare Pages  Cloudflare Pages

Latest commit: d84958c
Status: ✅  Deploy successful!
Preview URL: https://ba5f308b.openmudocs.pages.dev
Branch Preview URL: https://claude-pr-719-review-resubmi.openmudocs.pages.dev

View logs

Entry check:
EnterMiniGameAction warps the player to the entrance of the mini game
directly, and PlayerMapTransitions.WarpToAsync doesn't evaluate the map
requirements - only the warp actions do. The pendant was therefore only
required by the client, which is what jkok25 reported on #719. The action
now checks the requirements of the entrance map and shows the same message
the warp actions use. Kanturu and Icarus are the only maps with
requirements, and Icarus is no mini game, so nothing else changes.

Wear during the event:
The items which provide an attribute the event map requires now lose
durability in a configurable interval. When such an item is destroyed, it
is removed from the inventory and the player is moved to the safezone of
the event map, which also removes it from the running event.

The items aren't hardcoded: they are looked up as the equipped items whose
definition provides one of the attributes in the map requirements, so it
follows the Moonstone Pendant through the same configuration which gates
the entry.

The players are moved out after ForEachPlayerAsync returned: it holds the
reader lock of the enter lock, and removing a player from the map waits for
the same lock as a writer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G6STKnRbNYW5JPSuNXNFx4
@sven-n
sven-n merged commit 15c2705 into master Sep 8, 2026
2 of 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.

3 participants