Skip to content

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

Open
sven-n wants to merge 7 commits into
masterfrom
claude/pr-719-review-resubmit-as9xf0
Open

feat: Add Kanturu Refinery Tower event (resubmission of #719)#923
sven-n wants to merge 7 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
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