// try to heal the person, but if too far, screw it go for wait instead public override IEnumerator Execute() { map.FindSelectableTiles(currUnit.currentTile, currUnit.stats[StatString.MOVEMENT_RANGE].Value); yield return(new WaitForSecondsRealtime(0.75f)); Tile targetTile = enemyUnit.medicTarget.currentTile; EnemyUnit medicTarget = enemyUnit.medicTarget; AStarSearch.GeneratePath(map, currUnit.currentTile, targetTile, false, true); // check if target tile is selectable, and also go as far from movement range as possible. int distanceFromTarget = 0, minHealingRange = (int)enemyUnit.teamHealingAbilities.Min(ability => ability.attackRange); while (!targetTile.selectable || distanceFromTarget < minHealingRange && targetTile != currUnit.currentTile) { distanceFromTarget++; targetTile = targetTile.parent; } // if cannot reach in 2 turns, screw it change to Wait() int maxHealingRange = (int)enemyUnit.teamHealingAbilities.Max(ability => ability.attackRange); var maxPermissibleRange = (int) (currUnit.stats[StatString.MOVEMENT_RANGE].Value + enemyUnit.medicTarget.stats[StatString.MOVEMENT_RANGE].Value) * 2 + maxHealingRange; if (targetTile.distance > maxPermissibleRange) { turnScheduler.SetState(new EnemyAiWait(turnScheduler)); } // A star movement towards the target currUnit.GetPathToTile(targetTile); yield return(new WaitUntil(() => currUnit.CurrState == UnitState.IDLING)); // check if there are players in range bool canHeal = false; foreach (Ability ability in enemyUnit.teamHealingAbilities) // TODO should be able to sort in ascending order by healing amount { if (map.PlayerTargetInAttackRange(currUnit.currentTile, ability.attackRange, medicTarget)) { currUnit.abilityTargetUnits = new List <Unit>() { medicTarget }; // TODO only supports single target healing at the moment currUnit.chosenAbility = ability; turnScheduler.SetState(new EnemyAbility(turnScheduler)); canHeal = true; } } if (!canHeal) { turnScheduler.SetState(new EnemyAiWait(turnScheduler)); } }
public IEnumerator astar_finds_shortest_distance_from_5x1y_to_7x0y_equals_4() { // ACT AStarSearch.GeneratePath(_map, _map.tileList[5][1], _map.tileList[7][0]); // ASSERT Assert.AreEqual(4, _map.tileList[7][0].distance); yield return(null); }
// selectable means that only selectable tiles will be added (for walking within the selectable tile zone) // playerTargeting means that the only occupied tile allowed will be the goal tile (to allow targeting of units) public static Tile GeneratePathToNearestTarget(Map map, Tile start, List <Tile> targets, bool selectable = false, bool playerTargeting = false) { // use distance to determine closest player int minDistance = int.MaxValue; Tile targetTile = targets[0]; foreach (Tile target in targets) { AStarSearch.GeneratePath(map, start, target, selectable, playerTargeting); if (target.distance < minDistance) { minDistance = target.distance; targetTile = target; } } AStarSearch.GeneratePath(map, start, targetTile, selectable, playerTargeting); return(targetTile); }
public List <Vector2> GeneratePath(Vector2 start, Vector2 end) { uint nodeConversion = OS.GetTicksMsec(); TileMapGraphNode startNode = tileMapGraph.GetGraphNodeFromWorldPos(start); TileMapGraphNode endNode = tileMapGraph.GetGraphNodeFromWorldPos(end); GD.Print("Node Conversion: " + (OS.GetTicksMsec() - nodeConversion) + "ms"); uint astarSearch = OS.GetTicksMsec(); List <TileMapGraphNode> graphNodePath = AStarSearch.GeneratePath(tileMapGraph, startNode, endNode); GD.Print("AStarSearch.GeneratePath: " + (OS.GetTicksMsec() - astarSearch) + "ms"); List <Vector2> path = new List <Vector2>(); if (graphNodePath != null) { for (int i = 0; i < graphNodePath.Count; i++) { path.Add(graphNodePath[i].GetWorldPosition()); } } return(path); }
public override IEnumerator Execute() { // if can self heal if (enemyUnit.CanSelfHeal()) { // retreat, heal // from all player positions, find selectable tiles using dijkstra - add a proximity score to each one of them. // Then after that, enemy unit finds selectable tiles and chooses a tile at random of the least proximity score.. yield return(new WaitForSecondsRealtime(0.5f)); Retreat(); yield return(new WaitUntil(() => currUnit.CurrState == UnitState.IDLING)); map.RemoveSelectableTiles(currUnit.currentTile); // heal // randomly assign ability (in future maybe prioritise highest heal instead) Ability healAbility = enemyUnit.selfHealingAbilities[new System.Random().Next(0, enemyUnit.selfHealingAbilities.Count)]; enemyUnit.chosenAbility = healAbility; currUnit.CurrState = UnitState.TARGETING; map.FindAttackableTiles(currUnit.currentTile, healAbility.attackRange, healAbility.targetingStyle); yield return(new WaitForSecondsRealtime(1)); map.RemoveAttackableTiles(); // TODO in future need to migrate this to abiltyTargeting to maximise the number of targets List <Unit> targetUnits = new List <Unit>() { currUnit }; turnScheduler.StartCoroutine(turnScheduler.AbilityAnimation(currUnit)); yield return(new WaitUntil(() => currUnit.anim.GetBool("isAbility") == false)); yield return(turnScheduler.StartCoroutine(turnScheduler.currUnit.chosenAbility.Execute(turnScheduler.currUnit, targetUnits))); foreach (Unit unit in targetUnits) { if (unit.isDead()) { yield return(turnScheduler.StartCoroutine(turnScheduler.RemoveUnit(unit))); } else { unit.UpdateUI(); } } targetUnits.Clear(); turnScheduler.currUnit.chosenAbility = null; yield return(new WaitForSeconds(1f)); turnScheduler.SetState(new EnemyEndTurn(turnScheduler)); yield break; } else { List <EnemyUnit> healers = enemies.FindAll(x => x.CanTeamHeal()); // find closest unit who can team heal if (healers.Count > 0) { List <PlayerUnit> players = turnScheduler.players; // Rank healers and find one to run towards // Ranked according to distance // Ranked by maximising the minimum (distance from player - player range) // initialize min/max ally/enemies distance, AStarSearch.GeneratePath(map, enemyUnit.currentTile, healers[0].currentTile, false, true); List <int> allyDistances = new List <int>(); List <float> minPlayerDistances = new List <float>(); // loop through all the healers and players and rank heuristic for (int i = 0; i < healers.Count; i++) { EnemyUnit healer = healers[i]; AStarSearch.GeneratePath(map, enemyUnit.currentTile, healer.currentTile, false, true); allyDistances.Add(healer.currentTile.distance); // finding (distance from player - player range) for all players // moveTo is to tile the player will end up at Tile moveTo = healer.currentTile; bool changed = false; while (moveTo.distance > enemyUnit.stats[StatString.MOVEMENT_RANGE].Value) { moveTo = moveTo.parent; changed = true; } if (changed) { Assert.AreNotEqual(moveTo, healer.currentTile); } // calculating the minimum player distance and add the inverse since we want to be far away TODO DIKJSTRA ONCE AND FOR ALL int minPlayerDistance = int.MaxValue; foreach (Unit player in players) { AStarSearch.GeneratePath(map, player.currentTile, moveTo, false, true); var playerEffectiveAttackRange = player.stats[StatString.ATTACK_RANGE].Value + player.stats[StatString.MOVEMENT_RANGE].Value; int currPlayerDistance = (int)(moveTo.distance - playerEffectiveAttackRange); currPlayerDistance = currPlayerDistance < 1 ? 1 : currPlayerDistance; minPlayerDistance = Math.Min(minPlayerDistance, currPlayerDistance); } minPlayerDistances.Add(1.0f / minPlayerDistance); } // normalize both lists then find the min heustistic (60% ally 40% player) List <float> allyDistancesNorm = NormalizeList(allyDistances); List <float> minPlayerDistancesNorm = NormalizeList(minPlayerDistances); float minRecoveryHeuristic = float.MaxValue; int targetHealerIndex = 0; for (int i = 0; i < allyDistancesNorm.Count; i++) { float currentHeuristic = 0.6f * allyDistancesNorm[i] + 0.4f * minPlayerDistancesNorm[i]; if (currentHeuristic < minRecoveryHeuristic) { targetHealerIndex = i; minRecoveryHeuristic = currentHeuristic; } } // go to closest healer Tile targetTile = healers[targetHealerIndex].currentTile; AStarSearch.GeneratePath(map, enemyUnit.currentTile, targetTile, false, true); map.FindSelectableTiles(currUnit.currentTile, enemyUnit.stats[StatString.MOVEMENT_RANGE].Value); yield return(new WaitForSecondsRealtime(0.5f)); /* * while (targetTile.distance > enemyUnit.stats[StatString.MOVEMENT_RANGE].Value) * { * targetTile = targetTile.parent; * } */ while (!targetTile.selectable) { targetTile = targetTile.parent; } currUnit.GetPathToTile(targetTile); yield return(new WaitUntil(() => currUnit.CurrState == UnitState.IDLING)); turnScheduler.SetState(new EnemyEndTurn(turnScheduler)); } // no healing avenues. Run? Or change to aggressive mode else { yield return(new WaitForSecondsRealtime(0.5f)); Retreat(); yield return(new WaitUntil(() => currUnit.CurrState == UnitState.IDLING)); map.RemoveSelectableTiles(currUnit.currentTile); turnScheduler.SetState(new EnemyEndTurn(turnScheduler)); } } }