public Action(List <Tile> path, ActionType actionType = ActionType.Nothing, Targetable target = null, float score = 0f) { this.path = path; this.actionType = actionType; Score = score; if (actionType == ActionType.Rest && target != null) { throw new ArgumentException("A rest action cannot have a target unit"); } this.target = target; }
public void LinkTargetable(Targetable targetable) { if (targetable.GetType() == typeof(Unit)) { LinkUnit((Unit)targetable); } else if (targetable.GetType() == typeof(Door)) { LinkDoor((Door)targetable); } UpdateClickHint(); }
/// <summary> /// Finds the safest place a unit could go to rest /// </summary> /// <param name="potentialActions">The potential actions of this unit</param> /// <param name="playableUnit">The unit currently controlled by the AI</param> /// <returns>The path to the safest tile to go</returns> private List <Tile> FindFleePath(List <Action> potentialActions, Unit playableUnit, GridController grid) { int[,] optionMap = new int[grid.NbColumns, grid.NbLines]; for (int i = 0; i < optionMap.GetLength(0); i++) { for (int j = 0; j < optionMap.GetLength(1); j++) { optionMap[i, j] = 0; } } Targetable enemy = null; for (int i = 0; i < optionMap.GetLength(0); i++) { for (int j = 0; j < optionMap.GetLength(1); j++) { for (int k = 0; k < potentialActions.Count; k++) { enemy = potentialActions[k].Target; if (enemy is Unit enemyUnit) { //Each tile has a score based on the added number of turns it would take each player's unit to get there optionMap[i, j] += (int)Math.Ceiling((enemyUnit.MovementCosts[i, j] - 1) / (double)enemyUnit.Stats.MoveSpeed); } } } } int highestScore = 0; Vector2Int position = new Vector2Int(-1, -1); for (int i = 0; i < optionMap.GetLength(0); i++) { for (int j = 0; j < optionMap.GetLength(1); j++) { if (optionMap[i, j] >= highestScore && playableUnit.MovementCosts[i, j] <= playableUnit.Stats.MoveSpeed) { highestScore = optionMap[i, j]; position.x = i; position.y = j; } } } return(FindPathTo(playableUnit, position, grid)); }
/// <summary> /// Calculates how the distance to a target unit influences the score of an action /// </summary> /// <param name="playableUnit">The unit currently controlled by the AI</param> /// <param name="targetPath">The path to a target unit</param> /// <returns>The score modifier caused by the target's distance</returns> public float DistanceChoiceMod(Unit playableUnit, Targetable target, List <Tile> targetPath) { float scoreMod = 0f; if (playableUnit.TargetIsInRange(target)) { scoreMod = aiControllerValues.AdjacentTargetChoiceMod; } else if (targetPath.Count <= 1) { scoreMod += aiControllerValues.InaccessibleTargetChoiceMod; } else { double nbToursDouble = PathFinder.CalculatePathCost(targetPath, playableUnit.MovementCosts) / playableUnit.Stats.MoveSpeed; int nbTours = (int)Math.Ceiling(nbToursDouble); scoreMod = aiControllerValues.TurnMultiplierForDistanceChoiceMod * nbTours + aiControllerValues.TurnAdderForDistanceChoiceMod; } return(scoreMod); }
public IEnumerator Attack(Targetable target, bool isCountering, float duration) { if (associatedUnit.IsAttacking) { yield break; } associatedUnit.IsAttacking = true; associatedUnit.UnitAnimator?.PlayAttackAnimation(); var counter = 0f; var startPos = associatedUnit.Transform.position; //2f to go only halfway var targetPos = (target.CurrentTile.WorldPosition + startPos) / 2f; LookAt(targetPos); duration /= 2; while (counter < duration) { counter += Time.deltaTime; associatedUnit.Transform.position = Vector3.Lerp(startPos, targetPos, counter / duration); yield return(null); } var hitRate = associatedUnit.Stats.HitRate - target.CurrentTile.DefenseRate; var damage = 0; var critModifier = 1; if (Random.value <= hitRate) { damage = associatedUnit.Stats.AttackStrength; if (target is Unit unit) { associatedUnit.OnDodge.Publish(unit); associatedUnit.UnitAnimator?.PlayBlockAnimation(); } } else if (target is Unit unit) { associatedUnit.OnHurt.Publish(unit); associatedUnit.UnitAnimator?.PlayHurtAnimation(); } if (target is Unit enemyUnit) { if (damage > 0 && !isCountering && !enemyUnit.IsImmuneToCrits && (associatedUnit.CanCritOnEverybody || enemyUnit.WeaponType == associatedUnit.WeaponAdvantage)) { critModifier = Random.value <= associatedUnit.Stats.CritRate ? 2 : 1; damage *= critModifier; if (critModifier > 1 && associatedUnit.CameraShake != null) { associatedUnit.CameraShake.TriggerShake(); } } } target.CurrentHealthPoints -= damage; if (target is Unit) { uiController.ChangeCharacterDamageTaken(damage, !associatedUnit.IsEnemy, critModifier); } associatedUnit.UnitAnimator?.StopBlockAnimation(); associatedUnit.UnitAnimator?.StopHurtAnimation(); counter = 0; while (counter < duration) { counter += Time.deltaTime; associatedUnit.Transform.position = Vector3.Lerp(targetPos, startPos, counter / duration); yield return(null); } associatedUnit.Transform.position = startPos; associatedUnit.IsAttacking = false; associatedUnit.UnitAnimator?.StopAttackAnimation(); //A unit cannot make a critical hit on a counter && cannot counter on a counter if (!target.NoHealthLeft && !isCountering && target is Unit targetUnit) { yield return(targetUnit.UnitMover.Attack(associatedUnit, true, gameSettings.AttackDuration)); } if (!isCountering) { associatedUnit.HasActed = true; } }
public void AddTarget(Targetable target) { targetsToDestroy.Add(target); }