feat: Add Kanturu Refinery Tower event implementation#719
feat: Add Kanturu Refinery Tower event implementation#719apraxico wants to merge 3 commits intoMUnique:masterfrom
Conversation
Implements the full server-side logic for the Kanturu Refinery Tower mini-game event (maps 38-40), following the same plugin patterns used by BloodCastle and ChaosCastle. New files: - KanturuContext: event game loop with 8 sequential phases (Phase 1-3 wave clears, Nightmare preparation, Nightmare boss fight, Tower of Refinement victory) - IKanturuEventViewPlugIn: view plugin interface for client communication - KanturuEventViewPlugIn: sends state packets to connected clients - KanturuGatewayPlugIn: NPC gateway plugin for event entry - KanturuStartPlugIn / Configuration / GameServerState: periodic task scheduler that starts the event on a configurable interval - KanturuEnterRequestHandlerPlugIn: handles C1-D1-01 entry packet - KanturuInfoRequestHandlerPlugIn: handles C1-D1-00 info packet - KanturuGroupHandlerPlugIn: packet group handler registration - KanturuInitializer: seeds MiniGameDefinition and spawn waves Modified files: - MiniGameType.cs: added Kanturu enum value - GameConfigurationInitializer.cs: registers KanturuInitializer - KanturuEvent.cs: added full wave monster spawn definitions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces the complete server-side implementation of the Kanturu Refinery Tower event, a multi-phase mini-game designed to enhance player engagement. It provides a robust framework for managing event progression, monster spawns, boss mechanics, and client-side synchronization, significantly expanding the game's dynamic content offerings. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive implementation of the Kanturu Refinery Tower event. The changes are well-structured, following existing patterns for mini-games and plugins. The core event logic in KanturuContext is well-thought-out, especially regarding concurrency and the different event phases. The new view plugins and packet handlers are correctly implemented. I've identified a couple of areas for improvement related to exception handling in asynchronous fire-and-forget tasks to enhance server stability. Overall, this is a solid feature implementation.
| var pc = (byte)Math.Min(255, this.PlayerCount); | ||
| _ = Task.Run(() => this.SendMonsterUserCountAsync(remaining, pc).AsTask()); |
There was a problem hiding this comment.
This fire-and-forget task can lead to an unhandled exception if SendMonsterUserCountAsync throws, for example due to a player disconnecting. This could crash the server process. It's safer to handle potential exceptions within the task, for instance by wrapping the call in a try-catch block that logs the error. This applies to all similar Task.Run calls in this method used for sending client updates.
| // Fire barrier opening immediately from the death event. | ||
| // Do NOT wait for the game loop — it may be interrupted by | ||
| // GameEndedToken cancellation before reaching OpenElphisBarrierAsync. | ||
| _ = Task.Run(() => this.OpenElphisBarrierAsync().AsTask()); |
There was a problem hiding this comment.
This fire-and-forget task for OpenElphisBarrierAsync lacks exception handling. An exception here would be unhandled and could crash the server. Since opening the barrier is a critical part of the event progression, any failure should be logged. Please consider adding a try-catch block to handle potential exceptions.
There was a problem hiding this comment.
- Convert static classes to enums, remove numerical values. In the packet definitions, similar enums are required, where the values should be specified. Also mapping code is required to map between the enum types.
- Use the enum types for method parameters
- Change time parameters like
remainTimeSecto beTimeSpan. The conversion to seconds can be done in the view plugin implementation. - Change method names from
Send...toShow.... The game logic should not be aware that it's actually sending something over the network. - Change counts from byte to int. Conversion to byte can be done inside the view plugin implementation.
| private readonly IMapInitializer _mapInitializer; | ||
| private readonly TimeSpan _towerOfRefinementDuration; | ||
|
|
||
| private volatile KanturuPhase _phase = KanturuPhase.Open; |
| private int _waveKillCount; | ||
| private int _waveKillTarget; | ||
| private TaskCompletionSource _phaseComplete = new(TaskCreationOptions.RunContinuationsAsynchronously); | ||
| private volatile bool _isVictory; |
| await this.ForEachPlayerAsync(player => | ||
| player.InvokeViewPlugInAsync<IShowMessagePlugIn>(p => | ||
| p.ShowMessageAsync("Maya rises from the depths of the Refinery Tower!", MessageType.GoldenCenter)).AsTask()).ConfigureAwait(false); |
There was a problem hiding this comment.
Please move messages like this to the PlayerMessage resources. Then please also use the Player.ShowLocalizedBlueMessageAsync and Player.ShowLocalizedGoldenMessageAsync for this and all other player messages.
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| protected override void OnMonsterDied(object? sender, DeathInformation e) |
There was a problem hiding this comment.
- Make the method async void and wrap it with try-catch.
- Then you can remove all these Task.Runs, and await the calls, e.g. to SendMonsterUserCountAsync.
- Make sure that SendMonsterUserCountAsync doesn't throw exceptions.
There was a problem hiding this comment.
Instead of constructing every packet manually here, define them in the ServerToClientPackets.xml. Otherwise, they're not documented and not tested.
Then you can easily use the automatically generated structs to send the packets.
Addresses Gemini Code Assist review feedback (high severity): - OpenElphisBarrierAsync: wrapped entire body in try-catch so that failures while opening the Elphis barrier are logged instead of silently propagating as unobserved task exceptions. - SendMonsterUserCountAsync (8 call sites): introduced FireAndForgetMonsterCountAsync helper that wraps the broadcast in a try-catch, preventing a disconnecting player from causing an unhandled exception in the monster-death callback. - RunMayaWideAreaAttacksAsync: wrapped ForEachPlayerAsync broadcast in try-catch; the loop continues on errors rather than crashing the visual-effects task. - Diagnostic ForEachPlayerAsync in out-of-phase default case: added try-catch to the fire-and-forget lambda. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- IKanturuEventViewPlugIn: Replace static classes with enums (KanturuState, KanturuMayaDetailState, KanturuNightmareDetailState, KanturuTowerDetailState, KanturuEnterResult, KanturuBattleResult, KanturuMayaAttackType). Rename Send... methods to Show... and use TimeSpan/int instead of int/byte params. - ServerToClientPackets.xml: Define all 7 Kanturu packets (0xD1/00-07) so they are documented, tested, and auto-generated. - KanturuEventViewPlugIn: Use auto-generated connection.SendKanturu*Async() extension methods with mapping functions between enum and protocol values. - KanturuContext: Make OnMonsterDied async void with try-catch (VSTHRD100 pattern matching ChaosCastleContext); remove all Task.Run fire-and-forget wrappers; await ShowMonsterUserCountAsync directly. Add volatile field XML doc comments explaining threading rationale. Replace all SendKanturu* calls with typed ShowMaya/Nightmare/TowerStateToAllAsync helpers. Replace Send*Async helpers with Show*Async helpers using int/TimeSpan params. Use ShowGoldenMessageAsync + nameof(PlayerMessage.Kanturu*) for all localized messages. - KanturuGatewayPlugIn: Update to use KanturuState enum and new ShowStateInfoAsync(KanturuState, byte, bool, int, TimeSpan) signature. - PlayerMessage: Add 20 Kanturu message keys (Maya phases, Nightmare phases, Tower of Refinement) plus KanturuDefeat for the game-end defeat message. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
Implements the full server-side logic for the Kanturu Refinery Tower mini-game event (maps 38-40), following the same plugin patterns already used by BloodCastle and ChaosCastle.
The upstream already contained the three map definitions (
KanturuEvent.cs,KanturuRelics.cs,KanturuRuins.cs) and the two client-side packet docs (C1-D1-00,C1-D1-01). This PR adds the missing logic layer that wires those maps into a playable event.New files
KanturuContextIKanturuEventViewPlugInKanturuEventViewPlugInKanturuGatewayPlugInKanturuStartPlugInKanturuStartConfigurationKanturuGameServerStateKanturuEnterRequestHandlerPlugInC1-D1-01entry packetKanturuInfoRequestHandlerPlugInC1-D1-00info packetKanturuGroupHandlerPlugInKanturuInitializerMiniGameDefinitionand spawn waves at startupModified files
MiniGameType.cs— addedKanturuenum valueGameConfigurationInitializer.cs— registersKanturuInitializerKanturuEvent.cs— added full wave monster spawn definitions (Blade Hunters, Dreadfear, Twin Tale, Genocider, Persona, Maya hands, Nightmare)Event flow
Players who die respawn at Kanturu Relics (Map 38) via the map's
SafezoneMapsetting.Test plan
dotnet build src/compiles without errorsdotnet test tests/MUnique.OpenMU.Persistence.Initialization.Tests/passes🤖 Generated with Claude Code