~ruther/NosSmooth

c8bcfd67bd133d2c3ce303c1ffd27dac3ead05b1 — František Boháček 2 years ago b54039f
feat(game): add mates processing
34 files changed, 1110 insertions(+), 38 deletions(-)

M Core/NosSmooth.Game/Data/Entities/GroundItem.cs
D Core/NosSmooth.Game/Data/Entities/Partner.cs
D Core/NosSmooth.Game/Data/Entities/Pet.cs
A Core/NosSmooth.Game/Data/Items/PartnerEquipment.cs
A Core/NosSmooth.Game/Data/Mates/Mate.cs
A Core/NosSmooth.Game/Data/Mates/Mates.cs
A Core/NosSmooth.Game/Data/Mates/Partner.cs
A Core/NosSmooth.Game/Data/Mates/PartnerSkill.cs
A Core/NosSmooth.Game/Data/Mates/PartnerSp.cs
A Core/NosSmooth.Game/Data/Mates/PartyPartner.cs
A Core/NosSmooth.Game/Data/Mates/PartyPet.cs
A Core/NosSmooth.Game/Data/Mates/Pet.cs
A Core/NosSmooth.Game/Data/Stats/MateArmorStats.cs
A Core/NosSmooth.Game/Data/Stats/MateAttackStats.cs
A Core/NosSmooth.Game/Data/Stats/Resistance.cs
A Core/NosSmooth.Game/Events/Mates/MateStatEvent.cs
A Core/NosSmooth.Game/Events/Mates/MatesPartyInitializedEvent.cs
A Core/NosSmooth.Game/Events/Mates/PartnerInitializedEvent.cs
A Core/NosSmooth.Game/Events/Mates/PartnerSkillsReceivedEvent.cs
A Core/NosSmooth.Game/Events/Mates/PetInitializedEvent.cs
A Core/NosSmooth.Game/Events/Mates/PetSkillReceivedEvent.cs
R Core/NosSmooth.Game/{Data/Entities/IPet.cs => Extensions/LivingEntityExtensions.cs}
M Core/NosSmooth.Game/Extensions/ServiceCollectionExtensions.cs
M Core/NosSmooth.Game/Game.cs
M Core/NosSmooth.Game/GameSemaphoreType.cs
A Core/NosSmooth.Game/PacketHandlers/Relations/MatesInitResponder.cs
A Core/NosSmooth.Game/PacketHandlers/Skills/MatesSkillResponder.cs
M Core/NosSmooth.Game/PacketHandlers/Skills/PlayerSkillResponder.cs
A Packets/NosSmooth.Packets/Client/Battle/UsePetSkillPacket.cs
M Packets/NosSmooth.Packets/Server/Entities/EffectsSubPacket.cs
M Packets/NosSmooth.Packets/Server/Mates/ScNEquipmentSubPacket.cs
M Packets/NosSmooth.Packets/Server/Mates/ScNPacket.cs
M Packets/NosSmooth.Packets/Server/Mates/ScNSkillSubPacket.cs
A Samples/FileClient/Responders/InventoryInitializedResponder.cs
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

Do not follow this link