M Core/NosSmooth.Game/Data/Maps/MapEntities.cs => Core/NosSmooth.Game/Data/Maps/MapEntities.cs +8 -0
@@ 109,6 109,14 @@ public class MapEntities
}
/// <summary>
+ /// Remove all entities.
+ /// </summary>
+ internal void Clear()
+ {
+ _entities.Clear();
+ }
+
+ /// <summary>
/// Remove the given entity.
/// </summary>
/// <param name="entityId">The id of the entity to remove.</param>
M Core/NosSmooth.Game/Extensions/ServiceCollectionExtensions.cs => Core/NosSmooth.Game/Extensions/ServiceCollectionExtensions.cs +17 -4
@@ 13,6 13,7 @@ using NosSmooth.Game.Apis.Unsafe;
using NosSmooth.Game.Contracts;
using NosSmooth.Game.Events.Core;
using NosSmooth.Game.Events.Inventory;
+using NosSmooth.Game.Events.Ui;
using NosSmooth.Game.PacketHandlers.Act4;
using NosSmooth.Game.PacketHandlers.Characters;
using NosSmooth.Game.PacketHandlers.Entities;
@@ 22,6 23,7 @@ using NosSmooth.Game.PacketHandlers.Raids;
using NosSmooth.Game.PacketHandlers.Relations;
using NosSmooth.Game.PacketHandlers.Skills;
using NosSmooth.Game.PacketHandlers.Specialists;
+using NosSmooth.Game.PacketHandlers.Ui;
using NosSmooth.Packets.Server.Raids;
namespace NosSmooth.Game.Extensions;
@@ 57,6 59,7 @@ public static class ServiceCollectionExtensions
.AddPacketResponder<PlayerSkillResponder>()
.AddPacketResponder<MatesSkillResponder>()
.AddPacketResponder<SkillUsedResponder>()
+ .AddPacketResponder<AoeSkillUsedResponder>()
// friends
.AddPacketResponder<FriendInitResponder>()
@@ 70,9 73,6 @@ public static class ServiceCollectionExtensions
// mates
.AddPacketResponder<MatesInitResponder>()
- // skills
- .AddPacketResponder<AoeSkillUsedResponder>()
-
// map
.AddPacketResponder<AtResponder>()
.AddPacketResponder<CMapResponder>()
@@ 81,6 81,14 @@ public static class ServiceCollectionExtensions
.AddPacketResponder<InResponder>()
.AddPacketResponder<MoveResponder>()
.AddPacketResponder<OutResponder>()
+ .AddPacketResponder<RestResponder>()
+ .AddPacketResponder<TpResponder>()
+ .AddPacketResponder<MapclearResponder>()
+
+ // entities
+ .AddPacketResponder<DieResponder>()
+ .AddPacketResponder<ReviveResponder>()
+ .AddPacketResponder<CharScResponder>()
// hp, mp
.AddPacketResponder<StatPacketResponder>()
@@ 92,11 100,16 @@ public static class ServiceCollectionExtensions
.AddPacketResponder<EqResponder>()
// raids
+ .AddPacketResponder<ThrowResponder>()
.AddPacketResponder<RaidBfResponder>()
.AddPacketResponder<RaidMbfResponder>()
.AddPacketResponder<RaidResponder>()
+ .AddPacketResponder<RaidHpMpResponder>()
.AddPacketResponder<RbossResponder>()
- .AddPacketResponder<RdlstResponder>();
+ .AddPacketResponder<RdlstResponder>()
+
+ // ui
+ .AddPacketResponder<DialogOpenResponder>();
serviceCollection
.AddTransient<DialogHandler>()
A Core/NosSmooth.Game/PacketHandlers/Entities/CharScResponder.cs => Core/NosSmooth.Game/PacketHandlers/Entities/CharScResponder.cs +43 -0
@@ 0,0 1,43 @@
+//
+// CharScResponder.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.Packets.Server.Entities;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Entities;
+
+/// <summary>
+/// A responder to <see cref="CharScPacket"/>.
+/// </summary>
+public class CharScResponder : IPacketResponder<CharScPacket>
+{
+ private readonly Game _game;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CharScResponder"/> class.
+ /// </summary>
+ /// <param name="game">The game.</param>
+ public CharScResponder(Game game)
+ {
+ _game = game;
+ }
+
+ /// <inheritdoc />
+ public Task<Result> Respond(PacketEventArgs<CharScPacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+ var entity = _game.CurrentMap?.Entities.GetEntity<ILivingEntity>(packet.EntityId);
+
+ if (entity is not null)
+ {
+ entity.Size = packet.Size;
+ }
+
+ return Task.FromResult(Result.FromSuccess());
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.Game/PacketHandlers/Entities/DieResponder.cs => Core/NosSmooth.Game/PacketHandlers/Entities/DieResponder.cs +59 -0
@@ 0,0 1,59 @@
+//
+// DieResponder.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.Info;
+using NosSmooth.Game.Events.Core;
+using NosSmooth.Game.Events.Entities;
+using NosSmooth.Packets.Enums.Entities;
+using NosSmooth.Packets.Server.Entities;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Entities;
+
+/// <summary>
+/// A responder to <see cref="DiePacket"/>.
+/// </summary>
+public class DieResponder : IPacketResponder<DiePacket>
+{
+ private readonly Game _game;
+ private readonly EventDispatcher _eventDispatcher;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="DieResponder"/> class.
+ /// </summary>
+ /// <param name="game">The game.</param>
+ /// <param name="eventDispatcher">The event dispatcher.</param>
+ public DieResponder(Game game, EventDispatcher eventDispatcher)
+ {
+ _game = game;
+ _eventDispatcher = eventDispatcher;
+ }
+
+ /// <inheritdoc />
+ public Task<Result> Respond(PacketEventArgs<DiePacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+ var entity = _game.CurrentMap?.Entities.GetEntity<ILivingEntity>(packet.TargetEntityId);
+
+ if (entity is null)
+ {
+ return Task.FromResult(Result.FromSuccess());
+ }
+
+ if (entity.Type is not(EntityType.Player or EntityType.Npc))
+ {
+ _game.CurrentMap?.Entities.RemoveEntity(entity);
+ }
+
+ entity.Hp ??= new Health();
+ entity.Hp.Amount = 0;
+ entity.Hp.Percentage = 0;
+
+ return _eventDispatcher.DispatchEvent(new EntityDiedEvent(entity, null), ct);
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.Game/PacketHandlers/Entities/RestResponder.cs => Core/NosSmooth.Game/PacketHandlers/Entities/RestResponder.cs +43 -0
@@ 0,0 1,43 @@
+//
+// RestResponder.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.Packets.Server.UI;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Entities;
+
+/// <summary>
+/// Responds to <see cref="RestPacket"/>.
+/// </summary>
+public class RestResponder : IPacketResponder<RestPacket>
+{
+ private readonly Game _game;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RestResponder"/> class.
+ /// </summary>
+ /// <param name="game">The game.</param>
+ public RestResponder(Game game)
+ {
+ _game = game;
+ }
+
+ /// <inheritdoc />
+ public Task<Result> Respond(PacketEventArgs<RestPacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+ var entity = _game.CurrentMap?.Entities.GetEntity<ILivingEntity>(packet.EntityId);
+
+ if (entity is not null)
+ {
+ entity.IsSitting = packet.IsSitting;
+ }
+
+ return Task.FromResult(Result.FromSuccess());
+ }
+}<
\ No newline at end of file
R Core/NosSmooth.Game/PacketHandlers/Map/ReviveResponder.cs => Core/NosSmooth.Game/PacketHandlers/Entities/ReviveResponder.cs +1 -1
@@ 9,7 9,7 @@ using NosSmooth.Game.Data.Entities;
using NosSmooth.Packets.Server.Entities;
using Remora.Results;
-namespace NosSmooth.Game.PacketHandlers.Map;
+namespace NosSmooth.Game.PacketHandlers.Entities;
/// <summary>
/// A handler of <see cref="RevivePacket"/>.
A Core/NosSmooth.Game/PacketHandlers/Map/MapclearResponder.cs => Core/NosSmooth.Game/PacketHandlers/Map/MapclearResponder.cs +35 -0
@@ 0,0 1,35 @@
+//
+// MapclearResponder.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.Packets.Server.Maps;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Map;
+
+/// <summary>
+/// A responder to <see cref="MapclearPacket"/>.
+/// </summary>
+public class MapclearResponder : IPacketResponder<MapclearPacket>
+{
+ private readonly Game _game;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MapclearResponder"/> class.
+ /// </summary>
+ /// <param name="game">The game.</param>
+ public MapclearResponder(Game game)
+ {
+ _game = game;
+ }
+
+ /// <inheritdoc />
+ public Task<Result> Respond(PacketEventArgs<MapclearPacket> packetArgs, CancellationToken ct = default)
+ {
+ _game.CurrentMap?.Entities.Clear();
+ return Task.FromResult(Result.FromSuccess());
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.Game/PacketHandlers/Map/ThrowResponder.cs => Core/NosSmooth.Game/PacketHandlers/Map/ThrowResponder.cs +71 -0
@@ 0,0 1,71 @@
+//
+// ThrowResponder.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 Microsoft.Extensions.Logging;
+using NosSmooth.Core.Extensions;
+using NosSmooth.Core.Packets;
+using NosSmooth.Data.Abstractions;
+using NosSmooth.Game.Data.Entities;
+using NosSmooth.Game.Data.Info;
+using NosSmooth.Packets.Server.Maps;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Map;
+
+/// <summary>
+/// A responder to <see cref="ThrowResponder"/>.
+/// </summary>
+public class ThrowResponder : IPacketResponder<ThrowPacket>
+{
+ private readonly Game _game;
+ private readonly IInfoService _infoService;
+ private readonly ILogger<ThrowResponder> _logger;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ThrowResponder"/> class.
+ /// </summary>
+ /// <param name="game">The game.</param>
+ /// <param name="infoService">The info service.</param>
+ /// <param name="logger">The logger.</param>
+ public ThrowResponder(Game game, IInfoService infoService, ILogger<ThrowResponder> logger)
+ {
+ _game = game;
+ _infoService = infoService;
+ _logger = logger;
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(PacketEventArgs<ThrowPacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+ var map = _game.CurrentMap;
+
+ if (map is null)
+ {
+ return Result.FromSuccess();
+ }
+
+ var itemInfoResult = await _infoService.GetItemInfoAsync(packet.ItemVNum, ct);
+ if (!itemInfoResult.IsDefined(out var itemInfo))
+ {
+ _logger.LogWarning("Could not obtain item info for vnum {vnum}: {error}", packet.ItemVNum, itemInfoResult.ToFullString());
+ }
+
+ map.Entities.AddEntity
+ (
+ new GroundItem
+ {
+ Position = new Position(packet.TargetX, packet.TargetY),
+ Amount = packet.Amount,
+ Id = packet.DropId,
+ ItemInfo = itemInfo,
+ VNum = packet.ItemVNum
+ }
+ );
+
+ return Result.FromSuccess();
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.Game/PacketHandlers/Map/TpResponder.cs => Core/NosSmooth.Game/PacketHandlers/Map/TpResponder.cs +44 -0
@@ 0,0 1,44 @@
+//
+// TpResponder.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.Info;
+using NosSmooth.Packets.Server.Maps;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Map;
+
+/// <summary>
+/// A responder to <see cref="TpPacket"/>.
+/// </summary>
+public class TpResponder : IPacketResponder<TpPacket>
+{
+ private readonly Game _game;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TpResponder"/> class.
+ /// </summary>
+ /// <param name="game">The game.</param>
+ public TpResponder(Game game)
+ {
+ _game = game;
+ }
+
+ /// <inheritdoc />
+ public Task<Result> Respond(PacketEventArgs<TpPacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+ var entity = _game.CurrentMap?.Entities.GetEntity(packet.EntityId);
+
+ if (entity is not null)
+ {
+ entity.Position = new Position(packet.PositionX, packet.PositionY);
+ }
+
+ return Task.FromResult(Result.FromSuccess());
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.Game/PacketHandlers/Raids/RaidHpMpResponder.cs => Core/NosSmooth.Game/PacketHandlers/Raids/RaidHpMpResponder.cs +82 -0
@@ 0,0 1,82 @@
+//
+// RaidHpMpResponder.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.Info;
+using NosSmooth.Game.Data.Social;
+using NosSmooth.Packets.Server.Raids;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Raids;
+
+/// <summary>
+/// A responder to <see cref="RaidfhpPacket"/>, <see cref="RaidfmpPacket"/>.
+/// </summary>
+public class RaidHpMpResponder : IPacketResponder<RaidfhpPacket>, IPacketResponder<RaidfmpPacket>
+{
+ private readonly Game _game;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RaidHpMpResponder"/> class.
+ /// </summary>
+ /// <param name="game">The game.</param>
+ public RaidHpMpResponder(Game game)
+ {
+ _game = game;
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(PacketEventArgs<RaidfhpPacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+
+ await _game.UpdateRaidAsync
+ (
+ raid =>
+ {
+ foreach (var member in raid.Members ?? (IReadOnlyList<GroupMember>)Array.Empty<GroupMember>())
+ {
+ var data = packet.HpSubPackets?.FirstOrDefault(x => x.PlayerId == member.PlayerId);
+
+ if (data is not null)
+ {
+ member.Hp ??= new Health();
+ member.Hp.Percentage = data.HpPercentage;
+ }
+ }
+ return raid;
+ },
+ ct: ct
+ );
+ return Result.FromSuccess();
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(PacketEventArgs<RaidfmpPacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+
+ await _game.UpdateRaidAsync
+ (
+ raid =>
+ {
+ foreach (var member in raid.Members ?? (IReadOnlyList<GroupMember>)Array.Empty<GroupMember>())
+ {
+ var data = packet.HpSubPackets?.FirstOrDefault(x => x.PlayerId == member.PlayerId);
+
+ if (data is not null)
+ {
+ member.Mp ??= new Health();
+ member.Mp.Percentage = data.HpPercentage;
+ }
+ }
+ return raid;
+ },
+ ct: ct
+ );
+ return Result.FromSuccess();
+ }
+}<
\ No newline at end of file
M Core/NosSmooth.Game/PacketHandlers/Raids/RdlstResponder.cs => Core/NosSmooth.Game/PacketHandlers/Raids/RdlstResponder.cs +64 -1
@@ 17,7 17,7 @@ namespace NosSmooth.Game.PacketHandlers.Raids;
/// <summary>
/// A responder to <see cref="RdlstPacket"/>.
/// </summary>
-public class RdlstResponder : IPacketResponder<RdlstPacket>
+public class RdlstResponder : IPacketResponder<RdlstPacket>, IPacketResponder<RdlstfPacket>
{
private readonly Game _game;
private readonly EventDispatcher _eventDispatcher;
@@ 95,4 95,67 @@ public class RdlstResponder : IPacketResponder<RdlstPacket>
return Result.FromSuccess();
}
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(PacketEventArgs<RdlstfPacket> 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,
+ UpdateMembers(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