~ruther/NosSmooth

ed3cc81862dc539872cc6bafee351b9c688ff21e — František Boháček 2 years ago 6334c0f
feat(game): respond to rest, raidfhp, tp, throw, mapclear, die, char_sc packets

Resolves #58
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

Do not follow this link