~ruther/NosSmooth

c02cf72a8c911f1127991832b4b050250a586ba4 — Rutherther 2 years ago bc090b2
feat(combat): support self targeted and area skills
M Extensions/NosSmooth.Extensions.Combat/Extensions/CombatStateExtensions.cs => Extensions/NosSmooth.Extensions.Combat/Extensions/CombatStateExtensions.cs +6 -1
@@ 65,7 65,12 @@ public static class CombatStateExtensions
    /// <param name="target">The target to use skill at.</param>
    public static void UseSkill(this ICombatState combatState, Skill skill, ILivingEntity target)
    {
        combatState.EnqueueOperation(new UseSkillOperation(skill, target));
        if (combatState.Game.Character is null)
        {
            throw new InvalidOperationException("The character is not initialized.");
        }

        combatState.EnqueueOperation(new UseSkillOperation(skill, combatState.Game.Character, target));
    }

    /// <summary>

M Extensions/NosSmooth.Extensions.Combat/Operations/UsePrimarySkillOperation.cs => Extensions/NosSmooth.Extensions.Combat/Operations/UsePrimarySkillOperation.cs +12 -2
@@ 30,7 30,12 @@ public record UsePrimarySkillOperation(ILivingEntity Target) : ICombatOperation
                return Result<CanBeUsedResponse>.FromError(primarySkillResult);
            }

            _useSkillOperation = new UseSkillOperation(primarySkill, Target);
            if (combatState.Game.Character is null)
            {
                return new CharacterNotInitializedError();
            }

            _useSkillOperation = new UseSkillOperation(primarySkill, combatState.Game.Character, Target);
        }

        return _useSkillOperation.CanBeUsed(combatState);


@@ 47,7 52,12 @@ public record UsePrimarySkillOperation(ILivingEntity Target) : ICombatOperation
                return Result.FromError(primarySkillResult);
            }

            _useSkillOperation = new UseSkillOperation(primarySkill, Target);
            if (combatState.Game.Character is null)
            {
                return new CharacterNotInitializedError();
            }

            _useSkillOperation = new UseSkillOperation(primarySkill, combatState.Game.Character, Target);
        }

        return await _useSkillOperation.UseAsync(combatState, ct);

M Extensions/NosSmooth.Extensions.Combat/Operations/UseSkillOperation.cs => Extensions/NosSmooth.Extensions.Combat/Operations/UseSkillOperation.cs +51 -10
@@ 4,10 4,14 @@
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Diagnostics;
using System.Xml.XPath;
using NosSmooth.Data.Abstractions.Enums;
using NosSmooth.Data.Abstractions.Infos;
using NosSmooth.Extensions.Combat.Errors;
using NosSmooth.Game.Data.Characters;
using NosSmooth.Game.Data.Entities;
using NosSmooth.Packets;
using NosSmooth.Packets.Client.Battle;
using Remora.Results;



@@ 17,8 21,9 @@ namespace NosSmooth.Extensions.Combat.Operations;
/// A combat operation to use a skill.
/// </summary>
/// <param name="Skill">The skill to use.</param>
/// <param name="Caster">The caster entity that is using the skill.</param>
/// <param name="Target">The target entity to use the skill at.</param>
public record UseSkillOperation(Skill Skill, ILivingEntity Target) : ICombatOperation
public record UseSkillOperation(Skill Skill, ILivingEntity Caster, ILivingEntity Target) : ICombatOperation
{
    /// <inheritdoc />
    public Result<CanBeUsedResponse> CanBeUsed(ICombatState combatState)


@@ 48,19 53,11 @@ public record UseSkillOperation(Skill Skill, ILivingEntity Target) : ICombatOper
            return new MissingInfoError("skill", Skill.SkillVNum);
        }

        // TODO: support for area skills, support skills that use x, y coordinates (like dashes or teleports)
        using var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(ct);
        await combatState.CombatManager.RegisterSkillCancellationTokenAsync(linkedSource, ct);
        var sendResponse = await combatState.Client.SendPacketAsync
        (
            new UseSkillPacket
            (
                Skill.Info.CastId,
                Target.Type,
                Target.Id,
                null,
                null
            ),
            CreateSkillUsePacket(Skill.Info),
            ct
        );



@@ 84,4 81,48 @@ public record UseSkillOperation(Skill Skill, ILivingEntity Target) : ICombatOper

        return Result.FromSuccess();
    }

    private IPacket CreateSkillUsePacket(ISkillInfo info)
    {
        switch (info.TargetType)
        {
            case TargetType.SelfOrTarget: // a buff?
            case TargetType.Self:
                return CreateSelfTargetedSkillPacket(info);
            case TargetType.NoTarget: // area skill?
                return CreateAreaSkillPacket(info);
            case TargetType.Target:
                return CreateTargetedSkillPacket(info);
        }

        throw new UnreachableException();
    }

    private IPacket CreateAreaSkillPacket(ISkillInfo info)
        => new UseAOESkillPacket
        (
            info.CastId,
            Target.Position!.Value.X,
            Target.Position.Value.Y
        );

    private IPacket CreateTargetedSkillPacket(ISkillInfo info)
        => new UseSkillPacket
        (
            info.CastId,
            Target.Type,
            Target.Id,
            null,
            null
        );

    private IPacket CreateSelfTargetedSkillPacket(ISkillInfo info)
        => new UseSkillPacket
        (
            info.CastId,
            Caster.Type,
            Caster.Id,
            null,
            null
        );
}
\ No newline at end of file

M Extensions/NosSmooth.Extensions.Combat/Responders/SkillUseResponder.cs => Extensions/NosSmooth.Extensions.Combat/Responders/SkillUseResponder.cs +13 -4
@@ 1,5 1,5 @@
//
//  SuResponder.cs
//  SkillUseResponder.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.


@@ 17,27 17,36 @@ namespace NosSmooth.Extensions.Combat.Responders;
public class SuResponder : IPacketResponder<SuPacket>, IPacketResponder<BsPacket>
{
    private readonly CombatManager _combatManager;
    private readonly Game.Game _game;

    /// <summary>
    /// Initializes a new instance of the <see cref="SuResponder"/> class.
    /// </summary>
    /// <param name="combatManager">The combat manager.</param>
    public SuResponder(CombatManager combatManager)
    /// <param name="game">The game.</param>
    public SuResponder(CombatManager combatManager, Game.Game game)
    {
        _combatManager = combatManager;
        _game = game;
    }

    /// <inheritdoc />
    public Task<Result> Respond(PacketEventArgs<SuPacket> packetArgs, CancellationToken ct = default)
    {
        _combatManager.CancelSkillTokensAsync(ct);
        if (packetArgs.Packet.CasterEntityId == _game.Character?.Id)
        {
            _combatManager.CancelSkillTokensAsync(ct);
        }
        return Task.FromResult(Result.FromSuccess());
    }

    /// <inheritdoc />
    public Task<Result> Respond(PacketEventArgs<BsPacket> packetArgs, CancellationToken ct = default)
    {
        _combatManager.CancelSkillTokensAsync(ct);
        if (packetArgs.Packet.CasterEntityId == _game.Character?.Id)
        {
            _combatManager.CancelSkillTokensAsync(ct);
        }
        return Task.FromResult(Result.FromSuccess());
    }
}
\ No newline at end of file

M Extensions/NosSmooth.Extensions.Combat/Techniques/SimpleAttackTechnique.cs => Extensions/NosSmooth.Extensions.Combat/Techniques/SimpleAttackTechnique.cs +9 -1
@@ 4,6 4,7 @@
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Data.Abstractions.Enums;
using NosSmooth.Extensions.Combat.Errors;
using NosSmooth.Extensions.Combat.Extensions;
using NosSmooth.Extensions.Combat.Operations;


@@ 95,6 96,7 @@ public class SimpleAttackTechnique : ICombatTechnique
            var characterMp = character.Mp?.Amount ?? 0;
            var usableSkills = new[] { skills.PrimarySkill, skills.SecondarySkill }
                .Concat(skills.OtherSkills)
                .Where(x => x.Info is not null && x.Info.HitType != HitType.AlliesInZone)
                .Where(x => !x.IsOnCooldown && characterMp >= (x.Info?.MpCost ?? long.MaxValue));

            var skillResult = _skillSelector.GetSelectedSkill(usableSkills);


@@ 128,7 130,13 @@ public class SimpleAttackTechnique : ICombatTechnique
            return new EntityNotFoundError();
        }

        if (!character.Position.Value.IsInRange(_target.Position.Value, _currentSkill.Info.Range))
        var range = _currentSkill.Info.Range;
        if (_currentSkill.Info.TargetType == TargetType.Self && _currentSkill.Info.HitType == HitType.EnemiesInZone)
        {
            range = _currentSkill.Info.ZoneRange;
        }

        if (!character.Position.Value.IsInRange(_target.Position.Value, range))
        {
            state.WalkInRange(_walkManager, _target, _currentSkill.Info.Range);
        }

Do not follow this link