//
// EnemyPolicy.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.Data.SqlTypes;
using NosSmooth.Extensions.Combat.Errors;
using NosSmooth.Extensions.Combat.Selectors;
using NosSmooth.Game.Data.Characters;
using NosSmooth.Game.Data.Entities;
using NosSmooth.Game.Data.Info;
using Remora.Results;
namespace NosSmooth.Extensions.Combat.Policies;
///
/// An enemy selector policy that selects the monsters based on their vnums.
///
/// The policy to select an enemy.
/// The vnums of the monsters to target.
/// The area in which to get enemies from.
public record EnemyPolicy
(
EnemySelectPolicy SelectPolicy,
int[]? MonsterVNums = default,
CombatArea? CombatArea = default
) : IEnemySelector
{
///
public Result GetSelectedEntity(ICombatState combatState, ICollection possibleTargets)
{
var targets = possibleTargets.OfType();
if (MonsterVNums is not null)
{
targets = targets.Where(x => MonsterVNums.Contains(x.VNum));
}
targets = targets.ToArray();
if (!targets.Any())
{
return new EntityNotFoundError();
}
if (combatState.Game.Character is null)
{
return new CharacterNotInitializedError();
}
var position = combatState.Game.Character.Position;
if (position is null)
{
return new CharacterNotInitializedError();
}
var characterPosition = position.Value;
ILivingEntity? target = null;
switch (SelectPolicy)
{
case EnemySelectPolicy.Aggressive:
throw new NotImplementedException(); // TODO: implement aggressive policy
case EnemySelectPolicy.Closest:
target = targets
.Where(x => x.Position is not null && (CombatArea?.IsInside(x.Position.Value) ?? true))
.MinBy(x => x.Position!.Value.DistanceSquared(characterPosition))!;
break;
case EnemySelectPolicy.LowestHealth:
target = targets.MinBy
(
x =>
{
if (x.Hp is null)
{
return int.MaxValue;
}
if (x.Hp.Amount is not null)
{
return x.Hp.Amount;
}
if (x.Hp.Percentage is not null && x.Level is not null)
{
return x.Hp.Percentage * 100 * x.Level;
}
if (x.Hp.Maximum is not null)
{
return x.Hp.Maximum; // Assume max health, best guess.
}
return int.MaxValue;
}
);
break;
}
if (target is null)
{
return new EntityNotFoundError();
}
return Result.FromSuccess(target);
}
}
///
/// A policy enemy selector.
///
public enum EnemySelectPolicy
{
///
/// Select the enemy with the lowest health.
///
LowestHealth,
///
/// Selects the enemy that targets the user.
///
Aggressive,
///
/// Selects the enemy that is the closest to the character.
///
Closest
}
///
/// The combat area around which to find enemies.
///
/// The area center x coordinate.
/// The area center y coordinate.
/// The maximum range from the center.
public record CombatArea(short CenterX, short CenterY, short Range)
{
///
/// Create a combat area around a specified entity.
///
/// The entity.
/// The range.
/// The combat area.
/// If the entity does not have a position.
public static CombatArea CreateAroundEntity(IEntity entity, short range)
{
var position = entity.Position;
if (position is null)
{
throw new ArgumentException(nameof(entity));
}
return new CombatArea(position.Value.X, position.Value.Y, range);
}
///
/// Gets whether the position is inside of the combat area.
///
/// The position.
/// Whether the position is inside.
public bool IsInside(Position position)
{
return position.DistanceSquared(new Position(CenterX, CenterY)) < Range * Range;
}
}