diff --git a/OpenRA.Mods.RA/World/HackyAI.cs b/OpenRA.Mods.RA/World/HackyAI.cs index bd39f75dfb..e8be41d225 100644 --- a/OpenRA.Mods.RA/World/HackyAI.cs +++ b/OpenRA.Mods.RA/World/HackyAI.cs @@ -1,22 +1,38 @@ -using System.Linq; +using System.Linq; using OpenRA.Traits; using System; using System.Collections.Generic; + +//TODO: +// [y] never give harvesters orders +// maybe move rally points when a rally point gets blocked (by units or buildings) +// Don't send attack forces to your own spawn point +// effectively clear the area around the production buildings' spawn points. +// don't spam the build unit button, only queue one unit then wait for the backoff period. +// just make the build unit action only occur once every second. +// build defense buildings + +// later: +// don't build units randomly, have a method to it. +// explore spawn points methodically +// once you find a player, attack the player instead of spawn points. + namespace OpenRA.Mods.RA { class HackyAIInfo : TraitInfo { } - /* a pile of hacks, which control the local player on the host. */ + /* a pile of hacks, which control a local player on the host. */ class HackyAI : IGameStarted, ITick { bool enabled; int ticks; Player p; - ProductionQueue pq; - PlayerResources pr; + ProductionQueue productionQueue; + PlayerResources playerResources; int2 baseCenter; + Random random = new Random(); //we do not use the synced random number generator. Dictionary buildingFractions = new Dictionary { @@ -60,8 +76,8 @@ namespace OpenRA.Mods.RA enabled = Game.IsHost && p != null; if (enabled) { - pq = p.PlayerActor.Trait(); - pr = p.PlayerActor.Trait(); + productionQueue = p.PlayerActor.Trait(); + playerResources = p.PlayerActor.Trait(); } } @@ -72,11 +88,18 @@ namespace OpenRA.Mods.RA return bi.Power; } - string ChooseItemToBuild() + string ChooseRandomUnitToBuild(string category) + { + var buildableThings = Rules.TechTree.BuildableItems(p, category).ToArray(); + if (buildableThings.Length == 0) return null; + return buildableThings[random.Next(buildableThings.Length)]; + } + + string ChooseBuildingToBuild() { var buildableThings = Rules.TechTree.BuildableItems(p, "Building").ToArray(); - if (pr.PowerProvided <= pr.PowerDrained * 1.2) /* try to maintain 20% excess power */ + if (playerResources.PowerProvided <= playerResources.PowerDrained * 1.2) /* try to maintain 20% excess power */ { /* find the best thing we can build which produces power */ var best = buildableThings.Where(a => GetPowerProvidedBy(a) > 0) @@ -124,69 +147,201 @@ namespace OpenRA.Mods.RA ticks++; - if (ticks == 10) - { - /* find our mcv and deploy it */ - var mcv = self.World.Queries.OwnedBy[p] - .FirstOrDefault(a => a.Info == Rules.Info["mcv"]); + if (ticks == 10) + { + DeployMcv(self); + } - if (mcv != null) - { - baseCenter = mcv.Location; - Game.IssueOrder(new Order("DeployTransform", mcv)); - } - else - Game.Debug("AI: Can't find the MCV."); - } + if (ticks % feedbackTime == 0) + { + //about once every second, perform unintelligent cleanup tasks. + //e.g. ClearAreaAroundSpawnPoints(); + //e.g. start repairing damaged buildings. + BuildRandom("Vehicle"); + BuildRandom("Infantry"); + BuildRandom("Plane"); + } - var currentBuilding = pq.CurrentItem("Building"); - switch (state) - { - case BuildState.ChooseItem: - { - var item = ChooseItemToBuild(); - if (item == null) - { - state = BuildState.WaitForFeedback; - lastThinkTick = ticks; - } - else - { - state = BuildState.WaitForProduction; - Game.IssueOrder(Order.StartProduction(p, item, 1)); - } - } - break; + AssignRolesToIdleUnits(self); + SetRallyPointsForNewProductionBuildings(self); - case BuildState.WaitForProduction: - if (currentBuilding == null) return; /* let it happen.. */ - else if (currentBuilding.Paused) - Game.IssueOrder(Order.PauseProduction(p, currentBuilding.Item, false)); - else if (currentBuilding.Done) - { - state = BuildState.WaitForFeedback; - lastThinkTick = ticks; - - /* place the building */ - var location = ChooseBuildLocation(currentBuilding); - if (location == null) - { - Game.Debug("AI: Nowhere to place {0}".F(currentBuilding.Item)); - Game.IssueOrder(Order.CancelProduction(p, currentBuilding.Item)); - } - else - { - Game.IssueOrder(new Order("PlaceBuilding", p.PlayerActor, location.Value, currentBuilding.Item)); - } - } - break; - - case BuildState.WaitForFeedback: - if (ticks - lastThinkTick > feedbackTime) - state = BuildState.ChooseItem; - break; - } + BuildBuildings(); + //build Defense + //build Ship } - } + + //hacks etc sigh mess. + //A bunch of hardcoded lists to keep track of which units are doing what. + List unitsHangingAroundTheBase = new List(); + List attackForce = new List(); + + //Units that the ai already knows about. Any unit not on this list needs to be given a role. + List activeUnits = new List(); + + //This is purely to identify production buildings that don't have a rally point set. + List activeProductionBuildings = new List(); + + private void AssignRolesToIdleUnits(Actor self) + { + //don't select harvesters. + var newUnits = self.World.Queries.OwnedBy[p] + .Where(a => ((a.Info.Category == "Infantry" || a.Info.Category == "Vehicle") + && a.Info != Rules.Info["harv"] + && !activeUnits.Contains(a))).ToArray(); + + foreach (var a in newUnits) + { + Game.Debug("AI: Found a newly built unit"); + unitsHangingAroundTheBase.Add(a); + activeUnits.Add(a); + } + + /* Create an attack force when we have enough units around our base. */ + // (don't bother leaving any behind for defense.) + if (unitsHangingAroundTheBase.Count > 5) + { + Game.Debug("Launch an attack."); + int2[] spawnPoints = Game.world.Map.SpawnPoints.ToArray(); + // At the start of the game, all you can do is investigate each spawn point + // until you learn where some other players are. + // this sometimes sends an attack to the bot's own spawn point, + // which is a leading cause of blocking the spawn points :( + int2 attackTarget = spawnPoints[random.Next(0, spawnPoints.Length)]; + foreach (var a in unitsHangingAroundTheBase) + { + attackForce.Add(a); + tryToMove(a, attackTarget); + } + unitsHangingAroundTheBase.Clear(); + } + } + + private void SetRallyPointsForNewProductionBuildings(Actor self) + { + var newProdBuildings = self.World.Queries.OwnedBy[p] + .Where(a => (a.Info.Category == "Building" + && a.TraitOrDefault() != null + && !activeProductionBuildings.Contains(a))).ToArray(); + + foreach (var a in newProdBuildings) + { + activeProductionBuildings.Add(a); + var rp = self.TraitOrDefault(); + int2 newRallyPoint = ChooseRallyLocationNear(a.Location); + Game.IssueOrder(new Order("SetRallyPoint", a, newRallyPoint)); + } + } + + //won't work for shipyards... + private int2 ChooseRallyLocationNear(int2 startPos) + { + foreach (var t in Game.world.FindTilesInCircle(startPos, 6)) + if (Game.world.IsCellBuildable(t, false) && t != startPos) + return t; + + return startPos; // i don't know where to put it. + } + + //try very hard to find a valid move destination near the target. + //(Don't accept a move onto the subject's current position. maybe this is already not allowed? ) + private bool tryToMove(Actor a, int2 desiredMoveTarget) + { + int2 xy; + int loopCount = 0; //avoid infinite loops. + int range = 2; + do + { + //loop until we find a valid move location + xy = new int2(desiredMoveTarget.X + random.Next(-range, range), desiredMoveTarget.Y + random.Next(-range, range)); + loopCount++; + range = Math.Max(range, loopCount / 2); + if (loopCount > 10) return false; + } while (!a.Trait().CanEnterCell(xy) && xy != a.Location); + Game.IssueOrder(new Order("Move", a, xy)); + return true; + } + + private void DeployMcv(Actor self) + { + /* find our mcv and deploy it */ + var mcv = self.World.Queries.OwnedBy[p] + .FirstOrDefault(a => a.Info == Rules.Info["mcv"]); + + if (mcv != null) + { + baseCenter = mcv.Location; + Game.IssueOrder(new Order("DeployTransform", mcv)); + } + else + Game.Debug("AI: Can't find the MCV."); + } + + //Build a random unit of the given type. Not going to be needed once there is actual AI... + private void BuildRandom(string category) + { + var unitInProduction = productionQueue.CurrentItem(category); + if (unitInProduction == null) + { + var unit = ChooseRandomUnitToBuild(category); + if (unit != null) + { + Game.IssueOrder(Order.StartProduction(p, unit, 1)); + } + } + } + + private void BuildBuildings() + { + var currentBuilding = productionQueue.CurrentItem("Building"); + switch (state) + { + case BuildState.ChooseItem: + { + var item = ChooseBuildingToBuild(); + if (item == null) + { + state = BuildState.WaitForFeedback; + lastThinkTick = ticks; + } + else + { + state = BuildState.WaitForProduction; + Game.IssueOrder(Order.StartProduction(p, item, 1)); + } + } + break; + + case BuildState.WaitForProduction: + if (currentBuilding == null) return; /* let it happen.. */ + + else if (currentBuilding.Paused) + Game.IssueOrder(Order.PauseProduction(p, currentBuilding.Item, false)); + else if (currentBuilding.Done) + { + state = BuildState.WaitForFeedback; + lastThinkTick = ticks; + + /* place the building */ + var location = ChooseBuildLocation(currentBuilding); + if (location == null) + { + Game.Debug("AI: Nowhere to place {0}".F(currentBuilding.Item)); + Game.IssueOrder(Order.CancelProduction(p, currentBuilding.Item)); + } + else + { + Game.IssueOrder(new Order("PlaceBuilding", p.PlayerActor, location.Value, currentBuilding.Item)); + } + } + break; + + case BuildState.WaitForFeedback: + if (ticks - lastThinkTick > feedbackTime) + state = BuildState.ChooseItem; + break; + } + } + + } }