M Core/NosSmooth.Game/Data/Raids/Raid.cs => Core/NosSmooth.Game/Data/Raids/Raid.cs +16 -1
@@ 4,9 4,24 @@
// Copyright (c) František Boháček. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+using NosSmooth.Game.Data.Entities;
+using NosSmooth.Game.Data.Social;
+using NosSmooth.Packets.Enums.Raids;
+
namespace NosSmooth.Game.Data.Raids;
/// <summary>
/// Represents nostale raid.
/// </summary>
-public record Raid();>
\ No newline at end of file
+public record Raid
+(
+ RaidType Type,
+ RaidState State,
+ short MinimumLevel,
+ short MaximumLevel,
+ GroupMember? Leader,
+ RaidProgress? Progress,
+ Monster? Boss,
+ IReadOnlyList<Monster>? Bosses,
+ IReadOnlyList<GroupMember>? Members
+);<
\ No newline at end of file
A Core/NosSmooth.Game/Data/Raids/RaidProgress.cs => Core/NosSmooth.Game/Data/Raids/RaidProgress.cs +17 -0
@@ 0,0 1,17 @@
+//
+// RaidProgress.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace NosSmooth.Game.Data.Raids;
+
+public record RaidProgress
+(
+ short MonsterLockerInitial,
+ short MonsterLockerCurrent,
+ short ButtonLockerInitial,
+ short ButtonLockerCurrent,
+ short CurrentLives,
+ short InitialLives
+);<
\ No newline at end of file
A Core/NosSmooth.Game/Data/Raids/RaidState.cs => Core/NosSmooth.Game/Data/Raids/RaidState.cs +52 -0
@@ 0,0 1,52 @@
+//
+// RaidState.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace NosSmooth.Game.Data.Raids;
+
+/// <summary>
+/// A state of a <see cref="Raid"/>.
+/// </summary>
+public enum RaidState
+{
+ /// <summary>
+ /// Waiting for a raid start.
+ /// </summary>
+ Waiting,
+
+ /// <summary>
+ /// The raid has started, the current room is not boss room.
+ /// </summary>
+ Started,
+
+ /// <summary>
+ /// The raid has started and the current room is boss room.
+ /// </summary>
+ BossFight,
+
+ /// <summary>
+ /// The raid has ended, successfully.
+ /// </summary>
+ EndedSuccessfully,
+
+ /// <summary>
+ /// The raid has ended unsuccessfully. The whole team has failed.
+ /// </summary>
+ TeamFailed,
+
+ /// <summary>
+ /// The raid has ended unsuccessfully for the character. He ran out of lifes.
+ /// </summary>
+ MemberFailed,
+
+ /// <summary>
+ /// The character has left the raid.
+ /// </summary>
+ /// <remarks>
+ /// The previous state is needed to be able to tell whether
+ /// the raid was already started or was in the <see cref="Waiting"/> state.
+ /// </remarks>
+ Left
+}<
\ No newline at end of file
M Core/NosSmooth.Game/Data/Social/GroupMember.cs => Core/NosSmooth.Game/Data/Social/GroupMember.cs +1 -1
@@ 43,7 43,7 @@ public record GroupMember(long PlayerId)
/// <summary>
/// Gets the morph vnum of the player.
/// </summary>
- public long MorphVNum { get; internal set; }
+ public int? MorphVNum { get; internal set; }
/// <summary>
/// Gets the hp of the member.
A Core/NosSmooth.Game/Events/Raids/RaidFinishedEvent.cs => Core/NosSmooth.Game/Events/Raids/RaidFinishedEvent.cs +11 -0
@@ 0,0 1,11 @@
+//
+// RaidFinishedEvent.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Game.Data.Raids;
+
+namespace NosSmooth.Game.Events.Raids;
+
+public record RaidFinishedEvent(Raid Raid) : IGameEvent;<
\ No newline at end of file
A Core/NosSmooth.Game/Events/Raids/RaidJoinedEvent.cs => Core/NosSmooth.Game/Events/Raids/RaidJoinedEvent.cs +11 -0
@@ 0,0 1,11 @@
+//
+// RaidJoinedEvent.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Game.Data.Raids;
+
+namespace NosSmooth.Game.Events.Raids;
+
+public record RaidJoinedEvent(Raid Raid) : IGameEvent;<
\ No newline at end of file
A Core/NosSmooth.Game/Events/Raids/RaidStateChangedEvent.cs => Core/NosSmooth.Game/Events/Raids/RaidStateChangedEvent.cs +16 -0
@@ 0,0 1,16 @@
+//
+// RaidStateChangedEvent.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Game.Data.Raids;
+
+namespace NosSmooth.Game.Events.Raids;
+
+public record RaidStateChangedEvent
+(
+ RaidState PreviousState,
+ RaidState CurrentState,
+ Raid Raid
+) : IGameEvent;<
\ No newline at end of file
M Core/NosSmooth.Game/Extensions/ServiceCollectionExtensions.cs => Core/NosSmooth.Game/Extensions/ServiceCollectionExtensions.cs +40 -6
@@ 18,9 18,11 @@ using NosSmooth.Game.PacketHandlers.Characters;
using NosSmooth.Game.PacketHandlers.Entities;
using NosSmooth.Game.PacketHandlers.Inventory;
using NosSmooth.Game.PacketHandlers.Map;
+using NosSmooth.Game.PacketHandlers.Raids;
using NosSmooth.Game.PacketHandlers.Relations;
using NosSmooth.Game.PacketHandlers.Skills;
using NosSmooth.Game.PacketHandlers.Specialists;
+using NosSmooth.Packets.Server.Raids;
namespace NosSmooth.Game.Extensions;
@@ 43,17 45,35 @@ public static class ServiceCollectionExtensions
serviceCollection.TryAddSingleton<Game>();
serviceCollection
+
+ // act4
.AddPacketResponder<FcResponder>()
+
+ // character
.AddPacketResponder<CharacterInitResponder>()
+ .AddPacketResponder<WalkResponder>()
+
+ // skills
.AddPacketResponder<PlayerSkillResponder>()
.AddPacketResponder<MatesSkillResponder>()
- .AddPacketResponder<WalkResponder>()
.AddPacketResponder<SkillUsedResponder>()
+
+ // friends
.AddPacketResponder<FriendInitResponder>()
+
+ // inventory
.AddPacketResponder<InventoryInitResponder>()
+
+ // groups
.AddPacketResponder<GroupInitResponder>()
+
+ // mates
.AddPacketResponder<MatesInitResponder>()
+
+ // skills
.AddPacketResponder<AoeSkillUsedResponder>()
+
+ // map
.AddPacketResponder<AtResponder>()
.AddPacketResponder<CMapResponder>()
.AddPacketResponder<DropResponder>()
@@ 61,11 81,22 @@ public static class ServiceCollectionExtensions
.AddPacketResponder<InResponder>()
.AddPacketResponder<MoveResponder>()
.AddPacketResponder<OutResponder>()
+
+ // hp, mp
.AddPacketResponder<StatPacketResponder>()
.AddPacketResponder<StPacketResponder>()
.AddPacketResponder<CondPacketResponder>()
+
+ // equip
.AddPacketResponder<SpResponder>()
- .AddPacketResponder<EqResponder>();
+ .AddPacketResponder<EqResponder>()
+
+ // raids
+ .AddPacketResponder<RaidBfResponder>()
+ .AddPacketResponder<RaidMbfResponder>()
+ .AddPacketResponder<RaidResponder>()
+ .AddPacketResponder<RbossResponder>()
+ .AddPacketResponder<RdlstResponder>();
serviceCollection
.AddTransient<DialogHandler>()
@@ 102,13 133,16 @@ public static class ServiceCollectionExtensions
/// <returns>The collection.</returns>
public static IServiceCollection AddGameResponder(this IServiceCollection serviceCollection, Type gameResponder)
{
- if (!gameResponder.GetInterfaces().Any(
+ if (!gameResponder.GetInterfaces().Any
+ (
i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IGameResponder<>)
))
{
- throw new ArgumentException(
+ throw new ArgumentException
+ (
$"{nameof(gameResponder)} should implement IGameResponder.",
- nameof(gameResponder));
+ nameof(gameResponder)
+ );
}
var handlerTypeInterfaces = gameResponder.GetInterfaces();
@@ 124,4 158,4 @@ public static class ServiceCollectionExtensions
return serviceCollection;
}
-}
+}<
\ No newline at end of file
M Core/NosSmooth.Game/Game.cs => Core/NosSmooth.Game/Game.cs +54 -0
@@ 359,6 359,60 @@ public class Game : IStatefulEntity
);
}
+ /// <summary>
+ /// Updates the current raid, if it is not null.
+ /// </summary>
+ /// <param name="update">The function for updating the raid.</param>
+ /// <param name="releaseSemaphore">Whether to release the semaphore used for changing the raid.</param>
+ /// <param name="ct">The cancellation token for cancelling the operation.</param>
+ /// <returns>The updated raid.</returns>
+ internal Task<Raid?> UpdateRaidAsync
+ (
+ Func<Raid, Raid?> update,
+ bool releaseSemaphore = true,
+ CancellationToken ct = default
+ )
+ {
+ return CreateOrUpdateAsync
+ (
+ GameSemaphoreType.Raid,
+ () => CurrentRaid,
+ m => CurrentRaid = m,
+ () => null,
+ update,
+ releaseSemaphore,
+ ct
+ );
+ }
+
+ /// <summary>
+ /// Creates the raid if it is null, or updates the current raid.
+ /// </summary>
+ /// <param name="create">The function for creating the raid.</param>
+ /// <param name="update">The function for updating the raid.</param>
+ /// <param name="releaseSemaphore">Whether to release the semaphore used for changing the raid.</param>
+ /// <param name="ct">The cancellation token for cancelling the operation.</param>
+ /// <returns>The updated raid.</returns>
+ internal Task<Raid?> CreateOrUpdateRaidAsync
+ (
+ Func<Raid?> create,
+ Func<Raid, Raid?> update,
+ bool releaseSemaphore = true,
+ CancellationToken ct = default
+ )
+ {
+ return CreateOrUpdateAsync
+ (
+ GameSemaphoreType.Raid,
+ () => CurrentRaid,
+ m => CurrentRaid = m,
+ create,
+ update,
+ releaseSemaphore,
+ ct
+ );
+ }
+
private async Task<T> CreateAsync<T>
(
GameSemaphoreType type,
A Core/NosSmooth.Game/PacketHandlers/Raids/RaidBfResponder.cs => Core/NosSmooth.Game/PacketHandlers/Raids/RaidBfResponder.cs +69 -0
@@ 0,0 1,69 @@
+//
+// RaidBfResponder.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Data;
+using NosSmooth.Core.Packets;
+using NosSmooth.Game.Data.Raids;
+using NosSmooth.Game.Events.Core;
+using NosSmooth.Game.Events.Raids;
+using NosSmooth.Packets.Enums.Raids;
+using NosSmooth.Packets.Server.Raids;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Raids;
+
+/// <summary>
+/// A responder to <see cref="RaidBfPacket"/>.
+/// </summary>
+public class RaidBfResponder : IPacketResponder<RaidBfPacket>
+{
+ private readonly Game _game;
+ private readonly EventDispatcher _eventDispatcher;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RaidBfResponder"/> class.
+ /// </summary>
+ /// <param name="game">The game.</param>
+ /// <param name="eventDispatcher">The event dispatcher.</param>
+ public RaidBfResponder(Game game, EventDispatcher eventDispatcher)
+ {
+ _game = game;
+ _eventDispatcher = eventDispatcher;
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(PacketEventArgs<RaidBfPacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+ Raid? previousRaid = null;
+ var currentRaid = await _game.UpdateRaidAsync
+ (
+ raid =>
+ {
+ previousRaid = raid;
+ return raid with
+ {
+ State = packet.WindowType switch
+ {
+ RaidBfPacketType.MissionStarted => RaidState.Started,
+ RaidBfPacketType.MissionCleared => RaidState.EndedSuccessfully,
+ _ => RaidState
+ .TeamFailed // TODO: figure out whether OutOfLives is sent for both individual member and whole team
+ }
+ };
+ },
+ ct: ct
+ );
+
+ if (previousRaid is not null && currentRaid is not null && previousRaid.State != currentRaid.State)
+ {
+ return await _eventDispatcher.DispatchEvent
+ (new RaidStateChangedEvent(previousRaid.State, currentRaid.State, currentRaid), ct);
+ }
+
+ return Result.FromSuccess();
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.Game/PacketHandlers/Raids/RaidMbfResponder.cs => Core/NosSmooth.Game/PacketHandlers/Raids/RaidMbfResponder.cs +53 -0
@@ 0,0 1,53 @@
+//
+// RaidMbfResponder.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Core.Packets;
+using NosSmooth.Game.Data.Raids;
+using NosSmooth.Packets.Server.Raids;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Raids;
+
+/// <summary>
+/// A responder to <see cref="RaidMbfPacket"/>.
+/// </summary>
+public class RaidMbfResponder : IPacketResponder<RaidMbfPacket>
+{
+ private readonly Game _game;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RaidMbfResponder"/> class.
+ /// </summary>
+ /// <param name="game">The game.</param>
+ public RaidMbfResponder(Game game)
+ {
+ _game = game;
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(PacketEventArgs<RaidMbfPacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+
+ await _game.UpdateRaidAsync
+ (
+ raid => raid with
+ {
+ Progress = new RaidProgress
+ (
+ packet.MonsterLockerInitial,
+ packet.MonsterLockerCurrent,
+ packet.ButtonLockerInitial,
+ packet.ButtonLockerCurrent,
+ packet.CurrentLives,
+ packet.InitialLives
+ )
+ },
+ ct: ct
+ );
+ return Result.FromSuccess();
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.Game/PacketHandlers/Raids/RaidResponder.cs => Core/NosSmooth.Game/PacketHandlers/Raids/RaidResponder.cs +114 -0
@@ 0,0 1,114 @@
+//
+// RaidResponder.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Net.Http.Headers;
+using NosSmooth.Core.Packets;
+using NosSmooth.Game.Data.Info;
+using NosSmooth.Game.Data.Raids;
+using NosSmooth.Game.Data.Social;
+using NosSmooth.Game.Events.Core;
+using NosSmooth.Game.Events.Raids;
+using NosSmooth.Packets.Enums.Raids;
+using NosSmooth.Packets.Server.Raids;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Raids;
+
+/// <summary>
+/// A responder to <see cref="RaidPacket"/>.
+/// </summary>
+public class RaidResponder : IPacketResponder<RaidPacket>
+{
+ private readonly Game _game;
+ private readonly EventDispatcher _eventDispatcher;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RaidResponder"/> class.
+ /// </summary>
+ /// <param name="game">The game.</param>
+ /// <param name="eventDispatcher">The event dispatcher.</param>
+ public RaidResponder(Game game, EventDispatcher eventDispatcher)
+ {
+ _game = game;
+ _eventDispatcher = eventDispatcher;
+
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(PacketEventArgs<RaidPacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+ if (packet.Type is not(RaidPacketType.Leader or RaidPacketType.ListMembers or RaidPacketType.PlayerHealths or RaidPacketType.Leave))
+ {
+ return Result.FromSuccess();
+ }
+
+ Raid? prevRaid = null;
+ var currentRaid = await _game.UpdateRaidAsync
+ (
+ raid =>
+ {
+ prevRaid = raid;
+ switch (packet.Type)
+ {
+ case RaidPacketType.Leave:
+ if (packet.LeaveType is not null && packet.LeaveType == RaidLeaveType.PlayerLeft)
+ { // the player has left.
+ prevRaid = raid with
+ {
+ State = RaidState.Left
+ };
+
+ return null;
+ }
+
+ return raid;
+ case RaidPacketType.Leader:
+ if (packet.LeaderId is null)
+ { // set the raid to null.
+ return null;
+ }
+
+ return raid with
+ {
+ Leader = raid.Members?.FirstOrDefault(x => x.PlayerId == packet.LeaderId.Value)
+ };
+ case RaidPacketType.ListMembers:
+ return raid with
+ {
+ Members = raid.Members?.Where(x => packet.ListMembersPlayerIds?.Contains(x.PlayerId) ?? true).ToList()
+ };
+ case RaidPacketType.PlayerHealths:
+ // update healths
+ foreach (var member in raid.Members ?? (IReadOnlyList<GroupMember>)Array.Empty<GroupMember>())
+ {
+ var data = packet.PlayerHealths?.FirstOrDefault(x => x.PlayerId == member.PlayerId);
+
+ if (data is not null)
+ {
+ member.Hp ??= new Health();
+ member.Mp ??= new Health();
+
+ member.Hp.Percentage = data.HpPercentage;
+ member.Mp.Percentage = data.MpPercentage;
+ }
+ }
+ return raid;
+ }
+
+ return raid;
+ },
+ ct: ct
+ );
+
+ if (currentRaid == null && prevRaid != null)
+ {
+ return await _eventDispatcher.DispatchEvent(new RaidFinishedEvent(prevRaid), ct);
+ }
+
+ return Result.FromSuccess();
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.Game/PacketHandlers/Raids/RbossResponder.cs => Core/NosSmooth.Game/PacketHandlers/Raids/RbossResponder.cs +80 -0
@@ 0,0 1,80 @@
+//
+// RbossResponder.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Core.Packets;
+using NosSmooth.Game.Data.Entities;
+using NosSmooth.Game.Data.Raids;
+using NosSmooth.Game.Events.Core;
+using NosSmooth.Game.Events.Raids;
+using NosSmooth.Packets.Server.Raids;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Raids;
+
+/// <summary>
+/// A responder to <see cref="RbossPacket"/>.
+/// </summary>
+public class RbossResponder : IPacketResponder<RbossPacket>
+{
+ private readonly Game _game;
+ private readonly EventDispatcher _eventDispatcher;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RbossResponder"/> class.
+ /// </summary>
+ /// <param name="game">The game.</param>
+ /// <param name="eventDispatcher">The event dispatcher.</param>
+ public RbossResponder(Game game, EventDispatcher eventDispatcher)
+ {
+ _game = game;
+ _eventDispatcher = eventDispatcher;
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(PacketEventArgs<RbossPacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+ var map = _game.CurrentMap;
+ if (map is null)
+ {
+ return Result.FromSuccess();
+ }
+
+ var bossEntity = packet.EntityId is not null ? map.Entities.GetEntity<Monster>(packet.EntityId.Value) : null;
+
+ RaidState? previousState = null;
+ var currentRaid = await _game.UpdateRaidAsync
+ (
+ raid =>
+ {
+ previousState = raid.State;
+ if (bossEntity is not null && (raid.Bosses is null || !raid.Bosses.Contains(bossEntity)))
+ {
+ return raid with
+ {
+ Boss = bossEntity,
+ Bosses = (raid.Bosses ?? Array.Empty<Monster>()).Append(bossEntity).ToList(),
+ State = RaidState.BossFight
+ };
+ }
+
+ return raid with
+ { // this will oscillate between more bosses ...
+ Boss = bossEntity
+ };
+ },
+ ct: ct
+ );
+
+ if (currentRaid is not null && previousState is not null && previousState != currentRaid.State)
+ {
+ return await _eventDispatcher.DispatchEvent
+ (new RaidStateChangedEvent(previousState.Value, currentRaid.State, currentRaid), ct);
+ }
+
+ return Result.FromSuccess();
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.Game/PacketHandlers/Raids/RdlstResponder.cs => Core/NosSmooth.Game/PacketHandlers/Raids/RdlstResponder.cs +98 -0
@@ 0,0 1,98 @@
+//
+// RdlstResponder.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Core.Packets;
+using NosSmooth.Game.Data.Raids;
+using NosSmooth.Game.Data.Social;
+using NosSmooth.Game.Events.Core;
+using NosSmooth.Game.Events.Raids;
+using NosSmooth.Packets.Server.Raids;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Raids;
+
+/// <summary>
+/// A responder to <see cref="RdlstPacket"/>.
+/// </summary>
+public class RdlstResponder : IPacketResponder<RdlstPacket>
+{
+ private readonly Game _game;
+ private readonly EventDispatcher _eventDispatcher;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RdlstResponder"/> class.
+ /// </summary>
+ /// <param name="game">The game.</param>
+ /// <param name="eventDispatcher">The event dispatcher.</param>
+ public RdlstResponder(Game game, EventDispatcher eventDispatcher)
+ {
+ _game = game;
+ _eventDispatcher = eventDispatcher;
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(PacketEventArgs<RdlstPacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+
+ IReadOnlyList<GroupMember> UpdateMembers(IReadOnlyList<GroupMember>? currentMembers)
+ {
+ return packet.Players
+ .Select
+ (
+ packetMember =>
+ {
+ var newMember = currentMembers?.FirstOrDefault
+ (member => packetMember.Id == member.PlayerId) ?? new GroupMember(packetMember.Id);
+
+ newMember.Class = packetMember.Class;
+ newMember.Level = packetMember.Level;
+ newMember.HeroLevel = packetMember.HeroLevel;
+ newMember.Sex = packetMember.Sex;
+ newMember.MorphVNum = packetMember.MorphVNum;
+
+ return newMember;
+ }
+ ).ToArray();
+ }
+
+ Raid? prevRaid = null;
+ var currentRaid = await _game.CreateOrUpdateRaidAsync
+ (
+ () => new Raid
+ (
+ packet.RaidType,
+ RaidState.Waiting,
+ packet.MinimumLevel,
+ packet.MaximumLevel,
+ null,
+ null,
+ null,
+ null,
+ null
+ ),
+ raid =>
+ {
+ prevRaid = raid;
+ return raid with
+ {
+ Type = packet.RaidType,
+ MinimumLevel = packet.MinimumLevel,
+ MaximumLevel = packet.MaximumLevel,
+ Members = UpdateMembers(raid.Members),
+ };
+ },
+ ct: ct
+ );
+
+ if (prevRaid is null && currentRaid is not null)
+ {
+ return await _eventDispatcher.DispatchEvent(new RaidJoinedEvent(currentRaid), ct);
+ }
+
+ return Result.FromSuccess();
+ }
+}<
\ No newline at end of file
A Packets/NosSmooth.Packets/Enums/Raids/RaidBfPacketType.cs => Packets/NosSmooth.Packets/Enums/Raids/RaidBfPacketType.cs +25 -0
@@ 0,0 1,25 @@
+//
+// RaidBfPacketType.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Diagnostics.CodeAnalysis;
+using NosSmooth.Packets.Server.Raids;
+#pragma warning disable CS1591
+
+namespace NosSmooth.Packets.Enums.Raids;
+
+/// <summary>
+/// A type of <see cref="RaidBfPacket"/>.
+/// </summary>
+[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1602:Enumeration items should be documented", Justification = "Self-explanatory.")]
+public enum RaidBfPacketType
+{
+ MissionStarted = 0,
+ MissionCleared = 1,
+ TimeUp = 2,
+ LeaderDied = 3,
+ NoLivesLeft = 4,
+ MissionFailed = 5
+}<
\ No newline at end of file