//
// 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
)
)
);
}
}