From 57e494db60d03920d8d1c257f29eb628955235cb Mon Sep 17 00:00:00 2001 From: Rutherther Date: Fri, 6 Jan 2023 23:00:33 +0100 Subject: [PATCH] feat(game): add group processing --- Core/NosSmooth.Game/Data/Social/Group.cs | 43 +++++- .../NosSmooth.Game/Data/Social/GroupMember.cs | 58 ++++++++ .../Events/Groups/GroupInitializedEvent.cs | 11 ++ .../Events/Groups/GroupMemberStatEvent.cs | 11 ++ .../Extensions/ServiceCollectionExtensions.cs | 4 +- Core/NosSmooth.Game/Game.cs | 38 ++--- .../Characters/CharacterInitResponder.cs | 18 ++- .../Characters/WalkResponder.cs | 6 + .../Inventory/InventoryInitResponder.cs | 11 ++ .../Relations/GroupInitResponder.cs | 132 ++++++++++++++++++ .../PlayerSkillResponder.cs} | 12 +- 11 files changed, 316 insertions(+), 28 deletions(-) create mode 100644 Core/NosSmooth.Game/Data/Social/GroupMember.cs create mode 100644 Core/NosSmooth.Game/Events/Groups/GroupInitializedEvent.cs create mode 100644 Core/NosSmooth.Game/Events/Groups/GroupMemberStatEvent.cs create mode 100644 Core/NosSmooth.Game/PacketHandlers/Relations/GroupInitResponder.cs rename Core/NosSmooth.Game/PacketHandlers/{Characters/SkillResponder.cs => Skills/PlayerSkillResponder.cs} (92%) diff --git a/Core/NosSmooth.Game/Data/Social/Group.cs b/Core/NosSmooth.Game/Data/Social/Group.cs index 1fde2bb..94dcb49 100644 --- a/Core/NosSmooth.Game/Data/Social/Group.cs +++ b/Core/NosSmooth.Game/Data/Social/Group.cs @@ -5,7 +5,10 @@ // 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.Maps; +using NosSmooth.Packets.Enums.Entities; using OneOf; +using Remora.Results; namespace NosSmooth.Game.Data.Social; @@ -15,4 +18,42 @@ namespace NosSmooth.Game.Data.Social; /// The id of the group. /// The size of the group. /// The members of the group. (excluding the character) -public record Group(short? Id, byte? Size, IReadOnlyList>? Members); \ No newline at end of file +public record Group(short? Id, IReadOnlyList? Members) +{ + /// + /// Gets the living entities from the map associated with group members. + /// + /// + /// If a group member is not found on the map, null will be returned instead on its position. + /// + /// The map to get map from. + /// The living entities representing group members. + public IReadOnlyList GetLivingEntities(Game game) + => GetLivingEntities(game.CurrentMap); + + /// + /// Gets the living entities from the map associated with group members. + /// + /// + /// If a group member is not found on the map, null will be returned instead on its position. + /// + /// The map to get entities from. + /// The living entities representing group members. + public IReadOnlyList GetLivingEntities(Map? map) + => GetLivingEntities(map?.Entities); + + /// + /// Gets the living entities from the map associated with group members. + /// + /// + /// If a group member is not found on the map, null will be returned instead on its position. + /// + /// The entities to look at. + /// The living entities representing group members. + public IReadOnlyList GetLivingEntities(MapEntities? entities) + { + return (IReadOnlyList?)Members? + .Select(x => entities?.GetEntity(x.PlayerId)) + .ToList() ?? Array.Empty(); + } +} \ No newline at end of file diff --git a/Core/NosSmooth.Game/Data/Social/GroupMember.cs b/Core/NosSmooth.Game/Data/Social/GroupMember.cs new file mode 100644 index 0000000..bb46552 --- /dev/null +++ b/Core/NosSmooth.Game/Data/Social/GroupMember.cs @@ -0,0 +1,58 @@ +// +// GroupMember.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.Info; +using NosSmooth.Packets.Enums.Players; + +namespace NosSmooth.Game.Data.Social; + +public record GroupMember(long PlayerId) +{ + /// + /// Gets the level of the member. + /// + public byte Level { get; internal set; } + + /// + /// Gets the hero level of the member. + /// + public byte? HeroLevel { get; internal set; } + + /// + /// Gets the name of the member. + /// + public string? Name { get; internal set; } + + /// + /// Gets the class of the member. + /// + public PlayerClass Class { get; internal set; } + + /// + /// Gets the sex of the member. + /// + public SexType Sex { get; internal set; } + + /// + /// Gets the morph vnum of the player. + /// + public long MorphVNum { get; internal set; } + + /// + /// Gets the hp of the member. + /// + public Health? Hp { get; internal set; } + + /// + /// Gets the mp of the member. + /// + public Health? Mp { get; internal set; } + + /// + /// Gets the effects of the member. + /// + public IReadOnlyList? EffectsVNums { get; internal set; } +} \ No newline at end of file diff --git a/Core/NosSmooth.Game/Events/Groups/GroupInitializedEvent.cs b/Core/NosSmooth.Game/Events/Groups/GroupInitializedEvent.cs new file mode 100644 index 0000000..6afd6fd --- /dev/null +++ b/Core/NosSmooth.Game/Events/Groups/GroupInitializedEvent.cs @@ -0,0 +1,11 @@ +// +// GroupInitializedEvent.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.Social; + +namespace NosSmooth.Game.Events.Groups; + +public record GroupInitializedEvent(Group Group) : IGameEvent; \ No newline at end of file diff --git a/Core/NosSmooth.Game/Events/Groups/GroupMemberStatEvent.cs b/Core/NosSmooth.Game/Events/Groups/GroupMemberStatEvent.cs new file mode 100644 index 0000000..b7074ec --- /dev/null +++ b/Core/NosSmooth.Game/Events/Groups/GroupMemberStatEvent.cs @@ -0,0 +1,11 @@ +// +// GroupMemberStatEvent.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.Social; + +namespace NosSmooth.Game.Events.Groups; + +public record GroupMemberStatEvent(GroupMember Member) : IGameEvent; \ No newline at end of file diff --git a/Core/NosSmooth.Game/Extensions/ServiceCollectionExtensions.cs b/Core/NosSmooth.Game/Extensions/ServiceCollectionExtensions.cs index 7b5cdcb..def27fd 100644 --- a/Core/NosSmooth.Game/Extensions/ServiceCollectionExtensions.cs +++ b/Core/NosSmooth.Game/Extensions/ServiceCollectionExtensions.cs @@ -14,6 +14,7 @@ using NosSmooth.Game.PacketHandlers.Characters; using NosSmooth.Game.PacketHandlers.Entities; using NosSmooth.Game.PacketHandlers.Inventory; using NosSmooth.Game.PacketHandlers.Map; +using NosSmooth.Game.PacketHandlers.Relations; using NosSmooth.Game.PacketHandlers.Specialists; namespace NosSmooth.Game.Extensions; @@ -38,10 +39,11 @@ public static class ServiceCollectionExtensions serviceCollection .AddPacketResponder() - .AddPacketResponder() + .AddPacketResponder() .AddPacketResponder() .AddPacketResponder() .AddPacketResponder() + .AddPacketResponder() .AddPacketResponder() .AddPacketResponder() .AddPacketResponder() diff --git a/Core/NosSmooth.Game/Game.cs b/Core/NosSmooth.Game/Game.cs index 06ef675..5cf72ef 100644 --- a/Core/NosSmooth.Game/Game.cs +++ b/Core/NosSmooth.Game/Game.cs @@ -96,10 +96,10 @@ public class Game : IStatefulEntity /// Whether to release the semaphore used for changing the skills. /// The cancellation token for cancelling the operation. /// The updated skills. - internal async Task CreateOrUpdateSkillsAsync + internal async Task CreateOrUpdateSkillsAsync ( - Func create, - Func update, + Func create, + Func update, bool releaseSemaphore = true, CancellationToken ct = default ) @@ -124,10 +124,10 @@ public class Game : IStatefulEntity /// Whether to release the semaphore used for changing the inventory. /// The cancellation token for cancelling the operation. /// The updated inventory. - internal async Task CreateOrUpdateInventoryAsync + internal async Task CreateOrUpdateInventoryAsync ( - Func create, - Func update, + Func create, + Func update, bool releaseSemaphore = true, CancellationToken ct = default ) @@ -152,10 +152,10 @@ public class Game : IStatefulEntity /// Whether to release the semaphore used for changing the family. /// The cancellation token for cancelling the operation. /// The updated family. - internal async Task CreateOrUpdateFamilyAsync + internal async Task CreateOrUpdateFamilyAsync ( - Func create, - Func update, + Func create, + Func update, bool releaseSemaphore = true, CancellationToken ct = default ) @@ -196,10 +196,10 @@ public class Game : IStatefulEntity /// Whether to release the semaphore used for changing the group. /// The cancellation token for cancelling the operation. /// The updated group. - internal async Task CreateOrUpdateGroupAsync + internal async Task CreateOrUpdateGroupAsync ( - Func create, - Func update, + Func create, + Func update, bool releaseSemaphore = true, CancellationToken ct = default ) @@ -224,10 +224,10 @@ public class Game : IStatefulEntity /// Whether to release the semaphore used for changing the character. /// The cancellation token for cancelling the operation. /// The updated character. - internal async Task CreateOrUpdateCharacterAsync + internal async Task CreateOrUpdateCharacterAsync ( - Func create, - Func update, + Func create, + Func update, bool releaseSemaphore = true, CancellationToken ct = default ) @@ -322,13 +322,13 @@ public class Game : IStatefulEntity } } - private async Task CreateOrUpdateAsync + private async Task CreateOrUpdateAsync ( GameSemaphoreType type, Func get, - Action set, - Func create, - Func update, + Action set, + Func create, + Func update, bool releaseSemaphore = true, CancellationToken ct = default ) diff --git a/Core/NosSmooth.Game/PacketHandlers/Characters/CharacterInitResponder.cs b/Core/NosSmooth.Game/PacketHandlers/Characters/CharacterInitResponder.cs index 6f1888f..e14d4fa 100644 --- a/Core/NosSmooth.Game/PacketHandlers/Characters/CharacterInitResponder.cs +++ b/Core/NosSmooth.Game/PacketHandlers/Characters/CharacterInitResponder.cs @@ -4,6 +4,7 @@ // 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; using NosSmooth.Core.Packets; using NosSmooth.Game.Data.Characters; using NosSmooth.Game.Data.Info; @@ -80,7 +81,7 @@ public class CharacterInitResponder : IPacketResponder, IPacketResp await _game.CreateOrUpdateGroupAsync ( - () => new Group(packet.GroupId, null, null), + () => new Group(packet.GroupId, null), g => g with { Id = packet.GroupId @@ -107,6 +108,11 @@ public class CharacterInitResponder : IPacketResponder, IPacketResp ct: ct ); + if (character is null) + { + throw new UnreachableException(); + } + if (character != oldCharacter) { return await _eventDispatcher.DispatchEvent(new ReceivedCharacterDataEvent(character), ct); @@ -143,6 +149,11 @@ public class CharacterInitResponder : IPacketResponder, IPacketResp ct: ct ); + if (character is null) + { + throw new UnreachableException(); + } + if (character != oldCharacter) { return await _eventDispatcher.DispatchEvent(new ReceivedCharacterDataEvent(character), ct); @@ -182,6 +193,11 @@ public class CharacterInitResponder : IPacketResponder, IPacketResp ct: ct ); + if (character is null) + { + throw new UnreachableException(); + } + if (oldCharacter != character) { return await _eventDispatcher.DispatchEvent(new ReceivedCharacterDataEvent(character), ct); diff --git a/Core/NosSmooth.Game/PacketHandlers/Characters/WalkResponder.cs b/Core/NosSmooth.Game/PacketHandlers/Characters/WalkResponder.cs index ce5dd79..e28652d 100644 --- a/Core/NosSmooth.Game/PacketHandlers/Characters/WalkResponder.cs +++ b/Core/NosSmooth.Game/PacketHandlers/Characters/WalkResponder.cs @@ -4,6 +4,7 @@ // 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; using NosSmooth.Core.Packets; using NosSmooth.Game.Data.Characters; using NosSmooth.Game.Data.Info; @@ -55,6 +56,11 @@ public class WalkResponder : IPacketResponder ct: ct ); + if (character is null) + { + throw new UnreachableException(); + } + return await _eventDispatcher.DispatchEvent ( new EntityMovedEvent(character, oldPosition, character.Position!.Value), diff --git a/Core/NosSmooth.Game/PacketHandlers/Inventory/InventoryInitResponder.cs b/Core/NosSmooth.Game/PacketHandlers/Inventory/InventoryInitResponder.cs index 8b9005e..8a094c9 100644 --- a/Core/NosSmooth.Game/PacketHandlers/Inventory/InventoryInitResponder.cs +++ b/Core/NosSmooth.Game/PacketHandlers/Inventory/InventoryInitResponder.cs @@ -4,6 +4,7 @@ // 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; using Microsoft.Extensions.Logging; using NosSmooth.Core.Extensions; using NosSmooth.Core.Packets; @@ -91,6 +92,11 @@ public class InventoryInitResponder : IPacketResponder, IPacketRespon ct: ct ); + if (inventory is null) + { + throw new UnreachableException(); + } + if (packet.Bag == Packets.Enums.Inventory.BagType.Costume) { // last bag initialized. TODO solve race condition. @@ -161,6 +167,11 @@ public class InventoryInitResponder : IPacketResponder, IPacketRespon ct: ct ); + if (inventory is null) + { + throw new UnreachableException(); + } + return await _eventDispatcher.DispatchEvent ( new InventorySlotUpdatedEvent diff --git a/Core/NosSmooth.Game/PacketHandlers/Relations/GroupInitResponder.cs b/Core/NosSmooth.Game/PacketHandlers/Relations/GroupInitResponder.cs new file mode 100644 index 0000000..39df5fb --- /dev/null +++ b/Core/NosSmooth.Game/PacketHandlers/Relations/GroupInitResponder.cs @@ -0,0 +1,132 @@ +// +// GroupInitResponder.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; +using System.Linq.Expressions; +using NosSmooth.Core.Packets; +using NosSmooth.Game.Data.Info; +using NosSmooth.Game.Data.Social; +using NosSmooth.Game.Events.Core; +using NosSmooth.Game.Events.Groups; +using NosSmooth.Packets.Enums.Entities; +using NosSmooth.Packets.Server.Groups; +using Remora.Results; + +namespace NosSmooth.Game.PacketHandlers.Relations; + +/// +/// A group initialization responder. +/// +public class GroupInitResponder : IPacketResponder, IPacketResponder +{ + private readonly Game _game; + private readonly EventDispatcher _eventDispatcher; + + /// + /// Initializes a new instance of the class. + /// + /// The game. + /// The event dispatcher. + public GroupInitResponder(Game game, EventDispatcher eventDispatcher) + { + _game = game; + _eventDispatcher = eventDispatcher; + } + + /// + public async Task Respond(PacketEventArgs packetArgs, CancellationToken ct = default) + { + var packet = packetArgs.Packet; + + Group BuildGroup(Group? group) + { + var members = packet.PinitSubPackets? + .Where(x => x.PlayerSubPacket is not null) + .OrderBy(x => x.PlayerSubPacket!.GroupPosition) + .Select(e => CreateEntity(e, group?.Members)) + .ToList() ?? new List(); + + return new Group(null, members); + } + + var group = await _game.CreateOrUpdateGroupAsync + ( + () => BuildGroup(null), + BuildGroup, + ct: ct + ); + + if (group is null) + { + throw new UnreachableException(); + } + + return await _eventDispatcher.DispatchEvent(new GroupInitializedEvent(group), ct); + } + + /// + public async Task Respond(PacketEventArgs packetArgs, CancellationToken ct = default) + { + var packet = packetArgs.Packet; + if (packet.EntityType != EntityType.Player) + { + return Result.FromSuccess(); + } + + GroupMember? member = null; + await _game.CreateOrUpdateGroupAsync + ( + () => null, + g => + { + member = g.Members?.FirstOrDefault(x => x.PlayerId == packet.EntityId); + + if (member is not null) + { + member.Hp = new Health { Amount = packet.Hp, Percentage = packet.HpPercentage }; + member.Mp = new Health { Amount = packet.Mp, Percentage = packet.MpPercentage }; + member.Class = packet.PlayerClass ?? member.Class; + member.Sex = packet.PlayerSex ?? member.Sex; + member.EffectsVNums = packet.Effects?.Select(x => x.CardId).ToList(); + member.MorphVNum = packet.PlayerMorphVNum ?? member.MorphVNum; + } + + return g; + }, + ct: ct + ); + + if (member is not null) + { + await _eventDispatcher.DispatchEvent + ( + new GroupMemberStatEvent(member), + ct + ); + } + + return Result.FromSuccess(); + } + + private GroupMember CreateEntity(PinitSubPacket packet, IReadOnlyList? members) + { + var playerSubPacket = packet.PlayerSubPacket!; + var originalMember = members?.FirstOrDefault(x => x.PlayerId == packet.EntityId); + + return new(packet.EntityId) + { + Level = playerSubPacket.Level, + HeroLevel = playerSubPacket.HeroLevel, + Name = playerSubPacket.Name?.Name, + Class = playerSubPacket.Class, + Sex = playerSubPacket.Sex, + MorphVNum = playerSubPacket.MorphVNum, + Hp = originalMember?.Hp, + Mp = originalMember?.Mp, + EffectsVNums = originalMember?.EffectsVNums + }; + } +} \ No newline at end of file diff --git a/Core/NosSmooth.Game/PacketHandlers/Characters/SkillResponder.cs b/Core/NosSmooth.Game/PacketHandlers/Skills/PlayerSkillResponder.cs similarity index 92% rename from Core/NosSmooth.Game/PacketHandlers/Characters/SkillResponder.cs rename to Core/NosSmooth.Game/PacketHandlers/Skills/PlayerSkillResponder.cs index 1b70b8f..9f4883b 100644 --- a/Core/NosSmooth.Game/PacketHandlers/Characters/SkillResponder.cs +++ b/Core/NosSmooth.Game/PacketHandlers/Skills/PlayerSkillResponder.cs @@ -1,5 +1,5 @@ // -// SkillResponder.cs +// PlayerSkillResponder.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. @@ -19,26 +19,26 @@ namespace NosSmooth.Game.PacketHandlers.Characters; /// /// Responds to SkiPacket to add skill to the character. /// -public class SkillResponder : IPacketResponder +public class PlayerSkillResponder : IPacketResponder { private readonly Game _game; private readonly EventDispatcher _eventDispatcher; private readonly IInfoService _infoService; - private readonly ILogger _logger; + private readonly ILogger _logger; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The nostale game. /// The event dispatcher. /// The info service. /// The logger. - public SkillResponder + public PlayerSkillResponder ( Game game, EventDispatcher eventDispatcher, IInfoService infoService, - ILogger logger + ILogger logger ) { _game = game; -- 2.48.1