Skip to content

Add the Raklion event (Battle of Selupan) - #977

Merged
sven-n merged 16 commits into
MUnique:masterfrom
apraxico:feature/raklion-event
Sep 27, 2026
Merged

sven-n merged 16 commits into
MUnique:masterfrom
apraxico:feature/raklion-event

Conversation

@apraxico

@apraxico apraxico commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Part of #23.

Summary

The Raklion hatchery (map 58) now runs the Selupan event, like the original game:

  1. The 15 spider eggs appear in the hatchery. When only a few are left, the clients are notified.
  2. When all eggs are destroyed, Selupan appears after 5 seconds and falls from the sky.
  3. 5 minutes after Selupan appeared, the hatchery gate is closed. Only the players inside can fight.
  4. The battle ends when Selupan is killed, or when all players of the battle died or left.
  5. The hatchery opens again after 12 hours, and the spider eggs appear again.

All times and values are configurable in the admin panel (plugin "Raklion Event").

Selupan

SelupanIntelligence uses a skill every 1.5 seconds. Its pattern (1 to 7) depends on its remaining health
(80/60/50/40/20/10 %). With each pattern, it gets additional damage (berserk) and more skills:

Skill From pattern Effect
Poison, Ice Storm, Ice Strike 1 Hits the players around the target
Teleport 1 Vanishes and appears 3 to 6 fields away
Freeze 2 Hits and stuns the target
Heal 4 Heals 5 % of its current health
Summon 5 Summons the missing Coolutins
Invincibility 7 Doesn't receive damage for 10 seconds

The poison and ice skills hit the players in an area with their damage multipliers of the original server
(2.0, 2.2 and 2.3). Simplifications: there is no poison damage over time and the players aren't pushed back.

Hatchery access

Like the original game, only players which entered the hatchery while it was open may stay there. Others,
e.g. players entering while it's closed, are moved to the entrance of Raklion. After the battle, the remaining
players are moved out after a configurable time (PlayerRemovalDelay, 5 minutes by default). Unlike the
original game, game masters aren't exempt from these rules.

Changes

  • GameLogic: RaklionPlugIn (feature plugin with periodic task and custom configuration), RaklionContext
    (state machine), SelupanIntelligence, RaklionEventDefinition, IRaklionEventViewPlugIn.
  • Monster.TeleportAsync: new public method, so that a monster can be teleported (MoveType.Teleport)
    instead of moved instantly, which the client shows as sliding.
  • Packets: S2C D1 10 (state info), D1 11 (current state), D1 12 (state change), D1 13 (battle result)
    and 69 (monster skill animation, used for the skills of Selupan). The structures of the client aren't
    packed, so some fields are aligned (documented in the packet descriptions).
  • GameServer: RaklionEventViewPlugIn and the handler of the C2S D1 10 request, which was defined but unhandled.
  • Data: the monster spawns of the hatchery are spawned by the event, in waves: 1 = spider eggs,
    2 = Selupan, 3 = Coolutins summoned by Selupan. They were automatic spawns before.
  • Update plugin AddRaklionEventUpdatePlugIn (version 118, mandatory, idempotent): changes the automatic
    spawns of the hatchery of existing databases to the waves and adds the attack skills of Selupan (250 to 252) with
    their damage multipliers. Without it, the event doesn't start and logs one warning.
  • Messages: the texts of the original game, shown to all players of the server like the original. Additionally,
    the result of the battle and the time until the hatchery opens are shown in one new message, which is also written
    to the chat, so that they don't replace each other on the screen.

Drops of Selupan are left to the configuration of each server (drop groups), like the difficulty.

Notes

  • The behavior and the default values (times, egg count, pattern thresholds, spawn coordinates) follow the
    configuration of public server files (IGCN). No code was copied or converted.
  • The client only uses D1 10-D1 12; it ignores D1 13, which is sent for completeness.
  • MonsterSkillAnimation (0x69): the client searches the target by its id as it is, so the flag for a successful
    skill (highest bit of the target id) isn't set.

Tests

  • Packets (Network.Packets.Tests), view plugin serialization, pattern/berserk/skill selection of the definition,
    data of a new database, and the update plugin (applied twice).
  • Tested in game: eggs, appearance, the skills with their animations, summons, killing Selupan, the failed battle
    (all players died or left), the messages, the closed hatchery after the battle, the removal of the players and
    the reopening.

🤖 Generated with Claude Code

apraxico and others added 11 commits September 24, 2026 17:12
Defines the packets of the raklion event, as expected by the season 6
client: the state info, current state, state change (group 0xD1, 0x10 to
0x12) and battle result (0x13), and the monster skill animation (0x69),
which the client uses to show the skills of Selupan.

The client structures aren't packed, so the remaining seconds of the state
info are aligned at offset 8, and the ids of the monster skill animation at
offsets 6 and 8.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The hatchery of raklion (map 58) runs as a continuous event:
the spider eggs appear, when they're destroyed Selupan appears,
after some time the hatchery closes and only the players inside
fight against Selupan. After the battle, the hatchery opens again.

- RaklionPlugIn (feature + periodic task, configurable in the admin panel)
- RaklionContext with the state machine of the event
- SelupanIntelligence with the patterns by health, berserk and the skills
- view plugin for the 0xD1 0x10-0x13 and 0x69 packets, D1 10 request handler
- the monster spawns of the hatchery are spawned by the event (waves)
- update plugin which adapts the spawns of existing databases

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client searches the target of a monster skill by its id as it is,
so the flag for a successful skill made it ignore the packet. The
teleport of selupan moved it instantly, which the client shows as
sliding; it's now a teleport. The attacks are used more often than
the teleport.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Selupan vanishes with the effect of the teleport skill and appears
at a diagonal position 3 to 6 fields away. It teleports a bit more
often.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The messages are shown to all players of the game server, like the
original game does, and the opening of the hatchery gate is announced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Like the original game, only players which entered the hatchery while
it was open may stay there; others are moved to the entrance of
raklion. After the battle, the remaining players are moved out after a
configurable time. The hatchery opens again after 12 hours by default
(like IGCN), which is configurable as well.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- the event doesn't run without the monster spawns of the hatchery,
  instead of announcing Selupan without spawning it
- selupan isn't teleported when it died while vanishing
- dead summoned monsters are removed from the event
- remove unused usings and an obsolete todo
- test that the data update can be applied twice

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The result and the time until the hatchery opens are shown in one
message, so that they don't replace each other on the screen, and in
the chat, so that they can be read again. The time is shown in hours
and minutes. Players which try to enter the closed hatchery after the
battle are told when it opens again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- players in the hatchery while it's open are allowed to stay, even when
  the tick sees them before the event handler of the map registered them
- players which left the hatchery aren't counted as battle players
- monsters summoned while the battle ended are removed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@apraxico

Copy link
Copy Markdown
Contributor Author

The Codacy findings are addressed, except S1128 (unnecessary using) for System.Collections.Concurrent in RaklionContext.cs and RaklionPlugIn.cs, which looks like a false positive, like in #973: removing it breaks the build (CS0246, ConcurrentDictionary<,> isn't covered by a global using).

The Azure build fails within 0 seconds, like on master and on the previous PRs, so it's the known issue of the build infrastructure.

…ation

The output buffer is reused, so the padding byte, which the client
doesn't read, may contain data of a previous packet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ze-dom

ze-dom commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

Hey there!
It seems to me that the Selupan's skill multipliers are missing.

You can see in this source that:
| Skill | Multip | Description |
| 250 | 2.0 | Poison Attack |
| 251 | 2.2 | Ice Storm |
| 252 | 2.3 | Ice Strike |
| 253 | 2.5 | "First Skill" (this is probably when he falls from the sky) |

There is already a base skill multiplier of 2 in OpenMU.
If you add these, you can clear the comment on RaklionBoss.cs. If you don't, this stays for the record :).
If these are added, then the magic effects of the skills probably can go in the same job.

…iers

Selupan attacked with a null skill, and in CalculateDamageAsync the whole
multiplier block sits inside `if (skill != null)`. Its
Stats.SkillMultiplier of 2 was therefore never read, and Poison, Ice
Storm and Ice Strike all hit exactly the same.

The three attack skills are now defined with an attribute relationship to
Stats.SkillFinalMultiplier, and the intelligence passes their skill entry
to the damage calculation. InputOperator.Maximum keeps the multipliers
absolute, independent of the Stats.SkillMultiplier of the monster:

  Poison      2.0
  Ice Storm   2.2
  Ice Strike  2.3

The values are the ones of the original server, reported by ze-dom in the
review of MUnique#977.

The update plug-in adds the skills to existing databases, and the data
test covers both a new and an updated database.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@apraxico

Copy link
Copy Markdown
Contributor Author

Thanks @ze-dom, that was a useful pointer. I looked into it and the gap turned out to be bigger than the multipliers themselves.

SelupanIntelligence attacked with AttackByAsync(monster, null, false), and in AttackableExtensions.CalculateDamageAsync the whole multiplier block sits inside if (skill != null). So the existing Stats.SkillMultiplier of 2 on Selupan was never read: it was dead configuration rather than a baseline the per-skill values would refine, and Poison, Ice Storm and Ice Strike all hit exactly the same. The damageFactor parameter is in the same branch, so it was no shortcut either.

Fixed in 3959137: the three attack skills are now defined with an attribute relationship to Stats.SkillFinalMultiplier, and the intelligence passes their skill entry to the damage calculation.

Skill Number Multiplier
Selupan Poison 250 2.0
Selupan Ice Storm 251 2.2
Selupan Ice Strike 252 2.3

One detail worth mentioning: I used InputOperator.Maximum instead of the usual Multiply, following FallingSlash. With Multiply the relationship would resolve to SkillMultiplier * factor (2 * 2.2 = 4.4), which would force the values to be written relative to the monster's own multiplier. Maximum keeps them absolute, so the numbers from the original server go in as they are and stay correct if Selupan's SkillMultiplier ever changes.

The update plug-in adds the skills to existing databases as well, and RaklionDataTest now covers both a fresh and an updated database.

On skill 253: in this PR Fall is only the spawn animation, sent with a null target and dealing no damage, so a damage multiplier has nothing to apply to. Do you know whether Selupan actually hits nearby players when it lands in the original? If it does, that is a behaviour to add rather than a multiplier to set, so I would rather confirm before changing it.

I left the magic effects out of this commit on purpose. Adding the elemental modifier without the accompanying effects would only route the damage through the players' resistances, which is half the picture. Happy to do it as a follow-up.

The old TODO in RaklionBoss.cs is updated accordingly.

Resolve the conflict of the update versions: AddItemRuleFlags (116) and
AddDarkHorseCanFly (117) are on master, so AddRaklionEvent becomes 118.

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

@sven-n sven-n left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review

I read the whole change: RaklionContext (state machine, hatchery access, summons), SelupanIntelligence, RaklionPlugIn, the packets, the view plugin and request handler, the data and update plugin, and the tests. I also built it and ran the tests locally. The code looks good.

Verified

  • The full solution builds with no errors. MUnique.OpenMU.Tests (1034), Network.Packets.Tests (607) and Persistence.Initialization.Tests (27, 6 skipped) all pass.
  • The OnceAtWaveStart spawns don't respawn and aren't created at map creation, so the event fully controls eggs, Selupan and Coolutins.
  • A dead Selupan is removed and disposed by AttackableNpcBase (it doesn't respawn), so the intelligence timer is released. Removing a live Selupan through RemoveMonsterAsync disposes it too.
  • MonsterAttributeHolder.AddElement honours added elements over the static mappings. So invincibility (a ×0 element on DamageReceiveDecrement) and the berserk damage (on the physical base damage stats) take effect.
  • Skill multipliers: passing a SkillEntry is what makes CalculateDamageAsync apply a multiplier at all, so the fix in 3959137 is right. The update plugin skips skills that already exist, so running it twice is safe.

Blocking: merge conflict with master
#983 (AddItemRuleFlags = 116) and AddDarkHorseCanFly = 117 were merged, so AddRaklionEvent = 116 now conflicts in UpdateVersion.cs. The fix is AddRaklionEvent = 118. I resolved it with a merge commit on claude/pr-977-review-merge-2rzt3i (0ba2e66). The merged tree builds, and all tests pass (1047 / 607 / 32). I couldn't push to the fork's branch. @apraxico, please merge master (or cherry-pick that resolution) into feature/raklion-event. The PR description should then say version 118 instead of 116.

Non-blocking notes

  • InputOperator.Maximum makes the final multiplier max(SkillMultiplier, factor), not an absolute value. If Selupan's SkillMultiplier were raised above 2.3, all three skills would hit the same. That's fine for the current data, but the remark that the values "stay correct if SkillMultiplier changes" only holds while it stays at or below 2.0.
  • CI: Azure fails in 0s here and on master, so it's the known infrastructure issue. The remaining Codacy S1128 finding is a false positive, as noted above.

I'll merge once the conflict is resolved on the PR branch.


Generated by Claude Code

@apraxico

Copy link
Copy Markdown
Contributor Author

Thanks for the review and for preparing the resolution, @sven-n!

  • I fast-forwarded feature/raklion-event to your merge commit 0ba2e66, so AddRaklionEvent = 118 is exactly as you resolved it, and merged the current master (Add Discord notification workflow for merged pull requests #989) on top of it. It builds with -p:ci=true and all test suites pass (1047 / 607 / 32 / 41).
  • The PR description now says version 118, and mentions the Selupan skills which the update plugin adds.
  • You're right about InputOperator.Maximum: the final multiplier is max(SkillMultiplier, factor), so the values only stay as they are while Selupan's SkillMultiplier is at most 2.0. My remark was wrong there. I corrected the wording of the assertion in RaklionDataTest, which called it absolute.

sven-n commented Sep 27, 2026

Copy link
Copy Markdown
Member

Re-checked bd95f86f: all points are resolved.

  • The branch contains the conflict resolution (AddRaklionEvent = 118) and is up to date with master (Add Discord notification workflow for merged pull requests #989).
  • The InputOperator.Maximum wording is corrected in RaklionDataTest, and the PR description now says version 118.
  • The full solution builds with 0 errors, and all tests pass: MUnique.OpenMU.Tests 1047, Network.Packets.Tests 607, Persistence.Initialization.Tests 32 (6 skipped).
  • The Azure check again finished in 0 s (the known infrastructure issue). Codacy only has the S1128 false positive left.

Merging. Thanks @apraxico, and thanks @ze-dom for the multiplier pointer!


Generated by Claude Code

@sven-n
sven-n merged commit 5ea7d7a into MUnique:master Sep 27, 2026
0 of 2 checks passed
@ze-dom

ze-dom commented Sep 27, 2026

Copy link
Copy Markdown
Contributor

@apraxico yes, apparently when Selupan appears falling from the sky it can damage a target.
Video evidence: https://www.youtube.com/watch?v=hk_--fy1oZk

@apraxico

Copy link
Copy Markdown
Contributor Author

yes, apparently when Selupan appears falling from the sky it can damage a target.

Thanks for the video, @ze-dom, that settled it! The server sources agree: the fall hits every player closer than 4 fields to Selupan, with a multiplier of 2.5, and pushes them 4 fields away. Both are added in #994.

Your pointer also led to more: the push of the frost shock, and a fix for the skill multipliers from this PR, which weren't applied when a monster attacked. Those are in #994 as well.

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