Add the Doppelganger (Double Goer) event - #973
Conversation
Defines the doppelganger event packets as expected by the season 6 client: enter result, monster position, state, ice walker state, play info (timer and party positions), result and monster goal count. The client structures are not packed, so the reward experience of the result packet is aligned at offset 8 and the packet is 12 bytes long. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds IDoppelgangerEventViewPlugIn with its default implementation, which sends the doppelganger packets (0xBF 0x0F-0x14). The play info packet has a variable length, so it's written directly like the chain lightning packet. The enter result of the doppelganger is now handled by ShowMiniGameEnterResultViewPlugIn, instead of throwing an ArgumentOutOfRangeException. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DoppelgangerContext runs the event on top of the mini game lifecycle: - shows the event frame when the game starts and sends the remaining time and the path positions of the players every second, - counts the monsters which reached the magic circle and ends the event when too many of them reached it, - fails the event for a player which dies or leaves the map, - sends the result to the remaining players when the game ends. The path of each map consists of 23 areas, taken from the original season 6 server data. The countdowns of the mini game (0x92) got the values of the doppelganger, and the MiniGameManager creates the new context. The monster waves and the movement along the path follow in a later step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Lugard opens the doppelganger entrance window. The byte after the window (minutes until the entrance opens) is sent as 0, instead of leaving it uninitialized. - The DoppelgangerEnterRequest (0xBF 0x0E) is handled. The first player of a party enters a random event map, the other members follow into the same instance. The Mirror of Dimensions (14,111) is consumed as ticket. - Four mini game definitions, one per event map, with one instance per party. - Players who die inside the event maps respawn at Elvenland. - An update plug-in adds all of this to existing season 6 databases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Adds the monsters 529-539 with the base values of the original season 6 server data. The class clones use the skills of their class. - Herds spawn every 3 seconds at the start of the path. Their size grows with the game time and the number of players. Additional butchers and dark lord clones appear after 1, 4 and 7 minutes. - DoppelgangerMonsterIntelligence lets the monsters walk from area to area towards the magic circle. They only attack players in their attack range, so they don't leave the path, and they keep walking when no player observes them. When a monster reaches the magic circle, it's removed and counted as goal. - The ice walkers appear at a random position of the path after almost six minutes. They disappear when they aren't killed within a minute. The client shows their position and the countdown. - The client shows the position of the most advanced monster. - The update plug-in also adds the monsters to existing databases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The entrance gates of the event maps contained non-walkable coordinates, up to 39% of the area on the fourth map. The position within a gate is chosen randomly without checking the terrain, and a player on a non-walkable coordinate gets warped to the safezone map. So it happened that a player got warped out of the event right after entering, which already consumed the ticket. The gates are reduced to their walkable part, and the update plug-in adjusts them in existing databases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- When a butcher dies, three interim reward chests appear around it. Only one of them can be opened by attacking it, the others disappear then. Unopened chests disappear after a minute. - An opened interim chest contains either larvae (one for each player which started the event) or items. The drop generator of the event suppresses the item drop, when larvae came out of it. - When the players successfully defended the magic circle, the final reward chest appears next to a remaining player. - The chests (541, 542) are now destructibles with their own drop groups: jewels, and with a small chance a Loch's Feather or Crest of Monarch, similar to the original item bags. The update plug-in configures them in existing databases. - Monsters which don't walk along the path can't reach the magic circle, so larvae next to it aren't counted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client handles the reward chests (541, 542) as NPCs, so clicking on them sends a talk request instead of an attack, and the server answered that talking to them is not implemented. The DoppelgangerRewardChestPlugIn now opens a chest when a player talks to it. The client shows the opening as the death of the chest, then the items drop or the larvae appear, and the chest is removed. Because the content is decided when opening the chest, the special drop generator isn't required anymore. The chests got a high health, so that attacks don't destroy them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…yers The monsters get stronger depending on the highest player level (including the master level) and the number of players which started the game, like in the original event. The multipliers for the level, health, damage and defense are taken from the original season 6 server data, in ranges of ten player levels up to 800. When the players fail to kill the ice walkers in time, the following herds and additional monsters get twice the health, damage and defense. The multipliers are added to the attributes of each monster instance before it's initialized, so they don't affect the monster definitions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Like the original server, the players get a notice when the ice walkers appear, when all of them were killed and when they escaped, so that the players know why the following monsters got stronger. The messages are localizable resources of the PlayerMessage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…irrors - The event accepts a Mirror of Dimensions (14,111) and a Doppelganger Free Ticket (13,125). MiniGameDefinition supports only one ticket item, so the definitions don't define one anymore, and the EnterDoppelgangerAction checks and consumes the ticket after a successful entrance. A free ticket loses one durability per use. - The Sign of Dimensions (14,110) drops from monsters of level 32 and above with a chance of 0.1%. It stacks up to five pieces, which transform into a Mirror of Dimensions, like the Symbol of Kundun transforms into a Lost Map. - The update plug-in adds the new items, the drop group to all maps and removes the ticket item from the definitions of existing databases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The plug-ins of the reward chests and the sign of dimensions use localized names and descriptions of the PlugInResources, like their siblings. - The separator lines of the new PlayerMessage properties match the format of the resource code generator. - An opened chest is removed after awaiting its opening animation, instead of a fire-and-forget task without cancellation. - Removed a redundant assignment of the monster to its intelligence, which the constructor of the monster already does. - The shown goal count doesn't exceed its maximum, when several monsters reach the magic circle at the same time. - The default multipliers of the monsters end at a player level of 600. The entries above repeated the ones of 410 to 600, so monsters of higher level players got weaker. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The run of the event is described by the DoppelgangerEventDefinition, which is now the configuration of the DoppelgangerFeaturePlugIn, like the event definition of Kanturu. It can therefore be adapted in the admin panel, e.g. the monsters of the herds and their multipliers, the paths and the chests. - The monsters are referenced by their definition instead of their number. - The path areas have settable properties, so they can be edited. - A new database gets the default configuration; for existing databases the context falls back to the default definition, until an administrator configures it. - The default multipliers are simple values which grow linearly with the player level, meant to be adjusted by the server administrator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The packets of the event view, including the clamped path positions and the aligned reward experience, the enter result, the countdowns, the entrance window of Lugard and the handling of truncated enter requests. - The path progress of the monster intelligence: the position only increases with the next areas, a walking monster reaches the magic circle once, and a stationary monster next to it doesn't. - The ticket search: the mirror is preferred, a given slot is used and used up tickets are ignored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The comments describe the values as defaults which can be adjusted, and the description of the reward experience of the result packet starts with what it is, so the generated documentation reads well. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The method which opens the entrance window of the doppelganger event is placed after the static members (SA1204). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The timer callback of the monster intelligence awaits a Task instead of an async void method (S3168). - The chain of conditions when opening a chest ends with an else (S126). - The default monster multipliers are part of the event definition itself, instead of a partial class with only static members (S1118). - The center of a path area is calculated by the intelligence, which is its only user, instead of a method which looks like a property (S4049). - The maximum path position is a static property of the view interface instead of a constant (S2339). - The magic circle callback is invoked explicitly (S4487). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The parameter of the timer callback was named like a discard, so the task was assigned to the parameter instead of being discarded (S1226, S1854). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The Codacy findings are addressed, except S1128 in The Azure build fails within 0 seconds, like on master since the merge of #910 and on #970, so it seems to be an issue of the build infrastructure. |
sven-n
left a comment
There was a problem hiding this comment.
Reviewed and verified against current master (incl. #945, #971, #972): merges cleanly, builds with -p:ci=true, and all tests pass (OpenMU.Tests 1020, Persistence.Initialization.Tests 25, Network.Packets.Tests 602, PlugIns.Tests 41).
The context, intelligence, entrance, and data update all look solid. The concurrency handling (chest groups, goal count, per-player results) is careful, and the update plugin is idempotent.
One minor, non-blocking note: EnterDoppelgangerAction.TryEnterAsync checks player.CurrentMiniGame is DoppelgangerContext after the enter attempt. If a (modified) client sends the enter request while already inside an event, the enter fails but a ticket is still consumed. That only hurts the sender, but it could be tightened by checking before the attempt.
The Azure build failure is the known infrastructure issue (it also fails in 0s on other PRs).
Generated by Claude Code
Implements the Doppelganger (Double Goer) event of season 6. Part of #23.
Depends on #972: the monsters walk along a path of areas, and without the fix of the
ScopedGridNetworkthey get stuck next to the magic circle.How the event works
A party enters through the NPC Lugard (Elvenland/Devias) with a Mirror of Dimensions or a Doppelganger Free Ticket. It plays in its own instance of one of the maps 65–68. Herds of monsters spawn at the start of a path and walk towards a magic circle; when three of them reach it, the defense fails. Ice walkers appear on the path and have to be killed within a minute, otherwise the following monsters get stronger. Killed butchers leave reward chests, of which only one can be opened. When the time is over, the remaining players succeed and a final reward chest appears.
What it adds
ServerToClientPackets.xml(+ generated code, docs, tests)MiniGames/Doppelganger/DoppelgangerContext.csMiniGames/Doppelganger/DoppelgangerMonsterIntelligence.csBasicMonsterIntelligence, it keeps running without observers.MiniGames/Doppelganger/DoppelgangerEventDefinition*.cs,DoppelgangerPath*.cs,DoppelgangerHerdSize.cs,DoppelgangerMonsterSpawn.cs,DoppelgangerMonsterScaling.csMiniGames/Doppelganger/DoppelgangerFeaturePlugIn.csMiniGames/Doppelganger/IDoppelgangerEventViewPlugIn.cs,DoppelgangerState.cs,DoppelgangerResult.csRemoteView/MiniGames/DoppelgangerEventViewPlugIn.csPlayerActions/MiniGames/EnterDoppelgangerAction.cs,MessageHandler/MiniGames/DoppelgangerEnterRequestHandlerPlugIn.csPlugIns/DoppelgangerRewardChestPlugIn.csPlugIns/SignOfDimensionsStackedPlugIn.csEvents/DoppelgangerInitializer.cs,Events/DoppelgangerMonsters.cs,Updates/AddDoppelgangerDataUpdatePlugIn.cs(UpdateVersion115)Modified shared code
MiniGameManager.csDoppelgangerContext.ShowMiniGameEnterResultViewPlugIn.cs,Extensions.csArgumentOutOfRangeException.UpdateMiniGameStateViewViewPlugIn.csOpenNpcWindowPlugIn.csGates.csNpcInitialization.cs,EventTicketItems.cs,Doppelgaenger1-4.cs,DataInitializationBase.csDesign decisions
EnterMiniGameAction.EnterDoppelgangerActionchecks and consumes them.AggregateType.Multiplicate) before it's initialized, so the definitions aren't affected.Known limitations
Verification
dotnet build -p:ci=true: no new warnings in the touched files. The only errors are the existing ones ofWeb/AdminPanel(LogFiles.razor) with SDK 10.0.103, which also occur on master.MUnique.OpenMU.Tests1019 passed,Persistence.Initialization.Tests25 passed,Network.Packets.Tests602 passed,PlugIns.Tests41 passed.🤖 Generated with Claude Code