// // NostaleSkillsApi.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.Core.Client; using NosSmooth.Core.Contracts; using NosSmooth.Data.Abstractions.Enums; using NosSmooth.Game.Apis.Unsafe; using NosSmooth.Game.Contracts; using NosSmooth.Game.Data.Characters; using NosSmooth.Game.Data.Entities; using NosSmooth.Game.Data.Info; using NosSmooth.Game.Errors; using NosSmooth.Game.Events.Battle; using NosSmooth.Packets.Client.Battle; using Remora.Results; namespace NosSmooth.Game.Apis.Safe; /// /// A safe NosTale api for using character skills. /// public class NostaleSkillsApi { private readonly Game _game; private readonly INostaleClient _client; private readonly Contractor _contractor; /// /// Initializes a new instance of the class. /// /// The game. /// The NosTale client. /// The contractor. public NostaleSkillsApi(Game game, INostaleClient client, Contractor contractor) { _game = game; _client = client; _contractor = contractor; } /// /// Use the given (targetable) skill on character himself. /// /// The skill to use. /// The cancellation token for cancelling the operation. /// A result that may or may not have succeeded. public Task UseSkillOnCharacter ( Skill skill, CancellationToken ct = default ) { var character = _game.Character; if (character is null) { return Task.FromResult(new NotInitializedError("Game.Character")); } var skills = _game.Skills; if (skills is null) { return Task.FromResult(new NotInitializedError("Game.Skills")); } if (skill.IsOnCooldown) { return Task.FromResult(new SkillOnCooldownError(skill)); } if (skill.Info is null) { return Task.FromResult(new NotInitializedError("skill info")); } if (skill.Info.TargetType is not(TargetType.Self or TargetType.SelfOrTarget)) { return Task.FromResult(new WrongSkillTargetError(skill, character)); } return _client.SendPacketAsync ( new UseSkillPacket ( skill.Info.CastId, character.Type, character.Id, null, null ), ct ); } /// /// Use the given (targetable) skill on specified entity. /// /// /// The skill won't be used if it is on cooldown. /// For skills that can be used only on self, use of the character. /// For skills that cannot be targeted on an entity, proceed to UseSkillAt. /// /// The skill to use. /// The entity to use the skill on. /// The x coordinate on the map. (Used for non targeted dashes etc., says where the dash will be to.) /// The y coordinate on the map. (Used for non targeted dashes etc., says where the dash will be to.) /// The cancellation token for cancelling the operation. /// A result that may or may not have succeeded. public Task UseSkillOn ( Skill skill, ILivingEntity entity, short? mapX = default, short? mapY = default, CancellationToken ct = default ) { if (entity == _game.Character) { return UseSkillOnCharacter(skill, ct); } var skills = _game.Skills; if (skills is null) { return Task.FromResult(new NotInitializedError("Game.Skills")); } if (skill.IsOnCooldown) { return Task.FromResult(new SkillOnCooldownError(skill)); } if (skill.Info is null) { return Task.FromResult(new NotInitializedError("skill info")); } if (skill.Info.TargetType is not(TargetType.Target or TargetType.SelfOrTarget)) { return Task.FromResult(new WrongSkillTargetError(skill, entity)); } var entityPosition = entity.Position; if (entityPosition is null) { return Task.FromResult(new NotInitializedError("entity position")); } var characterPosition = _game.Character?.Position; if (characterPosition is null) { return Task.FromResult(new NotInitializedError("character position")); } if (!entityPosition.Value.IsInRange(characterPosition.Value, skill.Info.Range)) { return Task.FromResult ( new NotInRangeError ( "Character", characterPosition.Value, entityPosition.Value, skill.Info.Range ) ); } if (mapX != null && mapY != null) { var mapPosition = new Position(mapX.Value, mapY.Value); if (!mapPosition.IsInRange(characterPosition.Value, skill.Info.Range)) { return Task.FromResult ( new NotInRangeError ( "Character", characterPosition.Value, mapPosition, skill.Info.Range ) ); } } return _client.SendPacketAsync ( new UseSkillPacket ( skill.Info.CastId, entity.Type, entity.Id, mapX, mapY ), ct ); } /// /// Use the given (targetable) skill on specified entity. /// /// /// For skills that can be used only on self, use of the character. /// For skills that cannot be targeted on an entity, proceed to UseSkillAt. /// /// The skill to use. /// The id of the entity to use the skill on. /// The x coordinate on the map. (Used for non targeted dashes etc., says where the dash will be to.) /// The y coordinate on the map. (Used for non targeted dashes etc., says where the dash will be to.) /// The cancellation token for cancelling the operation. /// A result that may or may not have succeeded. public Task UseSkillOn ( Skill skill, long entityId, short? mapX = default, short? mapY = default, CancellationToken ct = default ) { var map = _game.CurrentMap; if (map is null) { return Task.FromResult(new NotInitializedError("Game.Map")); } var entity = map.Entities.GetEntity(entityId); if (entity is null) { return Task.FromResult(new NotFoundError($"Entity with id {entityId} was not found on the map.")); } return UseSkillOn ( skill, entity, mapX, mapY, ct ); } /// /// Use the given (aoe) skill on the specified place. /// /// /// For skills that can have targets, proceed to UseSkillOn. /// /// The skill to use. /// The x coordinate to use the skill at. /// The y coordinate to use the skill at. /// The cancellation token for cancelling the operation. /// A result that may or may not have succeeded. public Task UseSkillAt ( Skill skill, short mapX, short mapY, CancellationToken ct = default ) { var skills = _game.Skills; if (skills is null) { return Task.FromResult(new NotInitializedError("Game.Skills")); } if (skill.IsOnCooldown) { return Task.FromResult(new SkillOnCooldownError(skill)); } if (skill.Info is null) { return Task.FromResult(new NotInitializedError("skill info")); } if (skill.Info.TargetType is not TargetType.NoTarget) { return Task.FromResult(new WrongSkillTargetError(skill, null)); } var characterPosition = _game.Character?.Position; if (characterPosition is null) { return Task.FromResult(new NotInitializedError("character position")); } var target = new Position(mapX, mapY); if (!target.IsInRange(characterPosition.Value, skill.Info.Range)) { return Task.FromResult ( new NotInRangeError ( "Character", characterPosition.Value, target, skill.Info.Range ) ); } return _client.SendPacketAsync ( new UseAOESkillPacket(skill.Info.CastId, mapX, mapY), ct ); } /// /// Creates a contract for using a skill on character himself. /// /// The skill to use. /// The contract or an error. public Result> ContractUseSkillOnCharacter ( Skill skill ) { var characterId = _game?.Character?.Id; if (characterId is null) { return new NotInitializedError("Game.Character"); } return Result>.FromSuccess ( UnsafeSkillsApi.CreateUseSkillContract ( _contractor, skill.SkillVNum, characterId.Value, ct => UseSkillOnCharacter ( skill, ct ) ) ); } /// /// Creates a contract for using a skill on the given entity. /// /// The skill to use. /// The entity to use the skill on. /// The x coordinate to use the skill at. /// The y coordinate to use the skill at. /// The contract or an error. public Result> ContractUseSkillOn ( Skill skill, ILivingEntity entity, short? mapX = default, short? mapY = default ) { var characterId = _game?.Character?.Id; if (characterId is null) { return new NotInitializedError("Game.Character"); } return Result>.FromSuccess ( UnsafeSkillsApi.CreateUseSkillContract ( _contractor, skill.SkillVNum, characterId.Value, ct => UseSkillOn ( skill, entity, mapX, mapY, ct ) ) ); } /// /// Creates a contract for using a skill at the given location. /// /// The skill to use. /// The x coordinate to use the skill at. /// The y coordinate to use the skill at. /// The contract or an error. public Result> ContractUseSkillAt ( Skill skill, short mapX, short mapY ) { var characterId = _game?.Character?.Id; if (characterId is null) { return new NotInitializedError("Game.Character"); } return Result>.FromSuccess ( UnsafeSkillsApi.CreateUseSkillContract ( _contractor, skill.SkillVNum, characterId.Value, ct => UseSkillAt ( skill, mapX, mapY, ct ) ) ); } }