diff --git a/OpenRA.Mods.RA/HackyAI.cs b/OpenRA.Mods.RA/HackyAI.cs index 25ee874307..087486cc06 100644 --- a/OpenRA.Mods.RA/HackyAI.cs +++ b/OpenRA.Mods.RA/HackyAI.cs @@ -35,28 +35,28 @@ namespace OpenRA.Mods.RA { class HackyAIInfo : ITraitInfo { - [FieldLoader.LoadUsing( "LoadUnits" )] - public readonly Dictionary UnitsToBuild; + [FieldLoader.LoadUsing("LoadUnits")] + public readonly Dictionary UnitsToBuild = null; - [FieldLoader.LoadUsing( "LoadBuildings" )] - public readonly Dictionary BuildingFractions; - - static object LoadUnits( MiniYaml y ) + [FieldLoader.LoadUsing("LoadBuildings")] + public readonly Dictionary BuildingFractions = null; + + static object LoadUnits(MiniYaml y) { - Dictionary ret = new Dictionary(); + Dictionary ret = new Dictionary(); foreach (var t in y.NodesDict["UnitsToBuild"].Nodes) - ret.Add(t.Key, (float)FieldLoader.GetValue("units", typeof(float), t.Value.Value)); + ret.Add(t.Key, (float)FieldLoader.GetValue("units", typeof(float), t.Value.Value)); return ret; } - - static object LoadBuildings( MiniYaml y ) + + static object LoadBuildings(MiniYaml y) { - Dictionary ret = new Dictionary(); + Dictionary ret = new Dictionary(); foreach (var t in y.NodesDict["BuildingFractions"].Nodes) - ret.Add(t.Key, (float)FieldLoader.GetValue("units", typeof(float), t.Value.Value)); + ret.Add(t.Key, (float)FieldLoader.GetValue("units", typeof(float), t.Value.Value)); return ret; } - + public object Create(ActorInitializer init) { return new HackyAI(this); } } @@ -68,9 +68,10 @@ namespace OpenRA.Mods.RA int ticks; Player p; PowerManager playerPower; - + int2 baseCenter; - XRandom random = new XRandom(); //we do not use the synced random number generator. + XRandom random = new XRandom(); //we do not use the synced random number generator. + BaseBuilder[] builders; World world { get { return p.PlayerActor.World; } } @@ -79,7 +80,7 @@ namespace OpenRA.Mods.RA { this.Info = Info; } - + enum BuildState { ChooseItem, @@ -87,13 +88,8 @@ namespace OpenRA.Mods.RA WaitForFeedback, } - int lastThinkTick = 0; - const int MaxBaseDistance = 15; - BuildState bstate = BuildState.WaitForFeedback; - BuildState dstate = BuildState.WaitForFeedback; - public static void BotDebug(string s, params object[] args) { if (Game.Settings.Debug.BotDebug) @@ -106,6 +102,9 @@ namespace OpenRA.Mods.RA this.p = p; enabled = true; playerPower = p.PlayerActor.Trait(); + builders = new BaseBuilder[] { + new BaseBuilder( this, "Building", ChooseBuildingToBuild ), + new BaseBuilder( this, "Defense", ChooseDefenseToBuild ) }; } int GetPowerProvidedBy(ActorInfo building) @@ -115,17 +114,17 @@ namespace OpenRA.Mods.RA return bi.Power; } - ActorInfo ChooseRandomUnitToBuild(ProductionQueue queue) - { - var buildableThings = queue.BuildableItems(); - if (buildableThings.Count() == 0) return null; - return buildableThings.ElementAtOrDefault(random.Next(buildableThings.Count())); - } + ActorInfo ChooseRandomUnitToBuild(ProductionQueue queue) + { + var buildableThings = queue.BuildableItems(); + if (buildableThings.Count() == 0) return null; + return buildableThings.ElementAtOrDefault(random.Next(buildableThings.Count())); + } bool HasAdequatePower() { /* note: CNC `fact` provides a small amount of power. don't get jammed because of that. */ - return playerPower.PowerProvided > 50 && + return playerPower.PowerProvided > 50 && playerPower.PowerProvided > playerPower.PowerDrained * 1.2; } @@ -140,18 +139,13 @@ namespace OpenRA.Mods.RA .OrderByDescending(a => GetPowerProvidedBy(a)).FirstOrDefault(); if (best != null) - { - BotDebug("AI: Need more power, so {0} is best choice.", best.Name); return best; - } - else - BotDebug("AI: Need more power, but can't build anything that produces it."); } var myBuildings = p.World.Queries.OwnedBy[p].WithTrait() - .Select( a => a.Actor.Info.Name ).ToArray(); - - + .Select(a => a.Actor.Info.Name).ToArray(); + + foreach (var frac in Info.BuildingFractions) if (buildableThings.Any(b => b.Name == frac.Key)) if (myBuildings.Count(a => a == frac.Key) < frac.Value * myBuildings.Length) @@ -160,20 +154,20 @@ namespace OpenRA.Mods.RA return null; } - ActorInfo ChooseDefenseToBuild(ProductionQueue queue) - { - var buildableThings = queue.BuildableItems(); + ActorInfo ChooseDefenseToBuild(ProductionQueue queue) + { + var buildableThings = queue.BuildableItems(); - var myBuildings = p.World.Queries.OwnedBy[p].WithTrait() - .Select(a => a.Actor.Info.Name).ToArray(); + var myBuildings = p.World.Queries.OwnedBy[p].WithTrait() + .Select(a => a.Actor.Info.Name).ToArray(); - foreach (var frac in Info.BuildingFractions) - if (buildableThings.Any(b => b.Name == frac.Key)) - if (myBuildings.Count(a => a == frac.Key) < frac.Value * myBuildings.Length) - return Rules.Info[frac.Key]; + foreach (var frac in Info.BuildingFractions) + if (buildableThings.Any(b => b.Name == frac.Key)) + if (myBuildings.Count(a => a == frac.Key) < frac.Value * myBuildings.Length) + return Rules.Info[frac.Key]; - return null; - } + return null; + } int2? ChooseBuildLocation(ProductionItem item) { @@ -197,40 +191,38 @@ namespace OpenRA.Mods.RA ticks++; - if (ticks == 10) - { - DeployMcv(self); - } + if (ticks == 10) + { + DeployMcv(self); + } - 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"); - } + 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"); + } - AssignRolesToIdleUnits(self); - SetRallyPointsForNewProductionBuildings(self); + AssignRolesToIdleUnits(self); + SetRallyPointsForNewProductionBuildings(self); - - BuildBuildings(); - BuildDefense(); - //build Ship + foreach (var b in builders) + b.Tick(); } - //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(); + //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(); + //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(); + //This is purely to identify production buildings that don't have a rally point set. + List activeProductionBuildings = new List(); bool IsHumanPlayer(Player p) { @@ -251,15 +243,15 @@ namespace OpenRA.Mods.RA // 3. not dead. var possibleTargets = world.WorldActor.Trait().Start - .Where(kv => kv.Key != p && (!HasHumanPlayers()|| IsHumanPlayer(kv.Key)) + .Where(kv => kv.Key != p && (!HasHumanPlayers() || IsHumanPlayer(kv.Key)) && p.WinState == WinState.Undefined) .Select(kv => kv.Value); - return possibleTargets.Any() ? possibleTargets.Random(random) : (int2?) null; + return possibleTargets.Any() ? possibleTargets.Random(random) : (int2?)null; } - void AssignRolesToIdleUnits(Actor self) - { + void AssignRolesToIdleUnits(Actor self) + { //HACK: trim these lists -- we really shouldn't be hanging onto all this state //when it's invalidated so easily, but that's Matthew/Alli's problem. activeUnits.RemoveAll(a => a.Destroyed); @@ -267,23 +259,23 @@ namespace OpenRA.Mods.RA attackForce.RemoveAll(a => a.Destroyed); activeProductionBuildings.RemoveAll(a => a.Destroyed); - // don't select harvesters. - var newUnits = self.World.Queries.OwnedBy[p] - .Where(a => a.HasTrait() && a.Info != Rules.Info["harv"] - && !activeUnits.Contains(a)).ToArray(); + // don't select harvesters. + var newUnits = self.World.Queries.OwnedBy[p] + .Where(a => a.HasTrait() && a.Info != Rules.Info["harv"] + && !activeUnits.Contains(a)).ToArray(); - foreach (var a in newUnits) - { + foreach (var a in newUnits) + { BotDebug("AI: Found a newly built unit"); - unitsHangingAroundTheBase.Add(a); - activeUnits.Add(a); - } + 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 > 8) - { - BotDebug("Launch an attack."); + /* Create an attack force when we have enough units around our base. */ + // (don't bother leaving any behind for defense.) + if (unitsHangingAroundTheBase.Count > 8) + { + BotDebug("Launch an attack."); var attackTarget = ChooseEnemyTarget(); if (attackTarget == null) @@ -293,231 +285,186 @@ namespace OpenRA.Mods.RA if (TryToMove(a, attackTarget.Value)) attackForce.Add(a); - unitsHangingAroundTheBase.Clear(); - } - } + unitsHangingAroundTheBase.Clear(); + } + } - private void SetRallyPointsForNewProductionBuildings(Actor self) - { - var newProdBuildings = self.World.Queries.OwnedBy[p] - .Where(a => (a.TraitOrDefault() != null - && !activeProductionBuildings.Contains(a) - )).ToArray(); + void SetRallyPointsForNewProductionBuildings(Actor self) + { + var newProdBuildings = self.World.Queries.OwnedBy[p] + .Where(a => (a.TraitOrDefault() != null + && !activeProductionBuildings.Contains(a) + )).ToArray(); - foreach (var a in newProdBuildings) - { - activeProductionBuildings.Add(a); - int2 newRallyPoint = ChooseRallyLocationNear(a.Location); - newRallyPoint.X += 4; - newRallyPoint.Y += 4; - world.IssueOrder(new Order("SetRallyPoint", a, newRallyPoint)); - } - } + foreach (var a in newProdBuildings) + { + activeProductionBuildings.Add(a); + int2 newRallyPoint = ChooseRallyLocationNear(a.Location); + newRallyPoint.X += 4; + newRallyPoint.Y += 4; + world.IssueOrder(new Order("SetRallyPoint", a, newRallyPoint)); + } + } - //won't work for shipyards... - private int2 ChooseRallyLocationNear(int2 startPos) - { - Random r = new Random(); - foreach (var t in world.FindTilesInCircle(startPos, 8)) - if (world.IsCellBuildable(t, false) && t != startPos && r.Next(64) == 0) - return t; - - return startPos; // i don't know where to put it. - } + //won't work for shipyards... + int2 ChooseRallyLocationNear(int2 startPos) + { + Random r = new Random(); + foreach (var t in world.FindTilesInCircle(startPos, 8)) + if (world.IsCellBuildable(t, false) && t != startPos && r.Next(64) == 0) + return t; - //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) - { + 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? ) + bool TryToMove(Actor a, int2 desiredMoveTarget) + { if (!a.HasTrait()) return false; - 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); - world.IssueOrder(new Order("Move", a, xy)); - return true; - } + 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); + world.IssueOrder(new Order("Move", a, xy)); + return true; + } - private void DeployMcv(Actor self) - { + void DeployMcv(Actor self) + { /* find our mcv and deploy it */ - var mcv = self.World.Queries.OwnedBy[p] - .FirstOrDefault(a => a.Info == Rules.Info["mcv"]); + var mcv = self.World.Queries.OwnedBy[p] + .FirstOrDefault(a => a.Info == Rules.Info["mcv"]); - if (mcv != null) - { - baseCenter = mcv.Location; - world.IssueOrder(new Order("DeployTransform", mcv)); - } - else - BotDebug("AI: Can't find the MCV."); - } + if (mcv != null) + { + baseCenter = mcv.Location; + world.IssueOrder(new Order("DeployTransform", mcv)); + } + else + BotDebug("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) - { + //Build a random unit of the given type. Not going to be needed once there is actual AI... + private void BuildRandom(string category) + { // Pick a free queue var queue = world.Queries.WithTraitMultiple() .Where(a => a.Actor.Owner == p && - a.Trait.Info.Type == category && - a.Trait.CurrentItem() == null) + a.Trait.Info.Type == category && + a.Trait.CurrentItem() == null) .Select(a => a.Trait) .FirstOrDefault(); - + if (queue == null) return; - + var unit = ChooseRandomUnitToBuild(queue); - Boolean found = false; - if (unit != null) - { - foreach (var un in Info.UnitsToBuild) - { - if (un.Key == unit.Name) - { - found = true; - break; - } - } + Boolean found = false; + if (unit != null) + { + foreach (var un in Info.UnitsToBuild) + { + if (un.Key == unit.Name) + { + found = true; + break; + } + } - if (found == true) - { - world.IssueOrder(Order.StartProduction(queue.self, unit.Name, 1)); - } - } - } + if (found == true) + { + world.IssueOrder(Order.StartProduction(queue.self, unit.Name, 1)); + } + } + } - private void BuildBuildings() - { - // Pick a free queue - var queue = world.Queries.WithTraitMultiple() - .Where(a => a.Actor.Owner == p && a.Trait.Info.Type == "Building") - .Select(a => a.Trait) - .FirstOrDefault(); - - if (queue == null) - return; - - var currentBuilding = queue.CurrentItem(); - switch (bstate) - { - case BuildState.ChooseItem: - { - var item = ChooseBuildingToBuild(queue); - if (item == null) - { - bstate = BuildState.WaitForFeedback; - lastThinkTick = ticks; - } - else - { - BotDebug("AI: Starting production of {0}".F(item.Name)); - bstate = BuildState.WaitForProduction; - world.IssueOrder(Order.StartProduction(queue.self, item.Name, 1)); - } - } - break; + class BaseBuilder + { + BuildState state = BuildState.WaitForFeedback; + string category; + HackyAI ai; + int lastThinkTick; + Func chooseItem; - case BuildState.WaitForProduction: - if (currentBuilding == null) return; /* let it happen.. */ + public BaseBuilder(HackyAI ai, string category, Func chooseItem) + { + this.ai = ai; + this.category = category; + this.chooseItem = chooseItem; + } - else if (currentBuilding.Paused) - world.IssueOrder(Order.PauseProduction(queue.self, currentBuilding.Item, false)); - else if (currentBuilding.Done) - { - bstate = BuildState.WaitForFeedback; - lastThinkTick = ticks; + public void Tick() + { + // Pick a free queue + var queue = ai.world.Queries.WithTraitMultiple() + .Where(a => a.Actor.Owner == ai.p && a.Trait.Info.Type == category) + .Select(a => a.Trait) + .FirstOrDefault(); - /* place the building */ - var location = ChooseBuildLocation(currentBuilding); - if (location == null) - { - BotDebug("AI: Nowhere to place {0}".F(currentBuilding.Item)); - world.IssueOrder(Order.CancelProduction(queue.self, currentBuilding.Item, 1)); - } - else - { - world.IssueOrder(new Order("PlaceBuilding", p.PlayerActor, location.Value, currentBuilding.Item)); - } - } - break; + if (queue == null) + return; - case BuildState.WaitForFeedback: - if (ticks - lastThinkTick > feedbackTime) - bstate = BuildState.ChooseItem; - break; - } - } + var currentBuilding = queue.CurrentItem(); + switch (state) + { + case BuildState.ChooseItem: + { + var item = chooseItem(queue); + if (item == null) + { + state = BuildState.WaitForFeedback; + lastThinkTick = ai.ticks; + } + else + { + BotDebug("AI: Starting production of {0}".F(item.Name)); + state = BuildState.WaitForProduction; + ai.world.IssueOrder(Order.StartProduction(queue.self, item.Name, 1)); + } + } + break; - private void BuildDefense() - { - // Pick a free queue - var queue = world.Queries.WithTraitMultiple() - .Where(a => a.Actor.Owner == p && a.Trait.Info.Type == "Defense") - .Select(a => a.Trait) - .FirstOrDefault(); + case BuildState.WaitForProduction: + if (currentBuilding == null) return; /* let it happen.. */ - if (queue == null) - return; + else if (currentBuilding.Paused) + ai.world.IssueOrder(Order.PauseProduction(queue.self, currentBuilding.Item, false)); + else if (currentBuilding.Done) + { + state = BuildState.WaitForFeedback; + lastThinkTick = ai.ticks; - var currentBuilding = queue.CurrentItem(); - switch (dstate) - { - case BuildState.ChooseItem: - { - var item = ChooseDefenseToBuild(queue); - if (item == null) - { - dstate = BuildState.WaitForFeedback; - lastThinkTick = ticks; - } - else - { - BotDebug("AI: Starting production of {0}".F(item.Name)); - dstate = BuildState.WaitForProduction; - world.IssueOrder(Order.StartProduction(queue.self, item.Name, 1)); - } - } - break; + /* place the building */ + var location = ai.ChooseBuildLocation(currentBuilding); + if (location == null) + { + BotDebug("AI: Nowhere to place {0}".F(currentBuilding.Item)); + ai.world.IssueOrder(Order.CancelProduction(queue.self, currentBuilding.Item, 1)); + } + else + { + ai.world.IssueOrder(new Order("PlaceBuilding", ai.p.PlayerActor, + location.Value, currentBuilding.Item)); + } + } + break; - case BuildState.WaitForProduction: - if (currentBuilding == null) return; /* let it happen.. */ - - else if (currentBuilding.Paused) - world.IssueOrder(Order.PauseProduction(queue.self, currentBuilding.Item, false)); - else if (currentBuilding.Done) - { - dstate = BuildState.WaitForFeedback; - lastThinkTick = ticks; - - /* place the building */ - var location = ChooseBuildLocation(currentBuilding); - if (location == null) - { - BotDebug("AI: Nowhere to place {0}".F(currentBuilding.Item)); - world.IssueOrder(Order.CancelProduction(queue.self, currentBuilding.Item, 1)); - } - else - { - world.IssueOrder(new Order("PlaceBuilding", p.PlayerActor, location.Value, currentBuilding.Item)); - } - } - break; - - case BuildState.WaitForFeedback: - if (ticks - lastThinkTick > feedbackTime) - dstate = BuildState.ChooseItem; - break; - } - } + case BuildState.WaitForFeedback: + if (ai.ticks - lastThinkTick > feedbackTime) + state = BuildState.ChooseItem; + break; + } + } + } } }