M Core/NosSmooth.Game/Data/Entities/GroundItem.cs => Core/NosSmooth.Game/Data/Entities/GroundItem.cs +1 -1
@@ 18,7 18,7 @@ public class GroundItem : IEntity
/// <summary>
/// Gets or sets the id of the owner, if any.
/// </summary>
- public long? OwnerId { get; set; }
+ public long? OwnerId { get; internal set; }
/// <summary>
/// Gets or sets the amount of the item on the ground.
D Core/NosSmooth.Game/Data/Entities/Partner.cs => Core/NosSmooth.Game/Data/Entities/Partner.cs +0 -12
@@ 1,12 0,0 @@
-//
-// Partner.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.Entities;
-
-/// <summary>
-/// Represents Partner of the Character.
-/// </summary>
-public record Partner() : IPet;>
\ No newline at end of file
D Core/NosSmooth.Game/Data/Entities/Pet.cs => Core/NosSmooth.Game/Data/Entities/Pet.cs +0 -12
@@ 1,12 0,0 @@
-//
-// Pet.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.Entities;
-
-/// <summary>
-/// Represents pet of the character.
-/// </summary>
-public record Pet() : IPet;>
\ No newline at end of file
A Core/NosSmooth.Game/Data/Items/PartnerEquipment.cs => Core/NosSmooth.Game/Data/Items/PartnerEquipment.cs +15 -0
@@ 0,0 1,15 @@
+//
+// PartnerEquipment.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.Items;
+
+public record PartnerEquipment
+(
+ UpgradeableItem? Weapon,
+ UpgradeableItem? Armor,
+ UpgradeableItem? Gauntlet,
+ UpgradeableItem? Boots
+);<
\ No newline at end of file
A Core/NosSmooth.Game/Data/Mates/Mate.cs => Core/NosSmooth.Game/Data/Mates/Mate.cs +29 -0
@@ 0,0 1,29 @@
+//
+// Mate.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.Game.Data.Stats;
+using NosSmooth.Packets.Enums;
+using NosSmooth.PacketSerializer.Abstractions.Common;
+
+namespace NosSmooth.Game.Data.Mates;
+
+public record Mate
+(
+ long MateId,
+ long NpcVNum,
+ long TransportId,
+ Level Level,
+ short Loyalty,
+ MateAttackStats Attack,
+ MateArmorStats Armor,
+ Element Element,
+ Resistance Resistance,
+ Health Hp,
+ Health Mp,
+ string Name,
+ bool IsSummonable
+);<
\ No newline at end of file
A Core/NosSmooth.Game/Data/Mates/Mates.cs => Core/NosSmooth.Game/Data/Mates/Mates.cs +94 -0
@@ 0,0 1,94 @@
+//
+// Mates.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.Collections;
+using System.Collections.Concurrent;
+using NosSmooth.Game.Data.Characters;
+
+namespace NosSmooth.Game.Data.Mates;
+
+/// <summary>
+/// Game mates state.
+/// </summary>
+public class Mates : IEnumerable<Mate>
+{
+ private ConcurrentDictionary<long, Partner> _partners;
+ private ConcurrentDictionary<long, Pet> _pets;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Mates"/> class.
+ /// </summary>
+ public Mates()
+ {
+ _partners = new ConcurrentDictionary<long, Partner>();
+ _pets = new ConcurrentDictionary<long, Pet>();
+ }
+
+ /// <summary>
+ /// Gets all of the partners belonging to the character.
+ /// </summary>
+ public IEnumerable<Partner> Partners => _partners.Values;
+
+ /// <summary>
+ /// Gets all of the pets belonging to the character.
+ /// </summary>
+ public IEnumerable<Pet> Pets => _pets.Values;
+
+ /// <summary>
+ /// Gets the current skill of pet, if there is any.
+ /// </summary>
+ public Skill? PetSkill { get; internal set; }
+
+ /// <summary>
+ /// Get sthe current skills of partner(' sp).
+ /// </summary>
+ public IReadOnlyList<Skill>? PartnerSkills { get; internal set; }
+
+ /// <summary>
+ /// Gets the current pet of the client.
+ /// </summary>
+ public PartyPet? CurrentPet { get; internal set; }
+
+ /// <summary>
+ /// Gets the current partner of the client.
+ /// </summary>
+ public PartyPartner? CurrentPartner { get; internal set; }
+
+ /// <inheritdoc />
+ public IEnumerator<Mate> GetEnumerator()
+ => _partners.Values.Cast<Mate>().Concat(_pets.Values.Cast<Mate>()).GetEnumerator();
+
+ /// <inheritdoc/>
+ IEnumerator IEnumerable.GetEnumerator()
+ => GetEnumerator();
+
+ /// <summary>
+ /// Sets pet of the given id.
+ /// </summary>
+ /// <param name="pet">The pet.</param>
+ internal void SetPet(Pet pet)
+ {
+ _pets[pet.MateId] = pet;
+ }
+
+ /// <summary>
+ /// Sets partner of the given id.
+ /// </summary>
+ /// <param name="partner">The partner.</param>
+ internal void SetPartner(Partner partner)
+ {
+ _partners[partner.MateId] = partner;
+ }
+
+ /// <summary>
+ /// Clears partners and pets.
+ /// </summary>
+ internal void Clear()
+ {
+ _partners.Clear();
+ _pets.Clear();
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.Game/Data/Mates/Partner.cs => Core/NosSmooth.Game/Data/Mates/Partner.cs +47 -0
@@ 0,0 1,47 @@
+//
+// Partner.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.Game.Data.Items;
+using NosSmooth.Game.Data.Stats;
+using NosSmooth.Packets.Enums;
+
+namespace NosSmooth.Game.Data.Mates;
+
+public record Partner
+(
+ long MateId,
+ long NpcVNum,
+ long TransportId,
+ Level Level,
+ short Loyalty,
+ MateAttackStats Attack,
+ MateArmorStats Armor,
+ PartnerEquipment Equipment,
+ Element Element,
+ Resistance Resistance,
+ Health Hp,
+ Health Mp,
+ string Name,
+ int? MorphVNum,
+ bool IsSummonable,
+ PartnerSp? Sp
+) : Mate
+(
+ MateId,
+ NpcVNum,
+ TransportId,
+ Level,
+ Loyalty,
+ Attack,
+ Armor,
+ Element,
+ Resistance,
+ Hp,
+ Mp,
+ Name,
+ IsSummonable
+);<
\ No newline at end of file
A Core/NosSmooth.Game/Data/Mates/PartnerSkill.cs => Core/NosSmooth.Game/Data/Mates/PartnerSkill.cs +18 -0
@@ 0,0 1,18 @@
+//
+// PartnerSkill.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.Data.Abstractions.Infos;
+using NosSmooth.Game.Data.Characters;
+using NosSmooth.Packets.Enums.Mates;
+
+namespace NosSmooth.Game.Data.Mates;
+
+public record PartnerSkill
+(
+ int SkillVNum,
+ PartnerSkillRank? Rank,
+ ISkillInfo? Info
+) : Skill(SkillVNum, null, Info);<
\ No newline at end of file
A Core/NosSmooth.Game/Data/Mates/PartnerSp.cs => Core/NosSmooth.Game/Data/Mates/PartnerSp.cs +18 -0
@@ 0,0 1,18 @@
+//
+// PartnerSp.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.Characters;
+
+namespace NosSmooth.Game.Data.Mates;
+
+public record PartnerSp
+(
+ long VNum,
+ byte? AgilityPercentage,
+ PartnerSkill? Skill1,
+ PartnerSkill? Skill2,
+ PartnerSkill? Skill3
+);<
\ No newline at end of file
A Core/NosSmooth.Game/Data/Mates/PartyPartner.cs => Core/NosSmooth.Game/Data/Mates/PartyPartner.cs +25 -0
@@ 0,0 1,25 @@
+//
+// PartyPartner.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;
+
+namespace NosSmooth.Game.Data.Mates;
+
+public record PartyPartner
+(
+ Partner Partner
+)
+{
+ /// <summary>
+ /// Gets the hp of the partner.
+ /// </summary>
+ public Health? Hp { get; internal set; }
+
+ /// <summary>
+ /// Gets the mp of the partner.
+ /// </summary>
+ public Health? Mp { get; internal set; }
+}<
\ No newline at end of file
A Core/NosSmooth.Game/Data/Mates/PartyPet.cs => Core/NosSmooth.Game/Data/Mates/PartyPet.cs +25 -0
@@ 0,0 1,25 @@
+//
+// PartyPet.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;
+
+namespace NosSmooth.Game.Data.Mates;
+
+public record PartyPet
+(
+ Pet Pet
+)
+{
+ /// <summary>
+ /// Gets the hp of the partner.
+ /// </summary>
+ public Health? Hp { get; internal set; }
+
+ /// <summary>
+ /// Gets the mp of the partner.
+ /// </summary>
+ public Health? Mp { get; internal set; }
+}<
\ No newline at end of file
A Core/NosSmooth.Game/Data/Mates/Pet.cs => Core/NosSmooth.Game/Data/Mates/Pet.cs +46 -0
@@ 0,0 1,46 @@
+//
+// Pet.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.Game.Data.Items;
+using NosSmooth.Game.Data.Stats;
+using NosSmooth.Packets.Enums;
+using NosSmooth.PacketSerializer.Abstractions.Common;
+
+namespace NosSmooth.Game.Data.Mates;
+
+public record Pet
+(
+ long MateId,
+ long NpcVNum,
+ long TransportId,
+ Level Level,
+ short Loyalty,
+ MateAttackStats Attack,
+ MateArmorStats Armor,
+ Element Element,
+ Resistance Resistance,
+ Health Hp,
+ Health Mp,
+ string Name,
+ bool IsSummonable,
+ bool CanPickUp
+) : Mate
+(
+ MateId,
+ NpcVNum,
+ TransportId,
+ Level,
+ Loyalty,
+ Attack,
+ Armor,
+ Element,
+ Resistance,
+ Hp,
+ Mp,
+ Name,
+ IsSummonable
+);<
\ No newline at end of file
A Core/NosSmooth.Game/Data/Stats/MateArmorStats.cs => Core/NosSmooth.Game/Data/Stats/MateArmorStats.cs +17 -0
@@ 0,0 1,17 @@
+//
+// MateArmorStats.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.Stats;
+
+public record MateArmorStats
+(
+ short DefenceUpgrade,
+ int MeleeDefence,
+ int MeleeDefenceDodge,
+ int RangeDefence,
+ int RangeDodgeRate,
+ int MagicalDefence
+);<
\ No newline at end of file
A Core/NosSmooth.Game/Data/Stats/MateAttackStats.cs => Core/NosSmooth.Game/Data/Stats/MateAttackStats.cs +17 -0
@@ 0,0 1,17 @@
+//
+// MateAttackStats.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.Stats;
+
+public record MateAttackStats
+(
+ short AttackUpgrade,
+ int MinimumAttack,
+ int MaximumAttack,
+ int Precision,
+ int CriticalChance,
+ int CriticalRate
+);<
\ No newline at end of file
A Core/NosSmooth.Game/Data/Stats/Resistance.cs => Core/NosSmooth.Game/Data/Stats/Resistance.cs +15 -0
@@ 0,0 1,15 @@
+//
+// Resistance.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.Stats;
+
+public record Resistance
+(
+ short FireResistance,
+ short WaterResistance,
+ short LightResistance,
+ short DarkResistance
+);<
\ No newline at end of file
A Core/NosSmooth.Game/Events/Mates/MateStatEvent.cs => Core/NosSmooth.Game/Events/Mates/MateStatEvent.cs +13 -0
@@ 0,0 1,13 @@
+//
+// MateStatEvent.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.Game.Data.Mates;
+using NosSmooth.Game.Data.Social;
+
+namespace NosSmooth.Game.Events.Mates;
+
+public record MateStatEvent(Mate Mate, Health Hp, Health Mp) : IGameEvent;<
\ No newline at end of file
A Core/NosSmooth.Game/Events/Mates/MatesPartyInitializedEvent.cs => Core/NosSmooth.Game/Events/Mates/MatesPartyInitializedEvent.cs +12 -0
@@ 0,0 1,12 @@
+//
+// MatesPartyInitializedEvent.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.Mates;
+using NosSmooth.Game.Data.Social;
+
+namespace NosSmooth.Game.Events.Mates;
+
+public record MatesPartyInitializedEvent(Pet? Pet, Partner? Partner) : IGameEvent;<
\ No newline at end of file
A Core/NosSmooth.Game/Events/Mates/PartnerInitializedEvent.cs => Core/NosSmooth.Game/Events/Mates/PartnerInitializedEvent.cs +12 -0
@@ 0,0 1,12 @@
+//
+// PartnerInitializedEvent.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.Mates;
+using NosSmooth.Game.Data.Social;
+
+namespace NosSmooth.Game.Events.Mates;
+
+public record PartnerInitializedEvent(Partner Partner) : IGameEvent;<
\ No newline at end of file
A Core/NosSmooth.Game/Events/Mates/PartnerSkillsReceivedEvent.cs => Core/NosSmooth.Game/Events/Mates/PartnerSkillsReceivedEvent.cs +13 -0
@@ 0,0 1,13 @@
+//
+// PartnerSkillsReceivedEvent.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.Characters;
+using NosSmooth.Game.Data.Mates;
+using NosSmooth.Game.Data.Social;
+
+namespace NosSmooth.Game.Events.Mates;
+
+public record PartnerSkillsReceivedEvent(Partner? Partner, IReadOnlyList<Skill> Skills) : IGameEvent;<
\ No newline at end of file
A Core/NosSmooth.Game/Events/Mates/PetInitializedEvent.cs => Core/NosSmooth.Game/Events/Mates/PetInitializedEvent.cs +12 -0
@@ 0,0 1,12 @@
+//
+// PetInitializedEvent.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.Mates;
+using NosSmooth.Game.Data.Social;
+
+namespace NosSmooth.Game.Events.Mates;
+
+public record PetInitializedEvent(Pet Pet) : IGameEvent;<
\ No newline at end of file
A Core/NosSmooth.Game/Events/Mates/PetSkillReceivedEvent.cs => Core/NosSmooth.Game/Events/Mates/PetSkillReceivedEvent.cs +13 -0
@@ 0,0 1,13 @@
+//
+// PetSkillReceivedEvent.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.Characters;
+using NosSmooth.Game.Data.Mates;
+using NosSmooth.Game.Data.Social;
+
+namespace NosSmooth.Game.Events.Mates;
+
+public record PetSkillReceivedEvent(Pet? Pet, Skill? PetSkill) : IGameEvent;<
\ No newline at end of file
R Core/NosSmooth.Game/Data/Entities/IPet.cs => Core/NosSmooth.Game/Extensions/LivingEntityExtensions.cs +15 -4
@@ 1,14 1,25 @@
//
-// IPet.cs
+// LivingEntityExtensions.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.Entities;
+using NosSmooth.Game.Data.Entities;
+
+namespace NosSmooth.Game.Extensions;
/// <summary>
-/// Represents base type for a pet or a partner.
+/// An extension methods for <see cref="ILivingEntity"/>.
/// </summary>
-public interface IPet
+public static class LivingEntityExtensions
{
+ /// <summary>
+ /// Checks whether the entity is alive.
+ /// </summary>
+ /// <param name="entity">The entity to check.</param>
+ /// <returns>Whether the entity is alive.</returns>
+ public static bool IsAlive(ILivingEntity entity)
+ {
+ return entity.Hp is null || entity.Hp.Amount != 0 || entity.Hp.Percentage != 0;
+ }
}=
\ No newline at end of file
M Core/NosSmooth.Game/Extensions/ServiceCollectionExtensions.cs => Core/NosSmooth.Game/Extensions/ServiceCollectionExtensions.cs +3 -0
@@ 15,6 15,7 @@ using NosSmooth.Game.PacketHandlers.Entities;
using NosSmooth.Game.PacketHandlers.Inventory;
using NosSmooth.Game.PacketHandlers.Map;
using NosSmooth.Game.PacketHandlers.Relations;
+using NosSmooth.Game.PacketHandlers.Skills;
using NosSmooth.Game.PacketHandlers.Specialists;
namespace NosSmooth.Game.Extensions;
@@ 40,11 41,13 @@ public static class ServiceCollectionExtensions
serviceCollection
.AddPacketResponder<CharacterInitResponder>()
.AddPacketResponder<PlayerSkillResponder>()
+ .AddPacketResponder<MatesSkillResponder>()
.AddPacketResponder<WalkResponder>()
.AddPacketResponder<SkillUsedResponder>()
.AddPacketResponder<FriendInitResponder>()
.AddPacketResponder<InventoryInitResponder>()
.AddPacketResponder<GroupInitResponder>()
+ .AddPacketResponder<MatesInitResponder>()
.AddPacketResponder<AoeSkillUsedResponder>()
.AddPacketResponder<AtResponder>()
.AddPacketResponder<CMapResponder>()
M Core/NosSmooth.Game/Game.cs => Core/NosSmooth.Game/Game.cs +35 -0
@@ 6,10 6,12 @@
using Microsoft.Extensions.Options;
using NosSmooth.Core.Stateful;
+using NosSmooth.Game.Data;
using NosSmooth.Game.Data.Characters;
using NosSmooth.Game.Data.Chat;
using NosSmooth.Game.Data.Inventory;
using NosSmooth.Game.Data.Maps;
+using NosSmooth.Game.Data.Mates;
using NosSmooth.Game.Data.Raids;
using NosSmooth.Game.Data.Social;
@@ 44,6 46,11 @@ public class Game : IStatefulEntity
public Character? Character { get; internal set; }
/// <summary>
+ /// Gets the mates of the current character.
+ /// </summary>
+ public Mates? Mates { get; internal set; }
+
+ /// <summary>
/// Gets or sets the inventory of the character.
/// </summary>
public Inventory? Inventory { get; internal set; }
@@ 89,6 96,34 @@ public class Game : IStatefulEntity
public Raid? CurrentRaid { get; internal set; }
/// <summary>
+ /// Creates the mates if they are null, or updates the current mates.
+ /// </summary>
+ /// <param name="create">The function for creating the mates.</param>
+ /// <param name="update">The function for updating the mates.</param>
+ /// <param name="releaseSemaphore">Whether to release the semaphore used for changing the mates.</param>
+ /// <param name="ct">The cancellation token for cancelling the operation.</param>
+ /// <returns>The updated mates.</returns>
+ internal async Task<Mates?> CreateOrUpdateMatesAsync
+ (
+ Func<Mates?> create,
+ Func<Mates, Mates?> update,
+ bool releaseSemaphore = true,
+ CancellationToken ct = default
+ )
+ {
+ return await CreateOrUpdateAsync
+ (
+ GameSemaphoreType.Mates,
+ () => Mates,
+ s => Mates = s,
+ create,
+ update,
+ releaseSemaphore,
+ ct
+ );
+ }
+
+ /// <summary>
/// Creates the skills if they are null, or updates the current skills.
/// </summary>
/// <param name="create">The function for creating the skills.</param>
M Core/NosSmooth.Game/GameSemaphoreType.cs => Core/NosSmooth.Game/GameSemaphoreType.cs +6 -1
@@ 49,5 49,10 @@ public enum GameSemaphoreType
/// <summary>
/// The semaphore for raid.
/// </summary>
- Raid
+ Raid,
+
+ /// <summary>
+ /// The semaphore for mates.
+ /// </summary>
+ Mates
}=
\ No newline at end of file
A Core/NosSmooth.Game/PacketHandlers/Relations/MatesInitResponder.cs => Core/NosSmooth.Game/PacketHandlers/Relations/MatesInitResponder.cs +367 -0
@@ 0,0 1,367 @@
+//
+// MatesInitResponder.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.Characters;
+using NosSmooth.Game.Data.Info;
+using NosSmooth.Game.Data.Items;
+using NosSmooth.Game.Data.Mates;
+using NosSmooth.Game.Data.Social;
+using NosSmooth.Game.Data.Stats;
+using NosSmooth.Game.Events.Core;
+using NosSmooth.Game.Events.Mates;
+using NosSmooth.Packets.Enums.Entities;
+using NosSmooth.Packets.Enums.Mates;
+using NosSmooth.Packets.Server.Groups;
+using NosSmooth.Packets.Server.Mates;
+using NosSmooth.Packets.Server.Miniland;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Relations;
+
+/// <summary>
+/// A mates initialization responder.
+/// </summary>
+public class MatesInitResponder : IPacketResponder<ScPPacket>, IPacketResponder<ScNPacket>,
+ IPacketResponder<PinitPacket>, IPacketResponder<PstPacket>, IPacketResponder<PClearPacket>
+{
+ private readonly Game _game;
+ private readonly IInfoService _infoService;
+ private readonly EventDispatcher _eventDispatcher;
+ private readonly ILogger<MatesInitResponder> _logger;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MatesInitResponder"/> class.
+ /// </summary>
+ /// <param name="game">The game.</param>
+ /// <param name="infoService">The info service.</param>
+ /// <param name="eventDispatcher">The event dispatcher.</param>
+ /// <param name="logger">The logger.</param>
+ public MatesInitResponder
+ (
+ Game game,
+ IInfoService infoService,
+ EventDispatcher eventDispatcher,
+ ILogger<MatesInitResponder> logger
+ )
+ {
+ _game = game;
+ _infoService = infoService;
+ _eventDispatcher = eventDispatcher;
+ _logger = logger;
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(PacketEventArgs<ScPPacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+ var pet = new Pet
+ (
+ packet.PetId,
+ packet.NpcVNum,
+ packet.TransportId,
+ new Level(packet.Level, packet.Experience, packet.LevelExperience),
+ packet.Loyalty,
+ new MateAttackStats
+ (
+ packet.AttackUpgrade,
+ packet.MinimumAttack,
+ packet.MaximumAttack,
+ packet.Concentrate,
+ packet.CriticalChance,
+ packet.CriticalRate
+ ),
+ new MateArmorStats
+ (
+ packet.DefenceUpgrade,
+ packet.MeleeDefence,
+ packet.MeleeDefenceDodge,
+ packet.RangeDefence,
+ packet.RangeDodgeRate,
+ packet.MagicalDefence
+ ),
+ packet.Element,
+ new Resistance
+ (
+ packet.ResistanceSubPacket.FireResistance,
+ packet.ResistanceSubPacket.WaterResistance,
+ packet.ResistanceSubPacket.LightResistance,
+ packet.ResistanceSubPacket.DarkResistance
+ ),
+ new Health { Amount = packet.Hp, Maximum = packet.HpMax },
+ new Health { Amount = packet.Mp, Maximum = packet.MpMax },
+ packet.Name,
+ packet.IsSummonable,
+ packet.CanPickUp
+ );
+
+ await _game.CreateOrUpdateMatesAsync
+ (
+ () =>
+ {
+ var mates = new Mates();
+ mates.SetPet(pet);
+ return mates;
+ },
+ mates =>
+ {
+ mates.SetPet(pet);
+ return mates;
+ },
+ ct: ct
+ );
+
+ return await _eventDispatcher.DispatchEvent
+ (
+ new PetInitializedEvent(pet),
+ ct
+ );
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(PacketEventArgs<ScNPacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+ var partner = new Partner
+ (
+ packet.PartnerId,
+ packet.NpcVNum,
+ packet.TransportId,
+ new Level(packet.Level, packet.Experience, packet.LevelExperience),
+ packet.Loyalty,
+ new MateAttackStats
+ (
+ packet.AttackUpgrade,
+ packet.MinimumAttack,
+ packet.MaximumAttack,
+ packet.Precision,
+ packet.CriticalChance,
+ packet.CriticalRate
+ ),
+ new MateArmorStats
+ (
+ packet.DefenceUpgrade,
+ packet.MeleeDefence,
+ packet.MeleeDefenceDodge,
+ packet.RangeDefence,
+ packet.RangeDodgeRate,
+ packet.MagicalDefence
+ ),
+ new PartnerEquipment
+ (
+ await CreatePartnerItem(packet.WeaponSubPacket, ct),
+ await CreatePartnerItem(packet.ArmorSubPacket, ct),
+ await CreatePartnerItem(packet.GauntletSubPacket, ct),
+ await CreatePartnerItem(packet.BootsSubPacket, ct)
+ ),
+ packet.Element,
+ new Resistance
+ (
+ packet.ResistanceSubPacket.FireResistance,
+ packet.ResistanceSubPacket.WaterResistance,
+ packet.ResistanceSubPacket.LightResistance,
+ packet.ResistanceSubPacket.DarkResistance
+ ),
+ new Health { Amount = packet.Hp, Maximum = packet.HpMax },
+ new Health { Amount = packet.Mp, Maximum = packet.MpMax },
+ packet.Name,
+ packet.MorphVNum,
+ packet.IsSummonable,
+#pragma warning disable SA1118
+ packet.SpSubPacket?.ItemVNum is not null
+ ? new PartnerSp
+ (
+ packet.SpSubPacket.ItemVNum.Value,
+ packet.SpSubPacket.AgilityPercentage,
+ await CreateSkill(packet.Skill1SubPacket, ct),
+ await CreateSkill(packet.Skill2SubPacket, ct),
+ await CreateSkill(packet.Skill3SubPacket, ct)
+ )
+ : null
+#pragma warning restore SA1118
+ );
+
+ await _game.CreateOrUpdateMatesAsync
+ (
+ () =>
+ {
+ var mates = new Mates();
+ mates.SetPartner(partner);
+ return mates;
+ },
+ mates =>
+ {
+ mates.SetPartner(partner);
+ return mates;
+ },
+ ct: ct
+ );
+
+ return await _eventDispatcher.DispatchEvent
+ (
+ new PartnerInitializedEvent(partner),
+ ct
+ );
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(PacketEventArgs<PinitPacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+ var partner = packet.PinitSubPackets?.FirstOrDefault(x => x.MateSubPacket?.MateType == MateType.Partner);
+ var pet = packet.PinitSubPackets?.FirstOrDefault(x => x.MateSubPacket?.MateType == MateType.Pet);
+
+ Partner? gamePartner = null;
+ Pet? gamePet = null;
+
+ await _game.CreateOrUpdateMatesAsync
+ (
+ () => null,
+ m =>
+ {
+ if (partner is not null)
+ {
+ gamePartner = _game.Mates?.Partners.FirstOrDefault(x => x.MateId == partner.EntityId);
+ if (gamePartner is not null && gamePartner != m.CurrentPartner?.Partner)
+ {
+ m.CurrentPartner = new PartyPartner(gamePartner);
+ }
+ }
+
+ if (pet is not null)
+ {
+ gamePet = _game.Mates?.Pets.FirstOrDefault(x => x.MateId == pet.EntityId);
+ if (gamePet is not null && gamePet != m.CurrentPet?.Pet)
+ {
+ m.CurrentPet = new PartyPet(gamePet);
+ }
+ }
+
+ return m;
+ },
+ ct: ct
+ );
+
+ return await _eventDispatcher.DispatchEvent
+ (
+ new MatesPartyInitializedEvent(gamePet, gamePartner),
+ ct
+ );
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(PacketEventArgs<PstPacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+ if (packet.EntityType != EntityType.Npc || packet.MateType is null)
+ {
+ return Result.FromSuccess();
+ }
+
+ Mate? mate = null;
+ var hp = new Health { Amount = packet.Hp, Percentage = packet.HpPercentage };
+ var mp = new Health { Amount = packet.Mp, Percentage = packet.MpPercentage };
+
+ await _game.CreateOrUpdateMatesAsync
+ (
+ () => null,
+ m =>
+ {
+ if (packet.MateType is MateType.Pet && m.CurrentPet is not null)
+ {
+ mate = m.CurrentPet.Pet;
+ m.CurrentPet.Hp = hp;
+ m.CurrentPet.Mp = mp;
+ }
+ else if (packet.MateType is MateType.Partner && m.CurrentPartner is not null)
+ {
+ mate = m.CurrentPartner.Partner;
+ m.CurrentPartner.Hp = hp;
+ m.CurrentPartner.Mp = mp;
+ }
+
+ return m;
+ },
+ ct: ct
+ );
+
+ if (mate is not null)
+ {
+ return await _eventDispatcher.DispatchEvent(new MateStatEvent(mate, hp, mp), ct);
+ }
+
+ return Result.FromSuccess();
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(PacketEventArgs<PClearPacket> packetArgs, CancellationToken ct = default)
+ {
+ await _game.CreateOrUpdateMatesAsync
+ (
+ () => null,
+ m =>
+ {
+ m.Clear();
+ return m;
+ },
+ ct: ct
+ );
+
+ return Result.FromSuccess();
+ }
+
+ private async Task<UpgradeableItem?> CreatePartnerItem(ScNEquipmentSubPacket? packet, CancellationToken ct)
+ {
+ if (packet is null || packet.ItemVNum is null)
+ {
+ return null;
+ }
+
+ var itemInfoResult = await _infoService.GetItemInfoAsync(packet.ItemVNum.Value, ct);
+ if (!itemInfoResult.IsDefined(out var itemInfo))
+ {
+ _logger.LogWarning
+ (
+ "Could not obtain an item info for vnum {vnum}: {error}",
+ packet.ItemVNum.Value,
+ itemInfoResult.ToFullString()
+ );
+ }
+
+ return new UpgradeableItem
+ (
+ packet.ItemVNum.Value,
+ itemInfo,
+ packet.ItemUpgrade,
+ packet.ItemRare,
+ 0
+ );
+ }
+
+ private async Task<PartnerSkill?> CreateSkill(ScNSkillSubPacket? packet, CancellationToken ct)
+ {
+ if (packet is null || packet.SkillVNum is null)
+ {
+ return null;
+ }
+
+ var skillInfoResult = await _infoService.GetSkillInfoAsync(packet.SkillVNum.Value, ct);
+ if (!skillInfoResult.IsDefined(out var skillInfo))
+ {
+ _logger.LogWarning
+ (
+ "Could not obtain a skill info for vnum {vnum}: {error}",
+ packet.SkillVNum.Value,
+ skillInfoResult.ToFullString()
+ );
+ }
+
+ return new PartnerSkill(packet.SkillVNum.Value, packet.Rank, skillInfo);
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.Game/PacketHandlers/Skills/MatesSkillResponder.cs => Core/NosSmooth.Game/PacketHandlers/Skills/MatesSkillResponder.cs +145 -0
@@ 0,0 1,145 @@
+//
+// MatesSkillResponder.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.Characters;
+using NosSmooth.Game.Data.Mates;
+using NosSmooth.Game.Events.Core;
+using NosSmooth.Game.Events.Mates;
+using NosSmooth.Packets.Server.Skills;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Skills;
+
+/// <summary>
+/// Responds to petski and pski packets.
+/// </summary>
+public class MatesSkillResponder : IPacketResponder<PetskiPacket>, IPacketResponder<PSkiPacket>
+{
+ private readonly Game _game;
+ private readonly EventDispatcher _eventDispatcher;
+ private readonly IInfoService _infoService;
+ private readonly ILogger<MatesSkillResponder> _logger;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MatesSkillResponder"/> class.
+ /// </summary>
+ /// <param name="game">The game.</param>
+ /// <param name="eventDispatcher">The event dispatcher.</param>
+ /// <param name="infoService">The info service.</param>
+ /// <param name="logger">The logger.</param>
+ public MatesSkillResponder
+ (
+ Game game,
+ EventDispatcher eventDispatcher,
+ IInfoService infoService,
+ ILogger<MatesSkillResponder> logger
+ )
+ {
+ _game = game;
+ _eventDispatcher = eventDispatcher;
+ _infoService = infoService;
+ _logger = logger;
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(PacketEventArgs<PetskiPacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+ Skill? skill = null;
+ if (packet.SkillVNum is not null)
+ {
+ var skillInfoResult = await _infoService.GetSkillInfoAsync(packet.SkillVNum.Value, ct);
+ if (!skillInfoResult.IsDefined(out var skillInfo))
+ {
+ _logger.LogWarning
+ (
+ "Could not obtain skill info for vnum {vnum}: {error}",
+ packet.SkillVNum.Value,
+ skillInfoResult.ToFullString()
+ );
+ }
+
+ skill = new Skill(packet.SkillVNum.Value, null, skillInfo);
+ }
+
+ var mates = await _game.CreateOrUpdateMatesAsync
+ (
+ () => new Mates
+ {
+ PetSkill = skill
+ },
+ m =>
+ {
+ m.PetSkill = skill;
+ return m;
+ },
+ ct: ct
+ );
+
+ return await _eventDispatcher.DispatchEvent
+ (
+ new PetSkillReceivedEvent(mates?.CurrentPet?.Pet, skill),
+ ct
+ );
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(PacketEventArgs<PSkiPacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+ var skills = new List<Skill>();
+
+ foreach (var skillVNum in packet.SkillVNums)
+ {
+ if (skillVNum is null)
+ {
+ continue;
+ }
+
+ var skillInfoResult = await _infoService.GetSkillInfoAsync(skillVNum.Value, ct);
+ if (!skillInfoResult.IsDefined(out var skillInfo))
+ {
+ _logger.LogWarning
+ (
+ "Could not obtain skill info for vnum {vnum}: {error}",
+ skillVNum,
+ skillInfoResult.ToFullString()
+ );
+ }
+
+ skills.Add(new Skill(skillVNum.Value, null, skillInfo));
+ }
+
+ if (skills.Count == 0)
+ {
+ skills = null;
+ }
+
+ var mates = await _game.CreateOrUpdateMatesAsync
+ (
+ () => new Mates
+ {
+ PartnerSkills = skills
+ },
+ m =>
+ {
+ m.PartnerSkills = skills;
+ return m;
+ },
+ ct: ct
+ );
+
+ return await _eventDispatcher.DispatchEvent
+ (
+ new PartnerSkillsReceivedEvent(mates?.CurrentPartner?.Partner, skills ?? Array.Empty<Skill>().ToList()),
+ ct
+ );
+ }
+}<
\ No newline at end of file
M Core/NosSmooth.Game/PacketHandlers/Skills/PlayerSkillResponder.cs => Core/NosSmooth.Game/PacketHandlers/Skills/PlayerSkillResponder.cs +2 -2
@@ 14,7 14,7 @@ using NosSmooth.Game.Events.Core;
using NosSmooth.Packets.Server.Skills;
using Remora.Results;
-namespace NosSmooth.Game.PacketHandlers.Characters;
+namespace NosSmooth.Game.PacketHandlers.Skills;
/// <summary>
/// Responds to SkiPacket to add skill to the character.
@@ 94,7 94,7 @@ public class PlayerSkillResponder : IPacketResponder<SkiPacket>
otherSkillsFromCharacter.Add(await CreateSkill(newSkill, default));
}
- skills = new Skills(primarySkill, secondarySkill, otherSkillsFromCharacter);
+ skills = new Data.Characters.Skills(primarySkill, secondarySkill, otherSkillsFromCharacter);
await _game.CreateOrUpdateSkillsAsync
(
A Packets/NosSmooth.Packets/Client/Battle/UsePetSkillPacket.cs => Packets/NosSmooth.Packets/Client/Battle/UsePetSkillPacket.cs +37 -0
@@ 0,0 1,37 @@
+//
+// UsePetSkillPacket.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.Packets.Enums.Entities;
+using NosSmooth.PacketSerializer.Abstractions.Attributes;
+
+namespace NosSmooth.Packets.Client.Battle;
+
+/// <summary>
+/// Sent to use a pet skill.
+/// </summary>
+/// <param name="MateTransportId">The pet skill id.</param>
+/// <param name="TargetEntityType">The target entity type.</param>
+/// <param name="TargetId">The target id.</param>
+/// <param name="Unknown">Unknown, seems to always be 1.</param>
+/// <param name="Unknown1">Unknown, 6 for Otter.</param>
+/// <param name="Unknown2">Unknown, 9 for Otter.</param>
+[PacketHeader("u_pet", PacketSource.Client)]
+[GenerateSerializer(true)]
+public record UsePetSkillPacket
+(
+ [PacketIndex(0)]
+ long MateTransportId,
+ [PacketIndex(1)]
+ EntityType TargetEntityType,
+ [PacketIndex(2)]
+ long TargetId,
+ [PacketIndex(3)]
+ byte Unknown,
+ [PacketIndex(4)]
+ byte Unknown1,
+ [PacketIndex(5)]
+ byte Unknown2
+) : IPacket;<
\ No newline at end of file
M Packets/NosSmooth.Packets/Server/Entities/EffectsSubPacket.cs => Packets/NosSmooth.Packets/Server/Entities/EffectsSubPacket.cs +1 -1
@@ 18,7 18,7 @@ namespace NosSmooth.Packets.Server.Entities;
public record EffectsSubPacket
(
[PacketIndex(0)]
- long CardId,
+ short CardId,
[PacketIndex(1, IsOptional = true)]
short? Level
) : IPacket;=
\ No newline at end of file
M Packets/NosSmooth.Packets/Server/Mates/ScNEquipmentSubPacket.cs => Packets/NosSmooth.Packets/Server/Mates/ScNEquipmentSubPacket.cs +3 -3
@@ 21,9 21,9 @@ namespace NosSmooth.Packets.Server.Mates;
public record ScNEquipmentSubPacket
(
[PacketIndex(0)]
- long? ItemVNum,
+ int? ItemVNum,
[PacketIndex(1, IsOptional = true)]
- long? ItemRare,
+ sbyte? ItemRare,
[PacketIndex(2, IsOptional = true)]
- long? ItemUpgrade
+ byte? ItemUpgrade
) : IPacket;=
\ No newline at end of file
M Packets/NosSmooth.Packets/Server/Mates/ScNPacket.cs => Packets/NosSmooth.Packets/Server/Mates/ScNPacket.cs +1 -1
@@ 75,7 75,7 @@ public record ScNPacket
[PacketIndex(6, InnerSeparator = '.')]
ScNEquipmentSubPacket? WeaponSubPacket,
[PacketIndex(7, InnerSeparator = '.')]
- ScNEquipmentSubPacket? ArmodSubPacket,
+ ScNEquipmentSubPacket? ArmorSubPacket,
[PacketIndex(8, InnerSeparator = '.')]
ScNEquipmentSubPacket? GauntletSubPacket,
[PacketIndex(9, InnerSeparator = '.')]
M Packets/NosSmooth.Packets/Server/Mates/ScNSkillSubPacket.cs => Packets/NosSmooth.Packets/Server/Mates/ScNSkillSubPacket.cs +1 -1
@@ 21,7 21,7 @@ namespace NosSmooth.Packets.Server.Mates;
public record ScNSkillSubPacket
(
[PacketIndex(0)]
- long? SkillVNum,
+ int? SkillVNum,
[PacketIndex(1, IsOptional = true)]
PartnerSkillRank? Rank
) : IPacket;=
\ No newline at end of file
A Samples/FileClient/Responders/InventoryInitializedResponder.cs => Samples/FileClient/Responders/InventoryInitializedResponder.cs +52 -0
@@ 0,0 1,52 @@
+//
+// InventoryInitializedResponder.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.Data.Abstractions.Enums;
+using NosSmooth.Data.Abstractions.Language;
+using NosSmooth.Game.Data.Items;
+using NosSmooth.Game.Events.Core;
+using NosSmooth.Game.Events.Inventory;
+using NosSmooth.Game.Extensions;
+using Remora.Results;
+
+namespace FileClient.Responders;
+
+/// <inheritdoc />
+public class InventoryInitializedResponder : IGameResponder<InventoryInitializedEvent>
+{
+ private readonly ILanguageService _languageService;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="InventoryInitializedResponder"/> class.
+ /// </summary>
+ /// <param name="languageService">The langauge service.</param>
+ public InventoryInitializedResponder(ILanguageService languageService)
+ {
+ _languageService = languageService;
+
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(InventoryInitializedEvent gameEvent, CancellationToken ct = default)
+ {
+ foreach (var bag in gameEvent.Inventory)
+ {
+ foreach (var slot in bag)
+ {
+ var item = slot.Item;
+ if (item?.Info is not null && bag.Type != item.Info.BagType)
+ {
+ var translatedResult = await _languageService.GetTranslationAsync(item.Info.Name, Language.Cz, ct);
+ var entity = translatedResult.Entity;
+
+ Console.WriteLine(entity + $", {item.ItemVNum} is: {bag.Type}, should be: {item.Info.BagType.Convert()}");
+ }
+ }
+ }
+
+ return Result.FromSuccess();
+ }
+}<
\ No newline at end of file