From a76434f366210995ac509483a5726863388fcb1e Mon Sep 17 00:00:00 2001 From: Rutherther Date: Fri, 18 Feb 2022 23:31:57 +0100 Subject: [PATCH] feat(combat): add enemy, item, skill selectors --- .../Policies/EnemyPolicy.cs | 162 ++++++++++++++++++ .../Policies/UseItemPolicy.cs | 90 ++++++++++ .../Policies/UseSkillPolicy.cs | 58 +++++++ .../Selectors/IEnemySelector.cs | 24 +++ .../Selectors/IItemSelector.cs | 31 ++++ .../Selectors/ISkillSelector.cs | 23 +++ 6 files changed, 388 insertions(+) create mode 100644 Extensions/NosSmooth.Extensions.Combat/Policies/EnemyPolicy.cs create mode 100644 Extensions/NosSmooth.Extensions.Combat/Policies/UseItemPolicy.cs create mode 100644 Extensions/NosSmooth.Extensions.Combat/Policies/UseSkillPolicy.cs create mode 100644 Extensions/NosSmooth.Extensions.Combat/Selectors/IEnemySelector.cs create mode 100644 Extensions/NosSmooth.Extensions.Combat/Selectors/IItemSelector.cs create mode 100644 Extensions/NosSmooth.Extensions.Combat/Selectors/ISkillSelector.cs diff --git a/Extensions/NosSmooth.Extensions.Combat/Policies/EnemyPolicy.cs b/Extensions/NosSmooth.Extensions.Combat/Policies/EnemyPolicy.cs new file mode 100644 index 0000000..1d971b9 --- /dev/null +++ b/Extensions/NosSmooth.Extensions.Combat/Policies/EnemyPolicy.cs @@ -0,0 +1,162 @@ +// +// 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)); + } + + 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; + } +} \ No newline at end of file diff --git a/Extensions/NosSmooth.Extensions.Combat/Policies/UseItemPolicy.cs b/Extensions/NosSmooth.Extensions.Combat/Policies/UseItemPolicy.cs new file mode 100644 index 0000000..0150451 --- /dev/null +++ b/Extensions/NosSmooth.Extensions.Combat/Policies/UseItemPolicy.cs @@ -0,0 +1,90 @@ +// +// UseItemPolicy.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.Extensions.Combat.Errors; +using NosSmooth.Extensions.Combat.Selectors; +using NosSmooth.Game.Data.Characters; +using NosSmooth.Game.Data.Items; +using Remora.Results; + +namespace NosSmooth.Extensions.Combat.Policies; + +/// +/// The policy to use an item. +/// +/// Whether to use items. +/// Use items below the given character's health percentage. +/// Use items below the given character's mana percentage. +/// The vnums of the items to use as health items. +/// The vnums of the items to use as mana items. +public record UseItemPolicy +( + bool UseItems, + int UseBelowHealthPercentage, + int UseBelowManaPercentage, + int[] UseHealthItemsVNums, + int[] UseManaItemsVNums +) : IItemSelector +{ + /// + public Result GetSelectedItem(ICombatState combatState, ICollection possibleItems) + { + var character = combatState.Game.Character; + if (character is null) + { + return new ItemNotFoundError(); + } + + if (ShouldUseHpItem(character)) + { + var item = possibleItems.FirstOrDefault(x => UseHealthItemsVNums.Contains(x.ItemVNum)); + if (item is not null) + { + return item; + } + } + + if (ShouldUseMpItem(character)) + { + var item = possibleItems.FirstOrDefault(x => UseManaItemsVNums.Contains(x.ItemVNum)); + if (item is not null) + { + return item; + } + } + + return new ItemNotFoundError(); + } + + /// + public Result ShouldUseItem(ICombatState combatState) + { + if (!UseItems) + { + return false; + } + + var character = combatState.Game.Character; + if (character is null) + { + return false; + } + + return ShouldUseHpItem(character) || ShouldUseMpItem(character); + } + + private bool ShouldUseHpItem(Character character) + { + return character.Hp is not null && character.Hp.Percentage is not null + && character.Hp.Percentage < UseBelowHealthPercentage; + } + + private bool ShouldUseMpItem(Character character) + { + return character.Mp is not null && character.Mp.Percentage is not null + && character.Mp.Percentage < UseBelowManaPercentage; + } +} \ No newline at end of file diff --git a/Extensions/NosSmooth.Extensions.Combat/Policies/UseSkillPolicy.cs b/Extensions/NosSmooth.Extensions.Combat/Policies/UseSkillPolicy.cs new file mode 100644 index 0000000..694ffb1 --- /dev/null +++ b/Extensions/NosSmooth.Extensions.Combat/Policies/UseSkillPolicy.cs @@ -0,0 +1,58 @@ +// +// UseSkillPolicy.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.Data.Abstractions.Enums; +using NosSmooth.Extensions.Combat.Errors; +using NosSmooth.Extensions.Combat.Selectors; +using NosSmooth.Game.Data.Characters; +using Remora.Results; + +namespace NosSmooth.Extensions.Combat.Policies; + +/// +/// The policy to use a skill. +/// +/// Whether to prefer targeted skills (true) or area skills (false). +/// The vnums of the skills that are allowed to be used. +public record UseSkillPolicy(bool PreferTargetedSkills, int[]? AllowedSkillVNums) + : ISkillSelector +{ + /// + public Result GetSelectedSkill(IEnumerable usableSkills) + { + var skills = usableSkills.Where(x => CanBeUsed(x)) + .Reverse(); + + if (PreferTargetedSkills) + { + skills = skills.OrderBy(x => x.Info!.HitType == HitType.EnemiesInZone ? 1 : 0); + } + + var skill = skills.FirstOrDefault(); + if (skill is null) + { + return new SkillNotFoundError(); + } + + return skill; + } + + private bool CanBeUsed(Skill skill) + { + if (AllowedSkillVNums is not null && !AllowedSkillVNums.Contains(skill.SkillVNum)) + { + return false; + } + + if (skill.Info is null) + { + return false; + } + + return skill.Info.HitType is HitType.EnemiesInZone or HitType.TargetOnly + && skill.Info.TargetType is TargetType.Target or TargetType.NoTarget; + } +} \ No newline at end of file diff --git a/Extensions/NosSmooth.Extensions.Combat/Selectors/IEnemySelector.cs b/Extensions/NosSmooth.Extensions.Combat/Selectors/IEnemySelector.cs new file mode 100644 index 0000000..0f84fee --- /dev/null +++ b/Extensions/NosSmooth.Extensions.Combat/Selectors/IEnemySelector.cs @@ -0,0 +1,24 @@ +// +// IEnemySelector.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.Game.Data.Entities; +using Remora.Results; + +namespace NosSmooth.Extensions.Combat.Selectors; + +/// +/// Selects an enemy from the possible enemies. +/// +public interface IEnemySelector +{ + /// + /// Gets the entity to be currently selected. + /// + /// The combat state. + /// The collection of possible targets. + /// The selected entity, or an error. + public Result GetSelectedEntity(ICombatState combatState, ICollection possibleTargets); +} \ No newline at end of file diff --git a/Extensions/NosSmooth.Extensions.Combat/Selectors/IItemSelector.cs b/Extensions/NosSmooth.Extensions.Combat/Selectors/IItemSelector.cs new file mode 100644 index 0000000..a02637c --- /dev/null +++ b/Extensions/NosSmooth.Extensions.Combat/Selectors/IItemSelector.cs @@ -0,0 +1,31 @@ +// +// IItemSelector.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.Game.Data.Items; +using Remora.Results; + +namespace NosSmooth.Extensions.Combat.Selectors; + +/// +/// Selects an item to be used. +/// +public interface IItemSelector +{ + /// + /// Gets the entity to be currently selected. + /// + /// The combat state. + /// The items that may be used. + /// The selected item, or an error. + public Result GetSelectedItem(ICombatState combatState, ICollection possibleItems); + + /// + /// Gets whether currently an item should be used. + /// + /// The combat state. + /// Whether to use an item or an error. + public Result ShouldUseItem(ICombatState combatState); +} \ No newline at end of file diff --git a/Extensions/NosSmooth.Extensions.Combat/Selectors/ISkillSelector.cs b/Extensions/NosSmooth.Extensions.Combat/Selectors/ISkillSelector.cs new file mode 100644 index 0000000..341f9ce --- /dev/null +++ b/Extensions/NosSmooth.Extensions.Combat/Selectors/ISkillSelector.cs @@ -0,0 +1,23 @@ +// +// ISkillSelector.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.Game.Data.Characters; +using Remora.Results; + +namespace NosSmooth.Extensions.Combat.Selectors; + +/// +/// Selects a skill to use from a possible skills. +/// +public interface ISkillSelector +{ + /// + /// Gets the skill to use. + /// + /// The skills that may be used. Won't contain skills the user doesn't have mana for and that are on cooldown. + /// The skill to use, or an error. + public Result GetSelectedSkill(IEnumerable usableSkills); +} \ No newline at end of file -- 2.49.0