~ruther/NosSmooth

8ab46d37e63a6537d6a4f3ccd429a84a97cc9aaa — František Boháček 2 years ago 5b571cb
feat(game): put friends, group, inventory, skills in Game instead of Character
M Core/NosSmooth.Game/Data/Characters/Character.cs => Core/NosSmooth.Game/Data/Characters/Character.cs +0 -20
@@ 17,26 17,6 @@ namespace NosSmooth.Game.Data.Characters;
public class Character : Player
{
    /// <summary>
    /// Gets or sets the inventory of the character.
    /// </summary>
    public Inventory.Inventory? Inventory { get; set; }

    /// <summary>
    /// Get or sets the friends of the character.
    /// </summary>
    public IReadOnlyList<Friend>? Friends { get; set; }

    /// <summary>
    /// Gets or sets the skills of the player.
    /// </summary>
    public Skills? Skills { get; set; }

    /// <summary>
    /// Gets or sets the group the player is in.
    /// </summary>
    public Group? Group { get; set; }

    /// <summary>
    /// Gets or sets the c skill points.
    /// </summary>
    public int? SkillCp { get; set; }

M Core/NosSmooth.Game/Game.cs => Core/NosSmooth.Game/Game.cs +157 -4
@@ 7,8 7,11 @@
using Microsoft.Extensions.Options;
using NosSmooth.Core.Stateful;
using NosSmooth.Game.Data.Characters;
using NosSmooth.Game.Data.Chat;
using NosSmooth.Game.Data.Inventory;
using NosSmooth.Game.Data.Maps;
using NosSmooth.Game.Data.Raids;
using NosSmooth.Game.Data.Social;

namespace NosSmooth.Game;



@@ 41,6 44,31 @@ public class Game : IStatefulEntity
    public Character? Character { get; internal set; }

    /// <summary>
    /// Gets or sets the inventory of the character.
    /// </summary>
    public Inventory? Inventory { get; internal set; }

    /// <summary>
    /// Get or sets the friends of the character.
    /// </summary>
    public IReadOnlyList<Friend>? Friends { get; internal set; }

    /// <summary>
    /// Gets or sets the skills of the player.
    /// </summary>
    public Skills? Skills { get; internal set; }

    /// <summary>
    /// Gets or sets the family.
    /// </summary>
    public Family? Family { get; internal set; }

    /// <summary>
    /// Gets or sets the group the player is in.
    /// </summary>
    public Group? Group { get; internal set; }

    /// <summary>
    /// Gets the current map of the client.
    /// </summary>
    /// <remarks>


@@ 49,10 77,7 @@ public class Game : IStatefulEntity
    public Map? CurrentMap
    {
        get => _currentMap;
        internal set
        {
            _currentMap = value;
        }
        internal set { _currentMap = value; }
    }

    /// <summary>


@@ 64,6 89,134 @@ public class Game : IStatefulEntity
    public Raid? CurrentRaid { get; internal set; }

    /// <summary>
    /// Creates the skills if they are null, or updates the current skills.
    /// </summary>
    /// <param name="create">The function for creating the skills.</param>
    /// <param name="update">The function for updating the skills.</param>
    /// <param name="releaseSemaphore">Whether to release the semaphore used for changing the skills.</param>
    /// <param name="ct">The cancellation token for cancelling the operation.</param>
    /// <returns>The updated skills.</returns>
    internal async Task<Skills> CreateOrUpdateSkillsAsync
    (
        Func<Skills> create,
        Func<Skills, Skills> update,
        bool releaseSemaphore = true,
        CancellationToken ct = default
    )
    {
        return await CreateOrUpdateAsync
        (
            GameSemaphoreType.Skills,
            () => Skills,
            s => Skills = s,
            create,
            update,
            releaseSemaphore,
            ct
        );
    }

    /// <summary>
    /// Creates the inventory if it is null, or updates the current inventory.
    /// </summary>
    /// <param name="create">The function for creating the inventory.</param>
    /// <param name="update">The function for updating the inventory.</param>
    /// <param name="releaseSemaphore">Whether to release the semaphore used for changing the inventory.</param>
    /// <param name="ct">The cancellation token for cancelling the operation.</param>
    /// <returns>The updated inventory.</returns>
    internal async Task<Inventory> CreateOrUpdateInventoryAsync
    (
        Func<Inventory> create,
        Func<Inventory, Inventory> update,
        bool releaseSemaphore = true,
        CancellationToken ct = default
    )
    {
        return await CreateOrUpdateAsync
        (
            GameSemaphoreType.Inventory,
            () => Inventory,
            i => Inventory = i,
            create,
            update,
            releaseSemaphore,
            ct
        );
    }

    /// <summary>
    /// Creates the family if it is null, or updates the current family.
    /// </summary>
    /// <param name="create">The function for creating the family.</param>
    /// <param name="update">The function for updating the family.</param>
    /// <param name="releaseSemaphore">Whether to release the semaphore used for changing the family.</param>
    /// <param name="ct">The cancellation token for cancelling the operation.</param>
    /// <returns>The updated family.</returns>
    internal async Task<Family> CreateOrUpdateFamilyAsync
    (
        Func<Family> create,
        Func<Family, Family> update,
        bool releaseSemaphore = true,
        CancellationToken ct = default
    )
    {
        var family = await CreateOrUpdateAsync
        (
            GameSemaphoreType.Family,
            () => Family,
            c => Family = c,
            create,
            update,
            releaseSemaphore,
            ct
        );

        await CreateOrUpdateCharacterAsync
        (
            () => new Character
            {
                Family = family
            },
            c =>
            {
                c.Family = family;
                return c;
            },
            ct: ct
        );

        return family;
    }

    /// <summary>
    /// Creates the group if it is null, or updates the current group.
    /// </summary>
    /// <param name="create">The function for creating the group.</param>
    /// <param name="update">The function for updating the group.</param>
    /// <param name="releaseSemaphore">Whether to release the semaphore used for changing the group.</param>
    /// <param name="ct">The cancellation token for cancelling the operation.</param>
    /// <returns>The updated group.</returns>
    internal async Task<Group> CreateOrUpdateGroupAsync
    (
        Func<Group> create,
        Func<Group, Group> update,
        bool releaseSemaphore = true,
        CancellationToken ct = default
    )
    {
        return await CreateOrUpdateAsync
        (
            GameSemaphoreType.Group,
            () => Group,
            c => Group = c,
            create,
            update,
            releaseSemaphore,
            ct
        );
    }

    /// <summary>
    /// Creates the character if it is null, or updates the current character.
    /// </summary>
    /// <param name="create">The function for creating the character.</param>

M Core/NosSmooth.Game/GameSemaphoreType.cs => Core/NosSmooth.Game/GameSemaphoreType.cs +28 -3
@@ 12,17 12,42 @@ namespace NosSmooth.Game;
public enum GameSemaphoreType
{
    /// <summary>
    /// The semaphore for the character.
    /// The semaphore for character.
    /// </summary>
    Character,

    /// <summary>
    /// The semaphore for the map.
    /// The semaphore for inventory.
    /// </summary>
    Inventory,

    /// <summary>
    /// The semaphore for friends.
    /// </summary>
    Friends,

    /// <summary>
    /// The semaphore for family.
    /// </summary>
    Family,

    /// <summary>
    /// The semaphore for group.
    /// </summary>
    Group,

    /// <summary>
    /// The semaphore for skills.
    /// </summary>
    Skills,

    /// <summary>
    /// The semaphore for map.
    /// </summary>
    Map,

    /// <summary>
    /// The semaphore for the raid.
    /// The semaphore for raid.
    /// </summary>
    Raid
}
\ No newline at end of file

M Core/NosSmooth.Game/NosSmooth.Game.csproj => Core/NosSmooth.Game/NosSmooth.Game.csproj +0 -1
@@ 18,7 18,6 @@ Create Character in At responder if needed.</PackageReleaseNotes>

    <ItemGroup>
      <Folder Include="Apis" />
      <Folder Include="Events\Players" />
    </ItemGroup>

    <ItemGroup>

M Core/NosSmooth.Game/PacketHandlers/Characters/CharacterInitResponder.cs => Core/NosSmooth.Game/PacketHandlers/Characters/CharacterInitResponder.cs +29 -7
@@ 44,8 44,6 @@ public class CharacterInitResponder : IPacketResponder<CInfoPacket>, IPacketResp
        (
            () => new Character
            {
                Family = new Family(packet.FamilyId, null, packet.FamilyName, packet.FamilyLevel, null),
                Group = new Group(packet.GroupId, default, default),
                Id = packet.CharacterId,
                Name = packet.Name,
                Authority = packet.Authority,


@@ 69,22 67,46 @@ public class CharacterInitResponder : IPacketResponder<CInfoPacket>, IPacketResp
                character.Class = packet.Class;
                character.Icon = packet.Icon;
                character.Compliment = packet.Compliment;
                character.Group = (character.Group ?? new Group(packet.GroupId, null, null)) with
                {
                    Id = packet.GroupId
                };
                character.Morph = (character.Morph ?? new Morph(packet.MorphVNum, packet.MorphUpgrade)) with
                {
                    VNum = packet.MorphVNum, Upgrade = packet.MorphUpgrade
                };
                character.ArenaWinner = packet.ArenaWinner;
                character.IsInvisible = packet.IsInvisible;
                character.Family = new Family(packet.FamilyId, null, packet.FamilyName, packet.FamilyLevel, null);
                return character;
            },
            ct: ct
        );

        await _game.CreateOrUpdateGroupAsync
        (
            () => new Group(packet.GroupId, null, null),
            g => g with
            {
                Id = packet.GroupId
            },
            ct: ct
        );

        await _game.CreateOrUpdateFamilyAsync
        (
            () => new Family
            (
                packet.FamilyId,
                null,
                packet.FamilyName,
                packet.FamilyLevel,
                null
            ),
            f => f with
            {
                Id = packet.FamilyId,
                Name = packet.FamilyName,
                Level = packet.FamilyLevel
            },
            ct: ct
        );

        if (character != oldCharacter)
        {
            return await _eventDispatcher.DispatchEvent(new ReceivedCharacterDataEvent(character), ct);

M Core/NosSmooth.Game/PacketHandlers/Characters/SkillResponder.cs => Core/NosSmooth.Game/PacketHandlers/Characters/SkillResponder.cs +13 -17
@@ 55,24 55,24 @@ public class SkillResponder : IPacketResponder<SkiPacket>

        Skill primarySkill, secondarySkill;

        var character = _game.Character;
        var skills = _game.Skills;

        if (character is not null && packet.PrimarySkillVNum == character.Skills?.PrimarySkill.SkillVNum)
        if (packet.PrimarySkillVNum == skills?.PrimarySkill.SkillVNum)
        {
            primarySkill = character.Skills.PrimarySkill;
            primarySkill = skills.PrimarySkill;
        }
        else
        {
            primarySkill = await CreateSkill(packet.PrimarySkillVNum, default);
        }

        if (character is not null && packet.PrimarySkillVNum == packet.SecondarySkillVNum)
        if (packet.PrimarySkillVNum == packet.SecondarySkillVNum)
        {
            secondarySkill = primarySkill;
        }
        else if (character is not null && packet.SecondarySkillVNum == character.Skills?.SecondarySkill.SkillVNum)
        else if (packet.SecondarySkillVNum == skills?.SecondarySkill.SkillVNum)
        {
            secondarySkill = character.Skills.SecondarySkill;
            secondarySkill = skills.SecondarySkill;
        }
        else
        {


@@ 80,13 80,13 @@ public class SkillResponder : IPacketResponder<SkiPacket>
        }

        var skillsFromPacket = packet.SkillSubPackets?.Select(x => x.SkillVNum).ToList() ?? new List<int>();
        var skillsFromCharacter = character?.Skills is null
        var skillsFromCharacter = skills is null
            ? new List<int>()
            : character.Skills.OtherSkills.Select(x => x.SkillVNum).ToList();
            : skills.OtherSkills.Select(x => x.SkillVNum).ToList();
        var newSkills = skillsFromPacket.Except(skillsFromCharacter);
        var oldSkills = skillsFromCharacter.Except(skillsFromPacket);

        var otherSkillsFromCharacter = new List<Skill>(character?.Skills?.OtherSkills ?? new Skill[] { });
        var otherSkillsFromCharacter = new List<Skill>(skills?.OtherSkills ?? new Skill[] { });
        otherSkillsFromCharacter.RemoveAll(x => oldSkills.Contains(x.SkillVNum));

        foreach (var newSkill in newSkills)


@@ 94,16 94,12 @@ public class SkillResponder : IPacketResponder<SkiPacket>
            otherSkillsFromCharacter.Add(await CreateSkill(newSkill, default));
        }

        var skills = new Skills(primarySkill, secondarySkill, otherSkillsFromCharacter);
        skills = new Skills(primarySkill, secondarySkill, otherSkillsFromCharacter);

        await _game.CreateOrUpdateCharacterAsync
        await _game.CreateOrUpdateSkillsAsync
        (
            () => new Character { Skills = skills },
            c =>
            {
                c.Skills = skills;
                return c;
            },
            () => skills,
            _ => skills,
            ct: ct
        );


M Core/NosSmooth.Game/PacketHandlers/Entities/AoeSkillUsedResponder.cs => Core/NosSmooth.Game/PacketHandlers/Entities/AoeSkillUsedResponder.cs +1 -1
@@ 63,7 63,7 @@ public class AoeSkillUsedResponder : IPacketResponder<BsPacket>

        if (caster is Character character)
        {
            var skillResult = character.Skills?.TryGetSkillByVNum(packet.SkillVNum);
            var skillResult = _game.Skills?.TryGetSkillByVNum(packet.SkillVNum);
            if (skillResult?.IsSuccess ?? false)
            {
                skillEntity = skillResult.Value.Entity;

M Core/NosSmooth.Game/PacketHandlers/Entities/SkillUsedResponder.cs => Core/NosSmooth.Game/PacketHandlers/Entities/SkillUsedResponder.cs +9 -6
@@ 82,9 82,10 @@ public class SkillUsedResponder : IPacketResponder<SuPacket>, IPacketResponder<S
        }

        Skill? skillEntity;
        if (packet.SkillVNum is not null && caster is Character character && character.Skills is not null)
        var skills = _game.Skills;
        if (packet.SkillVNum is not null && caster is Character character && skills is not null)
        {
            var skillResult = character.Skills.TryGetSkillByVNum(packet.SkillVNum.Value);
            var skillResult = skills.TryGetSkillByVNum(packet.SkillVNum.Value);

            if (skillResult.IsDefined(out skillEntity))
            {


@@ 159,17 160,19 @@ public class SkillUsedResponder : IPacketResponder<SuPacket>, IPacketResponder<S
    public async Task<Result> Respond(PacketEventArgs<SrPacket> packetArgs, CancellationToken ct = default)
    {
        var packet = packetArgs.Packet;
        var character = _game.Character;

        if (character is not null && character.Skills is not null)
        var skills = _game.Skills;
        if (skills is not null)
        {
            var skillResult = character.Skills.TryGetSkillByCastId(packet.SkillId);
            var skillResult = skills.TryGetSkillByCastId(packet.SkillId);

            if (skillResult.IsDefined(out var skillEntity))
            {
                skillEntity.IsOnCooldown = false;
                await _eventDispatcher.DispatchEvent(new SkillReadyEvent(skillEntity, skillEntity.SkillVNum), ct);
            }

            await _eventDispatcher.DispatchEvent
                (new SkillReadyEvent(skillEntity, skillEntity?.SkillVNum ?? packet.SkillId), ct);
        }
        else
        {

M Extensions/NosSmooth.Extensions.Combat/Operations/UsePrimarySkillOperation.cs => Extensions/NosSmooth.Extensions.Combat/Operations/UsePrimarySkillOperation.cs +1 -6
@@ 65,13 65,8 @@ public record UsePrimarySkillOperation(ILivingEntity Target) : ICombatOperation

    private Result<Skill> GetPrimarySkill(ICombatState combatState)
    {
        var character = combatState.Game.Character;
        if (character is null)
        {
            return new CharacterNotInitializedError();
        }
        var skills = combatState.Game.Skills;

        var skills = character.Skills;
        if (skills is null)
        {
            return new CharacterNotInitializedError("Skills");

M Extensions/NosSmooth.Extensions.Combat/Techniques/SimpleAttackTechnique.cs => Extensions/NosSmooth.Extensions.Combat/Techniques/SimpleAttackTechnique.cs +1 -1
@@ 87,7 87,7 @@ public class SimpleAttackTechnique : ICombatTechnique

        if (_currentSkill is null)
        {
            var skills = character.Skills;
            var skills = state.Game.Skills;
            if (skills is null)
            {
                return new CharacterNotInitializedError("Skills");