//
// UseSkillOperation.cs
//
// Copyright (c) František Boháček. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Diagnostics;
using System.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;
namespace NosSmooth.Extensions.Combat.Operations;
///
/// A combat operation to use a skill.
///
/// The skill to use.
/// The caster entity that is using the skill.
/// The target entity to use the skill at.
public record UseSkillOperation(Skill Skill, ILivingEntity Caster, ILivingEntity Target) : ICombatOperation
{
///
public Result CanBeUsed(ICombatState combatState)
{
if (Skill.Info is null)
{
return new MissingInfoError("skill", Skill.SkillVNum);
}
var character = combatState.Game.Character;
if (character is not null && character.Mp is not null && character.Mp.Amount is not null)
{
if (character.Mp.Amount < Skill.Info.MpCost)
{ // The character is in combat, mp won't restore.
return CanBeUsedResponse.WontBeUsable;
}
}
return Skill.IsOnCooldown ? CanBeUsedResponse.MustWait : CanBeUsedResponse.CanBeUsed;
}
///
public async Task UseAsync(ICombatState combatState, CancellationToken ct = default)
{
if (Skill.Info is null)
{
return new MissingInfoError("skill", Skill.SkillVNum);
}
using var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(ct);
await combatState.CombatManager.RegisterSkillCancellationTokenAsync(linkedSource, ct);
var sendResponse = await combatState.Client.SendPacketAsync
(
CreateSkillUsePacket(Skill.Info),
ct
);
if (!sendResponse.IsSuccess)
{
await combatState.CombatManager.UnregisterSkillCancellationTokenAsync(linkedSource, ct);
return sendResponse;
}
try
{
// wait 10 times the cast delay in case su is not received.
await Task.Delay(Skill.Info.CastTime * 1000, linkedSource.Token);
}
catch (TaskCanceledException)
{
// ignored
}
await combatState.CombatManager.UnregisterSkillCancellationTokenAsync(linkedSource, ct);
await Task.Delay(1000, ct);
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
);
}