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