// // 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 NosSmooth.Core.Contracts; using NosSmooth.Data.Abstractions.Enums; using NosSmooth.Data.Abstractions.Infos; using NosSmooth.Extensions.Combat.Errors; using NosSmooth.Game.Apis.Safe; using NosSmooth.Game.Contracts; using NosSmooth.Game.Data.Characters; using NosSmooth.Game.Data.Entities; using NosSmooth.Game.Errors; using NosSmooth.Game.Events.Battle; using Remora.Results; namespace NosSmooth.Extensions.Combat.Operations; /// /// A combat operation to use a skill. /// /// The skills api used for executing the skills. /// The skill to use. /// The caster entity that is using the skill. /// The target entity to use the skill at. public record UseSkillOperation ( NostaleSkillsApi SkillsApi, Skill Skill, ILivingEntity Caster, ILivingEntity Target ) : ICombatOperation { private IContract? _contract; /// public OperationQueueType QueueType => OperationQueueType.TotalControl; /// public bool MayBeCancelled => false; /// public async Task BeginExecution(ICombatState combatState, CancellationToken ct = default) { if (_contract is not null) { return Result.FromSuccess(); } if (Skill.Info is null) { return new MissingInfoError("skill", Skill.SkillVNum); } if (Target.Position is null) { return new NotInitializedError("target's position"); } var contractResult = ContractSkill(Skill.Info); if (!contractResult.IsDefined(out var contract)) { return Result.FromError(contractResult); } _contract = contract; var executed = await _contract.OnlyExecuteAsync(ct); if (executed.IsSuccess) { _contract.Register(); } return executed; } /// public async Task WaitForFinishedAsync(ICombatState combatState, CancellationToken ct = default) { var result = await BeginExecution(combatState, ct); if (!result.IsSuccess) { return result; } if (_contract is null) { throw new UnreachableException(); } var waitResult = await _contract.WaitForAsync(UseSkillStates.CharacterRestored, ct: ct); return waitResult.IsSuccess ? Result.FromSuccess() : Result.FromError(waitResult); } /// public bool IsExecuting() => _contract is not null && _contract.CurrentState > UseSkillStates.None && !IsFinished(); /// public bool IsFinished() => _contract?.HasReachedState(UseSkillStates.CharacterRestored) ?? false; /// public Result CanBeUsed(ICombatState combatState) { if (Skill.Info is null) { return new MissingInfoError("skill", Skill.SkillVNum); } var character = combatState.Game.Character; if (Target.Hp is not null && Target.Hp.Amount is not null && Target.Hp.Amount == 0) { return new CannotBeUsedError(CanBeUsedResponse.WontBeUsable, new TargetDeadError()); } if (character is null) { return new CharacterNotInitializedError(); } if (character.CantAttack) { return new CannotBeUsedError(CanBeUsedResponse.MustWait, new CharacterCannotAttackError()); } if (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 new CannotBeUsedError ( CanBeUsedResponse.WontBeUsable, new NotEnoughManaError(character.Mp.Amount.Value, Skill.Info.MpCost) ); } } if (Skill.IsOnCooldown) { return new CannotBeUsedError(CanBeUsedResponse.MustWait, new SkillOnCooldownError(Skill)); } return Result.FromSuccess(); } /// public void Cancel() { throw new InvalidOperationException(); } private Result> ContractSkill(ISkillInfo info) { if (info.AttackType == AttackType.Dash) { return SkillsApi.ContractUseSkillOn ( Skill, info.TargetType == TargetType.Self ? Caster : Target, Target.Position!.Value.X, Target.Position!.Value.Y ); } switch (info.TargetType) { case TargetType.SelfOrTarget: // a buff? case TargetType.Self: return SkillsApi.ContractUseSkillOnCharacter(Skill); case TargetType.NoTarget: // area skill? return SkillsApi.ContractUseSkillAt(Skill, Target.Position!.Value.X, Target.Position.Value.Y); case TargetType.Target: return SkillsApi.ContractUseSkillOn(Skill, Target); } throw new UnreachableException(); } /// public void Dispose() { _contract?.Unregister(); } }