Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
789f282
feat(crywolf): add the packets of the crywolf event
apraxico Sep 28, 2026
da29dec
feat(crywolf): add the game logic of the crywolf event
apraxico Sep 28, 2026
ec2263d
feat(crywolf): add the data of the crywolf event
apraxico Sep 28, 2026
0fb7b59
test(crywolf): cover the definition, the altars, the terrain, the pac…
apraxico Sep 28, 2026
fdc07a3
test(crywolf): add the generated tests of the crywolf packets
apraxico Sep 28, 2026
2669b55
feat(crywolf): add a chat command for game masters to let the event p…
apraxico Sep 28, 2026
791d187
feat(crywolf): add the warp entry of the crywolf fortress
apraxico Sep 28, 2026
7ae5884
feat(crywolf): let Balgass appear by the chat command during the battle
apraxico Sep 28, 2026
663705d
fix(crywolf): show the heroes at the end of the battle
apraxico Sep 28, 2026
7c7555f
fix(crywolf): address the findings of the review
apraxico Sep 28, 2026
1558d2e
Save the crywolf occupation state in its own table
apraxico Sep 28, 2026
a58c0b3
Run the crywolf event on one game server and add its benefits and pen…
apraxico Sep 28, 2026
cbf4777
Log which crywolf benefits and penalties apply
apraxico Sep 28, 2026
92158c5
Give the crywolf chaos mix bonus to the blood castle ticket, like the…
apraxico Sep 29, 2026
be1c385
Add the skills of Balgass and his army
apraxico Sep 29, 2026
37062fb
Warn when the stun effect for the crywolf monster skills is missing
apraxico Sep 29, 2026
0e16401
Share the push of Selupan with the monsters of the crywolf event
apraxico Sep 29, 2026
8e39734
Keep the crywolf state of all game servers up to date
apraxico Sep 29, 2026
314b052
Address the static code analysis findings of the crywolf event
apraxico Sep 29, 2026
7cd2928
Address the review of the crywolf event
apraxico Sep 29, 2026
d3d3845
Merge remote-tracking branch 'origin/master' into feature/crywolf-event
apraxico Sep 29, 2026
372bcf6
fix(crywolf): renumber the update to 122
apraxico Sep 29, 2026
26310b4
refactor(crywolf): configure the jewels of the penalty
apraxico Sep 29, 2026
f3f2883
docs(crywolf): mark the crywolf packet handlers as done
apraxico Sep 29, 2026
e4bc6f4
Merge remote-tracking branch 'origin/master' into feature/crywolf-event
apraxico Sep 29, 2026
0026163
Merge remote-tracking branch 'origin/master' into feature/crywolf-event
apraxico Oct 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions docs/Packets/C1-BD-00-CrywolfInfo_by-server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# C1 BD 00 - CrywolfInfo (by server)

## Is sent when

The state of the crywolf event changed, or the player entered the crywolf map. It should only be sent to players on the crywolf map, because the client loads the terrain of the occupation state for its current map.

## Causes the following actions on the client side

The client shows the event window in the states Ready, Start and End, plays the intro in the state Notify2, shows the result in the state End, and loads the terrain and light of the occupation state.

## Structure

| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 6 | Packet header - length of the packet |
| 2 | 1 | Byte | 0xBD | Packet header - packet type identifier |
| 3 | 1 | Byte | 0x00 | Packet header - sub packet type identifier |
| 4 | 1 | OccupationState | | Occupation; The occupation state. It must be Peace or Occupied in the state End, otherwise the client shows no result. |
| 5 | 1 | CrywolfState | | State; The state of the event. |

### OccupationState Enum

The occupation state of the crywolf fortress.

| Value | Name | Description |
|-------|------|-------------|
| 0 | Peace | The fortress has been defended. The benefits apply. |
| 1 | Occupied | The fortress has been occupied by Balgass. The penalties apply. |
| 2 | War | The fortress is under attack. |

### CrywolfState Enum

The state of the crywolf event.

| Value | Name | Description |
|-------|------|-------------|
| 0 | None | The event is not running. |
| 1 | Notify1 | The first notification of the upcoming event. |
| 2 | Notify2 | The second notification. The client plays the intro of the event. |
| 3 | Ready | The altars can be contracted and the monsters appeared. |
| 4 | Start | The monsters attack. |
| 5 | End | The event ended. The client shows the result. |
| 6 | EndCycle | The event is finished. |
24 changes: 24 additions & 0 deletions docs/Packets/C1-BD-02-CrywolfStatueAndAltarInfo_by-server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# C1 BD 02 - CrywolfStatueAndAltarInfo (by server)

## Is sent when

Every two seconds while the altars can be contracted and during the battle.

## Causes the following actions on the client side

The client shows the shield of the statue and the states of the altars.

## Structure

| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 16 | Packet header - length of the packet |
| 2 | 1 | Byte | 0xBD | Packet header - packet type identifier |
| 3 | 1 | Byte | 0x02 | Packet header - sub packet type identifier |
| 4 | 4 | IntegerLittleEndian | | StatueHealthPercent; The shield of the statue in percent, from 0 to 100. |
| 8 | 1 | Byte | | Altar1State; The state of the first altar (205). The high nibble is the altar state (1 = contracted), the low nibble the number of remaining contracts. |
| 9 | 1 | Byte | | Altar2State; The state of the second altar (206), like Altar1State. |
| 10 | 1 | Byte | | Altar3State; The state of the third altar (207), like Altar1State. |
| 11 | 1 | Byte | | Altar4State; The state of the fourth altar (208), like Altar1State. |
| 12 | 1 | Byte | | Altar5State; The state of the fifth altar (209), like Altar1State. |
21 changes: 21 additions & 0 deletions docs/Packets/C1-BD-03-CrywolfContractResult_by-server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# C1 BD 03 - CrywolfContractResult (by server)

## Is sent when

The player requested to contract an altar.

## Causes the following actions on the client side

On success, the client shows that the player is a guardian of the altar, and lets the character pray. Otherwise, it shows why the contract failed.

## Structure

| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 8 | Packet header - length of the packet |
| 2 | 1 | Byte | 0xBD | Packet header - packet type identifier |
| 3 | 1 | Byte | 0x03 | Packet header - sub packet type identifier |
| 4 | 1 | Byte | | Result; The result of the contract: 1 when it was accepted, 0 otherwise. The client compares the whole byte, so it's no boolean field. |
| 5 | 1 | Byte | | AltarState; The new state of the altar, like in the CrywolfStatueAndAltarInfo. |
| 6 | 2 | ShortBigEndian | | AltarKey; The key of the altar. The client uses the key minus 317 as index into its five altar states without checking the range, so it must be 317 plus the index of the altar (0 to 4). |
20 changes: 20 additions & 0 deletions docs/Packets/C1-BD-04-CrywolfLeftTime_by-server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# C1 BD 04 - CrywolfLeftTime (by server)

## Is sent when

Every 20 seconds during the battle.

## Causes the following actions on the client side

The client shows the remaining minutes and counts down the seconds by itself. It expects this packet every 20 seconds: when the minute is the same as before, it assumes that 40 or 20 seconds of the minute are left.

## Structure

| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 6 | Packet header - length of the packet |
| 2 | 1 | Byte | 0xBD | Packet header - packet type identifier |
| 3 | 1 | Byte | 0x04 | Packet header - sub packet type identifier |
| 4 | 1 | Byte | | Hours; The remaining hours. The client doesn't show them. |
| 5 | 1 | Byte | | Minutes; The remaining minutes. |
20 changes: 20 additions & 0 deletions docs/Packets/C1-BD-05-CrywolfBossMonsterInfo_by-server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# C1 BD 05 - CrywolfBossMonsterInfo (by server)

## Is sent when

Every five seconds during the battle.

## Causes the following actions on the client side

The client shows the health of Balgass and the number of Dark Elves.

## Structure

| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 12 | Packet header - length of the packet |
| 2 | 1 | Byte | 0xBD | Packet header - packet type identifier |
| 3 | 1 | Byte | 0x05 | Packet header - sub packet type identifier |
| 4 | 4 | IntegerLittleEndian | | BalgassHealthPercent; The health of Balgass in percent, or -1, when Balgass is not alive. |
| 8 | 1 | Byte | | DarkElfCount; The number of alive Dark Elves. The client shows it as count of 12. |
32 changes: 32 additions & 0 deletions docs/Packets/C1-BD-07-CrywolfPersonalRank_by-server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# C1 BD 07 - CrywolfPersonalRank (by server)

## Is sent when

The crywolf event ended, after the CrywolfInfo with the state End.

## Causes the following actions on the client side

The client shows the rank and the experience of the player in the result.

## Structure

| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 12 | Packet header - length of the packet |
| 2 | 1 | Byte | 0xBD | Packet header - packet type identifier |
| 3 | 1 | Byte | 0x07 | Packet header - sub packet type identifier |
| 4 | 1 | CrywolfRank | | Rank; The rank of the player. |
| 8 | 4 | IntegerLittleEndian | | Experience; The rewarded experience. The field is aligned to 4 bytes, because the client structure is not packed. |

### CrywolfRank Enum

The rank of a player in the crywolf event.

| Value | Name | Description |
|-------|------|-------------|
| 0 | D | The rank D. |
| 1 | C | The rank C. |
| 2 | B | The rank B. |
| 3 | A | The rank A. |
| 4 | S | The rank S. |
33 changes: 33 additions & 0 deletions docs/Packets/C1-BD-08-CrywolfHeroList_by-server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# C1 BD 08 - CrywolfHeroList (by server)

## Is sent when

The crywolf event ended, after the CrywolfInfo with the state End.

## Causes the following actions on the client side

The client shows the heroes with the highest scores in the result.

## Structure

| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | | Packet header - length of the packet |
| 2 | 1 | Byte | 0xBD | Packet header - packet type identifier |
| 3 | 1 | Byte | 0x08 | Packet header - sub packet type identifier |
| 4 | 1 | Byte | | HeroCount; The number of heroes, at most 5. |
| 5 | Hero.Length * HeroCount | Array of Hero | | Heroes; The heroes. |

### Hero Structure

Contains a hero of the crywolf event.

Length: 20 Bytes

| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | | Rank; The place of the hero, from 0 (first) to 4. The client uses it as array index, so it must not be greater than 4. |
| 1 | 10 | String | | Name; The name of the hero. |
| 12 | 4 | IntegerLittleEndian | | Score; The score of the hero. The field is aligned to 4 bytes, because the client structure is not packed. |
| 16 | 1 | Byte | | CharacterClass; The number of the character class. |
19 changes: 19 additions & 0 deletions docs/Packets/C1-BD-09-CrywolfBenefitPlusChaosRate_by-server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# C1 BD 09 - CrywolfBenefitPlusChaosRate (by server)

## Is sent when

The player opened a crafting dialog and requested the chaos rate benefit of the crywolf event.

## Causes the following actions on the client side

The client shows the additional success rate of the crafting.

## Structure

| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 5 | Packet header - length of the packet |
| 2 | 1 | Byte | 0xBD | Packet header - packet type identifier |
| 3 | 1 | Byte | 0x09 | Packet header - sub packet type identifier |
| 4 | 1 | Byte | | PlusChaosRate; The additional success rate in percent. |
23 changes: 23 additions & 0 deletions docs/Packets/C1-BD-0C-CrywolfRegionMonsterAttack_by-server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# C1 BD 0C - CrywolfRegionMonsterAttack (by server)

## Is sent when

A ballista of the crywolf event attacks an area.

## Causes the following actions on the client side

The client shows an arrow which hits the target point. The direction of the arrow depends on the x coordinate of the ballista.

## Structure

| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 10 | Packet header - length of the packet |
| 2 | 1 | Byte | 0xBD | Packet header - packet type identifier |
| 3 | 1 | Byte | 0x0C | Packet header - sub packet type identifier |
| 4 | 2 | ShortBigEndian | | MonsterNumber; The number of the monster. The client ignores it. |
| 6 | 1 | Byte | | SourceX; The x coordinate of the ballista. |
| 7 | 1 | Byte | | SourceY; The y coordinate of the ballista. The client ignores it. |
| 8 | 1 | Byte | | TargetX; The x coordinate of the target point. |
| 9 | 1 | Byte | | TargetY; The y coordinate of the target point. |
9 changes: 9 additions & 0 deletions docs/Packets/ServerToClient.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,15 @@
* [C1 B9 05 - CastleSiegeHuntingZoneEnterResponse (by server)](C1-B9-05-CastleSiegeHuntingZoneEnterResponse_by-server.md)
* [C1 BA - SkillStageUpdate (by server)](C1-BA-SkillStageUpdate_by-server.md)
* [C2 BB - CastleSiegeMiniMapNpcPositions (by server)](C2-BB-CastleSiegeMiniMapNpcPositions_by-server.md)
* [C1 BD 00 - CrywolfInfo (by server)](C1-BD-00-CrywolfInfo_by-server.md)
* [C1 BD 02 - CrywolfStatueAndAltarInfo (by server)](C1-BD-02-CrywolfStatueAndAltarInfo_by-server.md)
* [C1 BD 03 - CrywolfContractResult (by server)](C1-BD-03-CrywolfContractResult_by-server.md)
* [C1 BD 04 - CrywolfLeftTime (by server)](C1-BD-04-CrywolfLeftTime_by-server.md)
* [C1 BD 05 - CrywolfBossMonsterInfo (by server)](C1-BD-05-CrywolfBossMonsterInfo_by-server.md)
* [C1 BD 07 - CrywolfPersonalRank (by server)](C1-BD-07-CrywolfPersonalRank_by-server.md)
* [C1 BD 08 - CrywolfHeroList (by server)](C1-BD-08-CrywolfHeroList_by-server.md)
* [C1 BD 09 - CrywolfBenefitPlusChaosRate (by server)](C1-BD-09-CrywolfBenefitPlusChaosRate_by-server.md)
* [C1 BD 0C - CrywolfRegionMonsterAttack (by server)](C1-BD-0C-CrywolfRegionMonsterAttack_by-server.md)
* [C1 BF 00 - IllusionTempleEnterResult (by server)](C1-BF-00-IllusionTempleEnterResult_by-server.md)
* [C1 BF 01 - IllusionTempleState (by server)](C1-BF-01-IllusionTempleState_by-server.md)
* [C1 BF 02 - IllusionTempleSkillUsageResult (by server)](C1-BF-02-IllusionTempleSkillUsageResult_by-server.md)
Expand Down
2 changes: 1 addition & 1 deletion docs/Progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ complexity and effort). Complexity 0 means we wont implement it.
| *LoggedIn* | 0xB8 | 100% | 1 | |
| RequestGuildMarkCastleOwner | 0xB9 | 0% | 1 | |
| *JewelMix* | 0xBC | 100% | 4 | |
| CrywolfGroup | 0xBD | 0% | 10 | |
| *CrywolfGroup* | 0xBD | 100% | 10 | |
| GuildAssignStatus | 0xBE | 0% | 1 | |
| FriendListRequest | 0xC0 | 0% | 0 | Not needed, friend list is sent automatically |
| *FriendAdd* | 0xC1 | 100% | 2 | |
Expand Down
7 changes: 7 additions & 0 deletions src/DataModel/Configuration/GameMapDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ public partial class GameMapDefinition
/// </remarks>
public byte[]? TerrainData { get; set; }

/// <summary>
/// Gets or sets the variants of the terrain, which replace the <see cref="TerrainData"/> in certain states
/// of the map, e.g. during an event.
/// </summary>
[MemberOfAggregate]
public virtual ICollection<GameMapTerrainVariant> TerrainVariants { get; protected set; } = null!;

/// <summary>
/// Gets or sets the defined monster spawn areas.
/// </summary>
Expand Down
39 changes: 39 additions & 0 deletions src/DataModel/Configuration/GameMapTerrainVariant.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// <copyright file="GameMapTerrainVariant.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

namespace MUnique.OpenMU.DataModel.Configuration;

using MUnique.OpenMU.Annotations;

/// <summary>
/// Defines an alternative terrain of a map, which replaces its <see cref="GameMapDefinition.TerrainData"/>
/// in a certain state, e.g. while the crywolf fortress is occupied.
/// </summary>
[Cloneable]
public partial class GameMapTerrainVariant
{
/// <summary>
/// Gets or sets the number, which identifies the variant on its map, e.g. the state in which it applies.
/// </summary>
public short Number { get; set; }

/// <summary>
/// Gets or sets the description.
/// </summary>
public string Description { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the terrain data.
/// </summary>
/// <remarks>
/// Content of the *.att file in the original server.
/// </remarks>
public byte[]? TerrainData { get; set; }

/// <inheritdoc />
public override string ToString()
{
return $"{this.Number}: {this.Description}";
}
}
48 changes: 48 additions & 0 deletions src/DataModel/Entities/CrywolfData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// <copyright file="CrywolfData.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

namespace MUnique.OpenMU.DataModel.Entities;

/// <summary>
/// Persistent state of the crywolf event, stored as a single row, like the original game does it.
/// It keeps the result of the last event until the next one.
/// </summary>
/// <remarks>
/// It's written by the game server which runs the event, and read by the other game servers,
/// so that the occupation state and its benefits and penalties apply on all of them.
/// </remarks>
[AggregateRoot]
public class CrywolfData
{
/// <summary>
/// Gets or sets the unique identifier of this record.
/// </summary>
public Guid Id { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the crywolf fortress is occupied by Balgass,
/// because the last event failed. Otherwise, it's in peace.
/// </summary>
public bool IsOccupied { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the war is running, from the preparation of the fortress
/// until the end of the battle. Meanwhile, neither the benefits nor the penalties apply.
/// </summary>
public bool IsWarRunning { get; set; }

/// <summary>
/// Gets or sets the time (UTC) when the war started, or <see langword="null"/> if it isn't running.
/// </summary>
/// <remarks>
/// The other game servers consider the war as ended after the longest possible duration of the event,
/// in case the game server which runs it stopped during the war.
/// </remarks>
public DateTime? WarStart { get; set; }

/// <summary>
/// Gets or sets the time (UTC) when the last battle ended, or <see langword="null"/> if there was no battle yet.
/// </summary>
public DateTime? LastBattleEnd { get; set; }
}
Loading