From 16c31c0b3272c454dd4e80bf76c451c2af546ae4 Mon Sep 17 00:00:00 2001 From: DejaFu Date: Thu, 25 Apr 2019 20:15:54 -0600 Subject: [PATCH 1/2] Update NavigationSubSystem.cs Initial changes for AI taking various actions that reposition the ship. Currently has code for boost, but hasn't been tested, as the AI needs to know what action it needs to take when the time comes to choose. --- .../NavigationSubSystem.cs | 233 +++++++++++++++++- 1 file changed, 232 insertions(+), 1 deletion(-) diff --git a/Assets/Scripts/Model/Ai/Aggressor/NavigationSubSystem/NavigationSubSystem.cs b/Assets/Scripts/Model/Ai/Aggressor/NavigationSubSystem/NavigationSubSystem.cs index e7ae58bb8f..141fd11166 100644 --- a/Assets/Scripts/Model/Ai/Aggressor/NavigationSubSystem/NavigationSubSystem.cs +++ b/Assets/Scripts/Model/Ai/Aggressor/NavigationSubSystem/NavigationSubSystem.cs @@ -1,4 +1,5 @@ -using System; +using ActionsList; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -326,5 +327,235 @@ private static bool IsActivationBeforeCurrentShip(GenericShip ship) || (ship.State.Initiative == CurrentShip.State.Initiative && ship.Owner.PlayerNo == Phases.PlayerWithInitiative && ship.Owner.PlayerNo != CurrentShip.Owner.PlayerNo) || (ship.State.Initiative == CurrentShip.State.Initiative && ship.ShipId < CurrentShip.ShipId && ship.Owner.PlayerNo == CurrentShip.Owner.PlayerNo); } + + // This function will try all moves related to a boost, barrelroll, decloak, SLAM, or tractor. If the action is from Supernatural Reflexes or Advanced Sensors, + // it will also test the maneuver that follows it to see if that is better or worse than without taking the action before it. + public static int TryActionPossibilities(GenericMovement FinalSuggestion, GenericAction actionToTry, bool isBeforeManeuverPhase = false) + { + int result = 0; + GenericShip thisShip = Selection.ActiveShip; + GenericMovement savedMovement = thisShip.AssignedManeuver; + FinalSuggestion = thisShip.AssignedManeuver; + int shieldHullTotal = thisShip.State.ShieldsCurrent + thisShip.State.HullCurrent; + int startingResult = 0; + + NavigationResult StartingPosition = null; + ShipPositionInfo shipOriginalPosition = thisShip.GetPositionInfo(); + + // Prepare our virtual board maneuver tests. + VirtualBoard.SetVirtualPositionInfo(thisShip, shipOriginalPosition); + VirtualBoard.SwitchToVirtualPosition(thisShip); + + + + // Set up our virtual board. + if (VirtualBoard == null) + { + VirtualBoard = new VirtualBoard(); + } + else + { + VirtualBoard.Update(); + } + + // Record our current position for comparison. + if (isBeforeManeuverPhase == true) + { + // Our action is before a maneuver. Find out the results for our maneuver if we don't boost/barrel-roll/decloak before it. + MovementPrediction maneuverWithoutActionFirst = new MovementPrediction(thisShip.AssignedManeuver); + VirtualBoard.SetVirtualPositionInfo(thisShip, maneuverWithoutActionFirst.FinalPositionInfo); + StartingPosition = GetCurrentPositionNavigationInfo(thisShip); + + // Move us back to before the maneuver. + VirtualBoard.SetVirtualPositionInfo(thisShip, shipOriginalPosition); + } + else + { + // Just record our current position. + StartingPosition = GetCurrentPositionNavigationInfo(thisShip); + } + // Determine how good our starting position is. + startingResult = CalculatePositionPriority(StartingPosition); + + // Test for a boost action. + if (actionToTry is BoostAction) + { + + int bestBoostResult = 0; + GenericMovement bestBoostMove = null; + NavigationResult bestBoostNavigation = null; + + NavigationResult currentBoostNavigation = null; + int currentBoostResult = 0; + + // We're performing a boost action. Check all boost action possibilities. + List AvailableBoostMoves = new List(); + AvailableBoostMoves = thisShip.GetAvailableBoostTemplates(); + foreach(BoostMove move in AvailableBoostMoves) + { + string selectedBoostHelper = move.Name; + + MovementPrediction boostPrediction = null; + GenericMovement boostMovement; + // Use the name of our boost action to generate a GenericMovement of the matching type. + switch (selectedBoostHelper) + { + case "Straight 1": + boostMovement = new StraightBoost(1, ManeuverDirection.Forward, ManeuverBearing.Straight, MovementComplexity.None); + break; + case "Bank 1 Left": + boostMovement = new BankBoost(1, ManeuverDirection.Left, ManeuverBearing.Bank, MovementComplexity.None); + break; + case "Bank 1 Right": + boostMovement = new BankBoost(1, ManeuverDirection.Right, ManeuverBearing.Bank, MovementComplexity.None); + break; + case "Turn 1 Right": + boostMovement = new TurnBoost(1, ManeuverDirection.Right, ManeuverBearing.Turn, MovementComplexity.None); + break; + case "Turn 1 Left": + boostMovement = new TurnBoost(1, ManeuverDirection.Left, ManeuverBearing.Turn, MovementComplexity.None); + break; + default: + boostMovement = new StraightBoost(1, ManeuverDirection.Forward, ManeuverBearing.Straight, MovementComplexity.None); + break; + } + + // Predict our collisions and future position. + boostPrediction = new MovementPrediction(boostMovement); + + VirtualBoard.SetVirtualPositionInfo(thisShip, boostPrediction.FinalPositionInfo); + + if(isBeforeManeuverPhase == true) + { + // We need to now perform our maneuver from this new position. + MovementPrediction maneuverAfterAction = new MovementPrediction(thisShip.AssignedManeuver); + VirtualBoard.SetVirtualPositionInfo(thisShip, maneuverAfterAction.FinalPositionInfo); + } + // Find out how good this move is. + currentBoostNavigation = GetCurrentPositionNavigationInfo(thisShip); + currentBoostResult = CalculatePositionPriority(currentBoostNavigation); + + if(move.IsRed == true) + { + // Make red maneuvers a little less optimal. + currentBoostResult -= 250; + } + if (currentBoostResult > bestBoostResult) + { + // We have a new best boost result. + bestBoostResult = currentBoostResult; + bestBoostNavigation = currentBoostNavigation; + bestBoostMove = boostMovement; + } + + // Reset our ship position for the next boost test. + VirtualBoard.SetVirtualPositionInfo(thisShip, shipOriginalPosition); + } + if(bestBoostResult > startingResult) + { + result = bestBoostResult; + FinalSuggestion = bestBoostMove; + } + } + + // Restore our original move. + if (savedMovement != null) + { + thisShip.SetAssignedManeuver(savedMovement, isSilent: true); + } + else + { + thisShip.ClearAssignedManeuver(); + } + + // Put us back to our normal location. + VirtualBoard.SwitchToRealPosition(thisShip); + return result; + } + + // Set navigation information for the current ship's position. + private static NavigationResult GetCurrentPositionNavigationInfo(GenericShip thisShip) + { + NavigationResult currentNavigationResult = new NavigationResult() + { + movement = thisShip.AssignedManeuver, + distanceToNearestEnemy = 0, + distanceToNearestEnemyInShotRange = 0, + enemiesInShotRange = 0, + isBumped = thisShip.IsBumped, + isLandedOnObstacle = thisShip.IsLandedOnObstacle, + obstaclesHit = 0, + isOffTheBoard = false, + minesHit = 0, + isOffTheBoardNextTurn = false, + isHitAsteroidNextTurn = false, + FinalPositionInfo = thisShip.GetPositionInfo() + }; + + int enemiesInShotRange = 0; + + float minDistanceToNearestEnemyInShotRange = 0; + foreach (GenericShip enemyShip in thisShip.Owner.EnemyShips.Values) + { + // Get our weapon shot info for each arc that has a weapon pointing in it. + foreach (IShipWeapon currentWeapon in thisShip.GetAllWeapons()) + { + ShotInfo shotInfo = new ShotInfo(thisShip, enemyShip, currentWeapon); + if (shotInfo.IsShotAvailable) + { + // We only need to find one target in range to pass our requirements (at least one shot available against that enemy with any particular weapon). + enemiesInShotRange++; + if (minDistanceToNearestEnemyInShotRange < shotInfo.DistanceReal) minDistanceToNearestEnemyInShotRange = shotInfo.DistanceReal; + break; + } + } + } + + currentNavigationResult.enemiesInShotRange = enemiesInShotRange; + currentNavigationResult.distanceToNearestEnemyInShotRange = minDistanceToNearestEnemyInShotRange; + + // Find the nearest distance to an enemy ship. + float minDistanceToEnemyShip = float.MaxValue; + foreach (GenericShip enemyShip in thisShip.Owner.EnemyShips.Values) + { + DistanceInfo distInfo = new DistanceInfo(CurrentShip, enemyShip); + if (distInfo.MinDistance.DistanceReal < minDistanceToEnemyShip) minDistanceToEnemyShip = distInfo.MinDistance.DistanceReal; + } + + currentNavigationResult.distanceToNearestEnemy = minDistanceToEnemyShip; + + return currentNavigationResult; + } + + // Determine how good the position we have been passed is. + private static int CalculatePositionPriority(NavigationResult CurrentPosition) + { + int Priority = 0; + + if (CurrentPosition.isOffTheBoard) + { + return 0; + } + if (CurrentPosition.isLandedOnObstacle) Priority -= 10000; + + if (CurrentPosition.isOffTheBoardNextTurn) Priority -= 20000; + + Priority += CurrentPosition.enemiesInShotRange * 1000; + + Priority -= CurrentPosition.obstaclesHit * 2000; + Priority -= CurrentPosition.minesHit * 2000; + + if (CurrentPosition.isHitAsteroidNextTurn) Priority -= 1000; + + if (CurrentPosition.isBumped) + { + // Leave space for testing Arvyl and Zeb. + Priority -= 1000; + } + + //distance is 0..10 + Priority += (10 - (int) CurrentPosition.distanceToNearestEnemy) * 10; + return Priority; + } } } From f518023a46b2d7a8235bd532651ada78330f51b9 Mon Sep 17 00:00:00 2001 From: DejaFu Date: Sat, 27 Apr 2019 19:07:38 -0600 Subject: [PATCH 2/2] AI Boost Action Capability AI will now boost under certain circumstances. Odds to boost improve with: - Fewer enemies being able to attack the ship. - Getting to range 1 against an enemy target that is in their firing arc. - Having at least one enemy they can shoot. --- .../Model/Actions/ActionsList/BoostAction.cs | 95 ++++- .../NavigationSubSystem/NavigationResult.cs | 4 + .../NavigationSubSystem.cs | 355 ++++++++++++------ .../Content/Core/Ship/GenericShipMovement.cs | 10 +- .../MovementPrediction/MovementPrediction.cs | 6 + Assets/Scripts/View/Ship/GenericShip.cs | 102 ++++- 6 files changed, 437 insertions(+), 135 deletions(-) diff --git a/Assets/Scripts/Model/Actions/ActionsList/BoostAction.cs b/Assets/Scripts/Model/Actions/ActionsList/BoostAction.cs index 586d278c7c..c47bccaaa0 100644 --- a/Assets/Scripts/Model/Actions/ActionsList/BoostAction.cs +++ b/Assets/Scripts/Model/Actions/ActionsList/BoostAction.cs @@ -1,13 +1,22 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEngine; +using Actions; +using ActionsList; +using AI; using BoardTools; -using GameModes; -using System.Linq; using Editions; +using GameModes; using Obstacles; -using ActionsList; -using Actions; +using Players; +using Ship; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + + + + + + namespace ActionsList { @@ -44,6 +53,19 @@ public override void RevertActionOnFail(bool hasSecondChance = false) { Phases.GoBack(); } + + public override int GetActionPriority() + { + int result = 0; + + // Check to see if we are before the maneuver phase or not. + bool isBeforeManeuverPhase = !Selection.ActiveShip.AiPlans.shipHasManeuvered; + // Until I get Advanced Sensors fixed... + isBeforeManeuverPhase = false; + result = AI.Aggressor.NavigationSubSystem.TryActionPossibilities(this, isBeforeManeuverPhase); + + return result; + } } public class BoostMove @@ -130,8 +152,23 @@ public void StartBoostPlanning() AvailableBoostMoves = TheShip.GetAvailableBoostTemplates(); InitializeRendering(); - - AskSelectTemplate(); + if (TheShip.Owner.PlayerType == PlayerType.Ai) + { + // We have AI here. Do AI things. + AiSinglePlan aiBoostAction = TheShip.AiPlans.GetPlanByActionName("Boost"); + if (aiBoostAction != null) + { + SelectTemplateByName(aiBoostAction.actionName, aiBoostAction.isRedAction); + + // Now that we're done with the plan, remove it. + TheShip.AiPlans.RemovePlan(aiBoostAction); + } + } + else + { + // We have a player here. Ask them about their boost. + AskSelectTemplate(); + } } private void AskSelectTemplate() @@ -188,6 +225,25 @@ private void SelectTemplate(BoostMove move) DecisionSubPhase.ConfirmDecision(); } + private void SelectTemplateByName(string actionName, bool isRed) + { + if (isRed && !HostAction.IsRed) + { + HostAction.Color = ActionColor.Red; + TheShip.OnActionIsPerformed += ResetActionColor; + } + + SelectedBoostHelper = actionName; + if (TheShip.Owner.PlayerType == PlayerType.Ai) + { + SelectTemplateDecisionIsTaken(); + } + else + { + DecisionSubPhase.ConfirmDecision(); + } + } + private void ResetActionColor(GenericAction action) { action.HostShip.OnActionIsPerformed -= ResetActionColor; @@ -316,7 +372,24 @@ private void GetResults(System.Action canBoostCallback = null) } else { - GameMode.CurrentGameMode.CancelBoost(boostProblems); + if (TheShip.Owner.PlayerType == PlayerType.Ai) + { + // Skip the failed boost action. + GameMode.CurrentGameMode.FinishBoost(); + string tempName = Phases.CurrentSubPhase.Name; + Phases.CurrentSubPhase = Phases.CurrentSubPhase.PreviousSubPhase; + tempName = Phases.CurrentSubPhase.Name; + + UpdateHelpInfo(); + + + + CallBack(); + } + else + { + GameMode.CurrentGameMode.CancelBoost(boostProblems); + } } } @@ -433,6 +506,7 @@ private void FinishBoostAnimation() { Phases.CurrentSubPhase = Phases.CurrentSubPhase.PreviousSubPhase; Phases.CurrentSubPhase = Phases.CurrentSubPhase.PreviousSubPhase; + UpdateHelpInfo(); CallBack(); @@ -449,5 +523,6 @@ public override bool AnotherShipCanBeSelected(Ship.GenericShip anotherShip, int bool result = false; return result; } + } } diff --git a/Assets/Scripts/Model/Ai/Aggressor/NavigationSubSystem/NavigationResult.cs b/Assets/Scripts/Model/Ai/Aggressor/NavigationSubSystem/NavigationResult.cs index 858013e382..063e98c0de 100644 --- a/Assets/Scripts/Model/Ai/Aggressor/NavigationSubSystem/NavigationResult.cs +++ b/Assets/Scripts/Model/Ai/Aggressor/NavigationSubSystem/NavigationResult.cs @@ -12,6 +12,7 @@ public class NavigationResult public bool isLandedOnObstacle; public int enemiesInShotRange; + public int enemiesTargetingThisShip; public int obstaclesHit; public int minesHit; @@ -42,7 +43,10 @@ public void CalculatePriority() if (isOffTheBoardNextTurn) Priority -= 20000; + // Base our priority off of how many enemies can shoot us versus how many we can shoot. Priority += enemiesInShotRange * 1000; + // Allow up to two enemies targeting us to equal one enemy in our attack range. + // Priority -= enemiesTargetingThisShip * 500; Priority -= obstaclesHit * 2000; Priority -= minesHit * 2000; diff --git a/Assets/Scripts/Model/Ai/Aggressor/NavigationSubSystem/NavigationSubSystem.cs b/Assets/Scripts/Model/Ai/Aggressor/NavigationSubSystem/NavigationSubSystem.cs index 141fd11166..5c092eaccf 100644 --- a/Assets/Scripts/Model/Ai/Aggressor/NavigationSubSystem/NavigationSubSystem.cs +++ b/Assets/Scripts/Model/Ai/Aggressor/NavigationSubSystem/NavigationSubSystem.cs @@ -249,14 +249,25 @@ private static IEnumerator ProcessMovementPredicition() //In arc - improve int enemiesInShotRange = 0; + int enemiesThatCanShootUs = 0; float minDistanceToNearestEnemyInShotRange = 0; + ShotInfo enemyCanShootThisShip = null; foreach (GenericShip enemyShip in CurrentShip.Owner.EnemyShips.Values) { + // See if we can shoot this enemy. ShotInfo shotInfo = new ShotInfo(CurrentShip, enemyShip, CurrentShip.PrimaryWeapons.First()); if (shotInfo.IsShotAvailable) { enemiesInShotRange++; if (minDistanceToNearestEnemyInShotRange < shotInfo.DistanceReal) minDistanceToNearestEnemyInShotRange = shotInfo.DistanceReal; + + + } + // See if this enemy can shoot us. + enemyCanShootThisShip = new ShotInfo(enemyShip, CurrentShip, enemyShip.PrimaryWeapons.First()); + if (enemyCanShootThisShip.IsShotAvailable == true) + { + enemiesThatCanShootUs++; } } @@ -266,6 +277,7 @@ private static IEnumerator ProcessMovementPredicition() distanceToNearestEnemy = minDistanceToEnenmyShip, distanceToNearestEnemyInShotRange = minDistanceToNearestEnemyInShotRange, enemiesInShotRange = enemiesInShotRange, + enemiesTargetingThisShip = enemiesThatCanShootUs, isBumped = CurrentMovementPrediction.IsBumped, isLandedOnObstacle = CurrentMovementPrediction.IsLandedOnAsteroid, obstaclesHit = CurrentMovementPrediction.AsteroidsHit.Count, @@ -330,12 +342,12 @@ private static bool IsActivationBeforeCurrentShip(GenericShip ship) // This function will try all moves related to a boost, barrelroll, decloak, SLAM, or tractor. If the action is from Supernatural Reflexes or Advanced Sensors, // it will also test the maneuver that follows it to see if that is better or worse than without taking the action before it. - public static int TryActionPossibilities(GenericMovement FinalSuggestion, GenericAction actionToTry, bool isBeforeManeuverPhase = false) + public static int TryActionPossibilities(GenericAction actionToTry, bool isBeforeManeuverPhase = false) { + VirtualBoard myBoard = new VirtualBoard(); int result = 0; GenericShip thisShip = Selection.ActiveShip; GenericMovement savedMovement = thisShip.AssignedManeuver; - FinalSuggestion = thisShip.AssignedManeuver; int shieldHullTotal = thisShip.State.ShieldsCurrent + thisShip.State.HullCurrent; int startingResult = 0; @@ -343,119 +355,35 @@ public static int TryActionPossibilities(GenericMovement FinalSuggestion, Generi ShipPositionInfo shipOriginalPosition = thisShip.GetPositionInfo(); // Prepare our virtual board maneuver tests. - VirtualBoard.SetVirtualPositionInfo(thisShip, shipOriginalPosition); - VirtualBoard.SwitchToVirtualPosition(thisShip); - - - - // Set up our virtual board. - if (VirtualBoard == null) - { - VirtualBoard = new VirtualBoard(); - } - else - { - VirtualBoard.Update(); - } + myBoard.UpdatePositionInfo(thisShip); // Record our current position for comparison. if (isBeforeManeuverPhase == true) { // Our action is before a maneuver. Find out the results for our maneuver if we don't boost/barrel-roll/decloak before it. MovementPrediction maneuverWithoutActionFirst = new MovementPrediction(thisShip.AssignedManeuver); - VirtualBoard.SetVirtualPositionInfo(thisShip, maneuverWithoutActionFirst.FinalPositionInfo); - StartingPosition = GetCurrentPositionNavigationInfo(thisShip); + myBoard.SetVirtualPositionInfo(thisShip, maneuverWithoutActionFirst.FinalPositionInfo); + myBoard.SwitchToVirtualPosition(thisShip); + StartingPosition = GetCurrentPositionNavigationInfo(thisShip, maneuverWithoutActionFirst); + myBoard.SwitchToRealPosition(thisShip); // Move us back to before the maneuver. - VirtualBoard.SetVirtualPositionInfo(thisShip, shipOriginalPosition); + myBoard.SetVirtualPositionInfo(thisShip, shipOriginalPosition); } else { // Just record our current position. + myBoard.SwitchToVirtualPosition(thisShip); StartingPosition = GetCurrentPositionNavigationInfo(thisShip); + myBoard.SwitchToRealPosition(thisShip); } - // Determine how good our starting position is. - startingResult = CalculatePositionPriority(StartingPosition); // Test for a boost action. if (actionToTry is BoostAction) { - - int bestBoostResult = 0; - GenericMovement bestBoostMove = null; - NavigationResult bestBoostNavigation = null; - - NavigationResult currentBoostNavigation = null; - int currentBoostResult = 0; - - // We're performing a boost action. Check all boost action possibilities. - List AvailableBoostMoves = new List(); - AvailableBoostMoves = thisShip.GetAvailableBoostTemplates(); - foreach(BoostMove move in AvailableBoostMoves) - { - string selectedBoostHelper = move.Name; - - MovementPrediction boostPrediction = null; - GenericMovement boostMovement; - // Use the name of our boost action to generate a GenericMovement of the matching type. - switch (selectedBoostHelper) - { - case "Straight 1": - boostMovement = new StraightBoost(1, ManeuverDirection.Forward, ManeuverBearing.Straight, MovementComplexity.None); - break; - case "Bank 1 Left": - boostMovement = new BankBoost(1, ManeuverDirection.Left, ManeuverBearing.Bank, MovementComplexity.None); - break; - case "Bank 1 Right": - boostMovement = new BankBoost(1, ManeuverDirection.Right, ManeuverBearing.Bank, MovementComplexity.None); - break; - case "Turn 1 Right": - boostMovement = new TurnBoost(1, ManeuverDirection.Right, ManeuverBearing.Turn, MovementComplexity.None); - break; - case "Turn 1 Left": - boostMovement = new TurnBoost(1, ManeuverDirection.Left, ManeuverBearing.Turn, MovementComplexity.None); - break; - default: - boostMovement = new StraightBoost(1, ManeuverDirection.Forward, ManeuverBearing.Straight, MovementComplexity.None); - break; - } - - // Predict our collisions and future position. - boostPrediction = new MovementPrediction(boostMovement); - - VirtualBoard.SetVirtualPositionInfo(thisShip, boostPrediction.FinalPositionInfo); - - if(isBeforeManeuverPhase == true) - { - // We need to now perform our maneuver from this new position. - MovementPrediction maneuverAfterAction = new MovementPrediction(thisShip.AssignedManeuver); - VirtualBoard.SetVirtualPositionInfo(thisShip, maneuverAfterAction.FinalPositionInfo); - } - // Find out how good this move is. - currentBoostNavigation = GetCurrentPositionNavigationInfo(thisShip); - currentBoostResult = CalculatePositionPriority(currentBoostNavigation); - - if(move.IsRed == true) - { - // Make red maneuvers a little less optimal. - currentBoostResult -= 250; - } - if (currentBoostResult > bestBoostResult) - { - // We have a new best boost result. - bestBoostResult = currentBoostResult; - bestBoostNavigation = currentBoostNavigation; - bestBoostMove = boostMovement; - } - - // Reset our ship position for the next boost test. - VirtualBoard.SetVirtualPositionInfo(thisShip, shipOriginalPosition); - } - if(bestBoostResult > startingResult) - { - result = bestBoostResult; - FinalSuggestion = bestBoostMove; - } + // Determine how good our starting position is. + startingResult = CalculateBoostPositionPriority(StartingPosition); + result = TryBoostAction(myBoard, thisShip, shipOriginalPosition, StartingPosition, startingResult, isBeforeManeuverPhase); } // Restore our original move. @@ -468,13 +396,11 @@ public static int TryActionPossibilities(GenericMovement FinalSuggestion, Generi thisShip.ClearAssignedManeuver(); } - // Put us back to our normal location. - VirtualBoard.SwitchToRealPosition(thisShip); return result; } // Set navigation information for the current ship's position. - private static NavigationResult GetCurrentPositionNavigationInfo(GenericShip thisShip) + private static NavigationResult GetCurrentPositionNavigationInfo(GenericShip thisShip, MovementPrediction firstMovement = null, MovementPrediction secondMovement = null) { NavigationResult currentNavigationResult = new NavigationResult() { @@ -482,6 +408,7 @@ private static NavigationResult GetCurrentPositionNavigationInfo(GenericShip thi distanceToNearestEnemy = 0, distanceToNearestEnemyInShotRange = 0, enemiesInShotRange = 0, + enemiesTargetingThisShip = 0, isBumped = thisShip.IsBumped, isLandedOnObstacle = thisShip.IsLandedOnObstacle, obstaclesHit = 0, @@ -492,21 +419,48 @@ private static NavigationResult GetCurrentPositionNavigationInfo(GenericShip thi FinalPositionInfo = thisShip.GetPositionInfo() }; + // Duplicate all information we can from our movement prediction(s). + if(firstMovement != null) + { + if(secondMovement != null) + { + currentNavigationResult.movement = secondMovement.CurrentMovement; + currentNavigationResult.isBumped = (secondMovement.IsBumped || firstMovement.IsBumped); + currentNavigationResult.isLandedOnObstacle = (secondMovement.IsLandedOnAsteroid || firstMovement.IsLandedOnAsteroid); + currentNavigationResult.obstaclesHit = (secondMovement.AsteroidsHit.Count + firstMovement.AsteroidsHit.Count); + currentNavigationResult.isOffTheBoard = (secondMovement.IsOffTheBoard || firstMovement.IsOffTheBoard); + currentNavigationResult.minesHit = (secondMovement.MinesHit.Count + firstMovement.MinesHit.Count); + } + else + { + currentNavigationResult.movement = firstMovement.CurrentMovement; + currentNavigationResult.isBumped = firstMovement.IsBumped; + currentNavigationResult.isLandedOnObstacle = firstMovement.IsLandedOnAsteroid; + currentNavigationResult.obstaclesHit = firstMovement.AsteroidsHit.Count; + currentNavigationResult.isOffTheBoard = firstMovement.IsOffTheBoard; + currentNavigationResult.minesHit = firstMovement.MinesHit.Count; + } + } + int enemiesInShotRange = 0; - float minDistanceToNearestEnemyInShotRange = 0; + float minDistanceToNearestEnemyInShotRange = float.MaxValue; + CurrentShip = thisShip; foreach (GenericShip enemyShip in thisShip.Owner.EnemyShips.Values) { - // Get our weapon shot info for each arc that has a weapon pointing in it. - foreach (IShipWeapon currentWeapon in thisShip.GetAllWeapons()) + if (IsActivationBeforeCurrentShip(enemyShip)) { - ShotInfo shotInfo = new ShotInfo(thisShip, enemyShip, currentWeapon); - if (shotInfo.IsShotAvailable) + // This enemy acts before us. They won't be able to leave our targeting arc. + // Get our weapon shot info for each arc that has a weapon pointing in it. + foreach (IShipWeapon currentWeapon in thisShip.GetAllWeapons()) { - // We only need to find one target in range to pass our requirements (at least one shot available against that enemy with any particular weapon). - enemiesInShotRange++; - if (minDistanceToNearestEnemyInShotRange < shotInfo.DistanceReal) minDistanceToNearestEnemyInShotRange = shotInfo.DistanceReal; - break; + ShotInfo shotInfo = new ShotInfo(thisShip, enemyShip, currentWeapon); + if (shotInfo.IsShotAvailable) + { + // We are looking for our closest enemy target. + enemiesInShotRange++; + if (minDistanceToNearestEnemyInShotRange > shotInfo.DistanceReal) minDistanceToNearestEnemyInShotRange = shotInfo.DistanceReal; + } } } } @@ -516,10 +470,30 @@ private static NavigationResult GetCurrentPositionNavigationInfo(GenericShip thi // Find the nearest distance to an enemy ship. float minDistanceToEnemyShip = float.MaxValue; + ShotInfo enemyCanShootThisShip = null; foreach (GenericShip enemyShip in thisShip.Owner.EnemyShips.Values) { DistanceInfo distInfo = new DistanceInfo(CurrentShip, enemyShip); - if (distInfo.MinDistance.DistanceReal < minDistanceToEnemyShip) minDistanceToEnemyShip = distInfo.MinDistance.DistanceReal; + if (distInfo.MinDistance.DistanceReal < minDistanceToEnemyShip) + { + if(distInfo.MinDistance.DistanceReal != 0 || thisShip.IsBumped == true) + { + // A value of 0 is false unless we've bumped. + minDistanceToEnemyShip = distInfo.MinDistance.DistanceReal; + } + + } + + // See if this enemy can shoot us with any of its weapons. + foreach (IShipWeapon currentWeapon in thisShip.GetAllWeapons()) + { + enemyCanShootThisShip = new ShotInfo(enemyShip, thisShip, currentWeapon); + if (enemyCanShootThisShip.IsShotAvailable == true) + { + currentNavigationResult.enemiesTargetingThisShip++; + break; + } + } } currentNavigationResult.distanceToNearestEnemy = minDistanceToEnemyShip; @@ -528,34 +502,175 @@ private static NavigationResult GetCurrentPositionNavigationInfo(GenericShip thi } // Determine how good the position we have been passed is. - private static int CalculatePositionPriority(NavigationResult CurrentPosition) + private static int CalculateBoostPositionPriority(NavigationResult CurrentPosition) { int Priority = 0; - if (CurrentPosition.isOffTheBoard) { return 0; } - if (CurrentPosition.isLandedOnObstacle) Priority -= 10000; + if (CurrentPosition.isLandedOnObstacle) Priority -= 20000; if (CurrentPosition.isOffTheBoardNextTurn) Priority -= 20000; - Priority += CurrentPosition.enemiesInShotRange * 1000; + // Base our priority off of how many enemies can shoot us versus how many we can shoot. + if (CurrentPosition.enemiesInShotRange > 0) + { + // We have at least one enemy in shot range. We don't need to maximize our targets. + Priority += 20; + if(CurrentPosition.distanceToNearestEnemyInShotRange < 1) + { + // We are at range 1 of a target? + Priority += 10; + } + } + // Reduce our priority by the number of enemies that can still target us after the action. + Priority -= CurrentPosition.enemiesTargetingThisShip * 40; + if(CurrentPosition.enemiesTargetingThisShip == 0) + { + // Any position that leads to no-one attacking us is a pretty good position. + Priority += 10; + if(CurrentPosition.enemiesInShotRange > 0) + { + // We have no-one targeting us and at least one target in range. A really good action! + Priority += 30; + } + } - Priority -= CurrentPosition.obstaclesHit * 2000; + if (CurrentPosition.obstaclesHit > 0) + { + Priority -= CurrentPosition.obstaclesHit * 2000; + } Priority -= CurrentPosition.minesHit * 2000; - if (CurrentPosition.isHitAsteroidNextTurn) Priority -= 1000; - if (CurrentPosition.isBumped) { - // Leave space for testing Arvyl and Zeb. + // Leave space for testing Arvyl and Zeb. Otherwise, we want to avoid this. Priority -= 1000; } - //distance is 0..10 - Priority += (10 - (int) CurrentPosition.distanceToNearestEnemy) * 10; + // Set our priority between 0 and 90. + if(Priority < 0) + { + Priority = 0; + } + return Priority; } + + // Given a ship, their starting position, their current navigation results and its priority, determine which, if any, boost action is best for us. If this action + // takes place before the maneuver phase, calculate our results based on our chosen maneuver. + private static int TryBoostAction(VirtualBoard myBoard, GenericShip thisShip, ShipPositionInfo shipOriginalPosition, NavigationResult StartingPosition, int startingResult, bool isBeforeManeuverPhase = false) + { + int result = 0; + int bestBoostResult = 0; + string bestBoostName = "1.S"; + GenericMovement bestBoostMove = null; + NavigationResult bestBoostNavigation = null; + AiSinglePlan bestPlan = new AiSinglePlan(); + + NavigationResult currentBoostNavigation = null; + int currentBoostResult = 0; + bool bestMoveStresses = false; + + // We're performing a boost action. Check all boost action possibilities. + List AvailableBoostMoves = new List(); + AvailableBoostMoves = thisShip.GetAvailableBoostTemplates(); + int numBoostMoves = AvailableBoostMoves.Count(); + + foreach (BoostMove move in AvailableBoostMoves) + { + string selectedBoostHelper = move.Name; + + MovementPrediction boostPrediction = null; + GenericMovement boostMovement; + // Use the name of our boost action to generate a GenericMovement of the matching type. + switch (selectedBoostHelper) + { + case "Straight 1": + boostMovement = new StraightBoost(1, ManeuverDirection.Forward, ManeuverBearing.Straight, MovementComplexity.None); + break; + case "Bank 1 Left": + boostMovement = new BankBoost(1, ManeuverDirection.Left, ManeuverBearing.Bank, MovementComplexity.None); + break; + case "Bank 1 Right": + boostMovement = new BankBoost(1, ManeuverDirection.Right, ManeuverBearing.Bank, MovementComplexity.None); ; + break; + case "Turn 1 Right": + boostMovement = new TurnBoost(1, ManeuverDirection.Right, ManeuverBearing.Turn, MovementComplexity.None); + break; + case "Turn 1 Left": + boostMovement = new TurnBoost(1, ManeuverDirection.Left, ManeuverBearing.Turn, MovementComplexity.None); + break; + default: + boostMovement = new StraightBoost(1, ManeuverDirection.Forward, ManeuverBearing.Straight, MovementComplexity.None); + break; + } + + // Predict our collisions and future position. + boostMovement.Initialize(); + boostPrediction = new MovementPrediction(boostMovement); + boostPrediction.CalculateMovePredictionFast(); + + myBoard.SetVirtualPositionInfo(thisShip, boostPrediction.FinalPositionInfo); + MovementPrediction maneuverAfterAction = null; + if (isBeforeManeuverPhase == true) + { + // We need to now perform our maneuver from this new position. + maneuverAfterAction = new MovementPrediction(thisShip.AssignedManeuver); + maneuverAfterAction.CalculateMovePredictionFast(); + myBoard.SetVirtualPositionInfo(thisShip, maneuverAfterAction.FinalPositionInfo); + } + + // Find out how good this move is. + myBoard.SwitchToVirtualPosition(thisShip); + + currentBoostNavigation = GetCurrentPositionNavigationInfo(thisShip, boostPrediction, maneuverAfterAction); + myBoard.SwitchToRealPosition(thisShip); + + if (currentBoostNavigation == null) + { + currentBoostResult = -20000; + } + currentBoostResult = CalculateBoostPositionPriority(currentBoostNavigation); + + if (move.IsRed == true) + { + // Make red maneuvers a little less optimal. + currentBoostResult -= 10; + } + + if (currentBoostResult > bestBoostResult) + { + // We have a new best boost result. + bestBoostResult = currentBoostResult; + bestBoostNavigation = currentBoostNavigation; + bestBoostMove = boostMovement; + bestMoveStresses = move.IsRed; + bestBoostName = selectedBoostHelper; + } + + + // Reset our ship position for the next boost test. + myBoard.SetVirtualPositionInfo(thisShip, shipOriginalPosition); + } + if (bestBoostResult > startingResult) + { + // Boosting is better than staying here! + // Since the navigation results can reach a maximum of 8,000 (8 enemies in range, none targeting us), we need the range to be up to 100 to match other priorities. + + result = bestBoostResult; + bestPlan.Priority = bestBoostResult; + bestPlan.currentAction = new BoostAction(); + bestPlan.currentActionMove = bestBoostMove; + bestPlan.actionName = bestBoostName; + bestPlan.isRedAction = bestMoveStresses; + + // Give our ship our new plan. + thisShip.AiPlans.AddPlan(bestPlan); + } + + return result; + } } } diff --git a/Assets/Scripts/Model/Content/Core/Ship/GenericShipMovement.cs b/Assets/Scripts/Model/Content/Core/Ship/GenericShipMovement.cs index 7fbb0b6f4d..fb75afb1cc 100644 --- a/Assets/Scripts/Model/Content/Core/Ship/GenericShipMovement.cs +++ b/Assets/Scripts/Model/Content/Core/Ship/GenericShipMovement.cs @@ -117,7 +117,10 @@ public void CallManeuverIsRevealed(System.Action callBack) { if (OnManeuverIsRevealed != null) OnManeuverIsRevealed(this); if (OnManeuverIsRevealedGlobal != null) OnManeuverIsRevealedGlobal(this); - + if (Selection.ThisShip.AiPlans != null) + { + Selection.ThisShip.AiPlans.shipHasManeuvered = false; + } Triggers.ResolveTriggers(TriggerTypes.OnManeuverIsRevealed, callBack); } else // For ionized ships @@ -131,7 +134,10 @@ public void CallManeuverIsRevealed(System.Action callBack) public void StartMoving(System.Action callback) { if (OnMovementStart != null) OnMovementStart(this); - + if (Selection.ThisShip.AiPlans != null) + { + Selection.ThisShip.AiPlans.shipHasManeuvered = true; + } Triggers.ResolveTriggers(TriggerTypes.OnMovementStart, callback); } diff --git a/Assets/Scripts/Model/Movement/MovementPrediction/MovementPrediction.cs b/Assets/Scripts/Model/Movement/MovementPrediction/MovementPrediction.cs index 18604d7a59..af0eaf3df3 100644 --- a/Assets/Scripts/Model/Movement/MovementPrediction/MovementPrediction.cs +++ b/Assets/Scripts/Model/Movement/MovementPrediction/MovementPrediction.cs @@ -41,6 +41,12 @@ public MovementPrediction(GenericMovement movement) GenerateShipStands(); } + public void CalculateMovePredictionFast() + { + WaitForFrames(4); + GetResults(); + } + public IEnumerator CalculateMovementPredicition() { yield return UpdateColisionDetectionAlt(); diff --git a/Assets/Scripts/View/Ship/GenericShip.cs b/Assets/Scripts/View/Ship/GenericShip.cs index 088dcfd015..4b26ddbdeb 100644 --- a/Assets/Scripts/View/Ship/GenericShip.cs +++ b/Assets/Scripts/View/Ship/GenericShip.cs @@ -1,6 +1,8 @@ -using Arcs; +using ActionsList; +using Arcs; using BoardTools; using Editions; +using Movement; using System.Collections; using System.Collections.Generic; using System.IO; @@ -15,6 +17,8 @@ public partial class GenericShip private Transform shipAllParts; private Transform modelCenter; + public AiPlansStorage AiPlans = new AiPlansStorage(); + private string originalSkinName; protected string SpecialModel; @@ -399,8 +403,6 @@ public void HighlightThisSelected() projector.GetComponent().material = (Material)Resources.Load("Projectors/Materials/SelectionThisProjector", typeof(Material)); } - - public void HighlightAnyHovered() { Transform projector = GetSelectionProjector(); @@ -576,4 +578,98 @@ public string FixTypeName(string inputName) } + public class AiPlansStorage + { + private List PlansList = new List(); + public bool shipHasManeuvered = false; + + public AiSinglePlan GetPlannedAction() + { + if (PlansList.Count != 0) + { + OrderByPriority(); + return PlansList.First(); + } + else + { + return null; + } + } + + public int GetActionsCount() + { + return PlansList.Count(); + } + + public AiSinglePlan GetPlanByActionName(string actionName) + { + AiSinglePlan chosenPlan = null; + if (PlansList.Count != 0) + { + foreach (AiSinglePlan plan in PlansList) + { + if (plan.currentAction.Name == actionName) + { + chosenPlan = plan; + break; + } + } + } + return chosenPlan; + } + + public void OrderByPriority() + { + // Put the highest priority action first. + PlansList.OrderByDescending(plan => plan.Priority); + } + + public void AddPlan(AiSinglePlan newPlan) + { + PlansList.Add(newPlan); + } + + public void RemovePlan(AiSinglePlan Plan) + { + if (PlansList.Count > 0) + { + PlansList.Remove(Plan); + } + } + + public void ClearPlans() + { + PlansList.Clear(); + } + } + + public class AiSinglePlan + { + public int Priority = 0; + public string actionName; + public bool isRedAction = false; + public GenericAction currentAction = null; + public GenericMovement currentActionMove = null; + private List preferredTargets = new List(); + + public void AddTarget(GenericShip newTarget) + { + preferredTargets.Add(newTarget); + } + + public void RemoveTarget(GenericShip ship) + { + preferredTargets.Remove(ship); + } + + public void ClearTargetList() + { + preferredTargets.Clear(); + } + + public GenericShip GetFirstTarget() + { + return preferredTargets.First(); + } + } }