diff --git a/OpenRA.Mods.Common/AI/BaseBuilder.cs b/OpenRA.Mods.Common/AI/BaseBuilder.cs index 8b5d29477f..e70fa4656e 100644 --- a/OpenRA.Mods.Common/AI/BaseBuilder.cs +++ b/OpenRA.Mods.Common/AI/BaseBuilder.cs @@ -70,7 +70,7 @@ namespace OpenRA.Mods.Common.AI return false; HackyAI.BotDebug("AI: {0} is starting production of {1}".F(player, item.Name)); - world.IssueOrder(Order.StartProduction(queue.Actor, item.Name, 1)); + ai.QueueOrder(Order.StartProduction(queue.Actor, item.Name, 1)); } else if (currentBuilding.Done) { @@ -87,11 +87,11 @@ namespace OpenRA.Mods.Common.AI if (location == null) { HackyAI.BotDebug("AI: {0} has nowhere to place {1}".F(player, currentBuilding.Item)); - world.IssueOrder(Order.CancelProduction(queue.Actor, currentBuilding.Item, 1)); + ai.QueueOrder(Order.CancelProduction(queue.Actor, currentBuilding.Item, 1)); } else { - world.IssueOrder(new Order("PlaceBuilding", player.PlayerActor, false) + ai.QueueOrder(new Order("PlaceBuilding", player.PlayerActor, false) { TargetLocation = location.Value, TargetString = currentBuilding.Item, diff --git a/OpenRA.Mods.Common/AI/HackyAI.cs b/OpenRA.Mods.Common/AI/HackyAI.cs index d58cb04d19..82485786bc 100644 --- a/OpenRA.Mods.Common/AI/HackyAI.cs +++ b/OpenRA.Mods.Common/AI/HackyAI.cs @@ -58,6 +58,9 @@ namespace OpenRA.Mods.Common.AI [Desc("How long to wait (in ticks) between structure production checks ticks when actively building things.")] public readonly int StructureProductionActiveDelay = 10; + [Desc("Minimum portion of pending orders to issue each tick (e.g. 5 issues at least 1/5th of all pending orders). Excess orders remain queued for subsequent ticks.")] + public readonly int MinOrderQuotientPerTick = 5; + [Desc("Minimum range at which to build defensive structures near a combat hotspot.")] public readonly int MinimumDefenseRadius = 5; @@ -189,7 +192,7 @@ namespace OpenRA.Mods.Common.AI // Units that the ai already knows about. Any unit not on this list needs to be given a role. List activeUnits = new List(); - public const int FeedbackTime = 30; // ticks; = a bit over 1s. must be >= netlag. + public const int FeedbackTime = 30; // ticks; = a bit over 1s. must be >= netlag. public readonly World World; public Map Map { get { return World.Map; } } @@ -200,6 +203,8 @@ namespace OpenRA.Mods.Common.AI int attackForceTicks; int minAttackForceDelayTicks; + readonly Queue orders = new Queue(); + public HackyAI(HackyAIInfo info, ActorInitializer init) { Info = info; @@ -245,6 +250,11 @@ namespace OpenRA.Mods.Common.AI resourceTypeIndices.Set(World.TileSet.GetTerrainIndex(t.TerrainType), true); } + public void QueueOrder(Order order) + { + orders.Enqueue(order); + } + ActorInfo ChooseRandomUnitToBuild(ProductionQueue queue) { var buildableThings = queue.BuildableItems(); @@ -449,6 +459,10 @@ namespace OpenRA.Mods.Common.AI foreach (var b in builders) b.Tick(); + + var ordersToIssueThisTick = Math.Min((orders.Count + Info.MinOrderQuotientPerTick - 1) / Info.MinOrderQuotientPerTick, orders.Count); + for (var i = 0; i < ordersToIssueThisTick; i++) + World.IssueOrder(orders.Dequeue()); } internal Actor ChooseEnemyTarget() @@ -592,7 +606,7 @@ namespace OpenRA.Mods.Common.AI continue; // Tell the idle harvester to quit slacking: - World.IssueOrder(new Order("Harvest", a, false)); + QueueOrder(new Order("Harvest", a, false)); } } @@ -606,7 +620,7 @@ namespace OpenRA.Mods.Common.AI foreach (var a in newUnits) { if (a.HasTrait()) - World.IssueOrder(new Order("Harvest", a, false)); + QueueOrder(new Order("Harvest", a, false)); else unitsHangingAroundTheBase.Add(a); @@ -702,7 +716,7 @@ namespace OpenRA.Mods.Common.AI !IsRallyPointValid(rp.Trait.Location, rp.Actor.Info.Traits.GetOrDefault())).ToArray(); foreach (var a in buildings) - World.IssueOrder(new Order("SetRallyPoint", a.Actor, false) { TargetLocation = ChooseRallyLocationNear(a.Actor), SuppressVisualFeedback = true }); + QueueOrder(new Order("SetRallyPoint", a.Actor, false) { TargetLocation = ChooseRallyLocationNear(a.Actor), SuppressVisualFeedback = true }); } // Won't work for shipyards... @@ -734,7 +748,7 @@ namespace OpenRA.Mods.Common.AI // Don't transform the mcv if it is a fact // HACK: This needs to query against MCVs directly if (mcv.HasTrait()) - World.IssueOrder(new Order("DeployTransform", mcv, false)); + QueueOrder(new Order("DeployTransform", mcv, false)); } else BotDebug("AI: Can't find BaseBuildUnit."); @@ -759,8 +773,8 @@ namespace OpenRA.Mods.Common.AI if (desiredLocation == null) continue; - World.IssueOrder(new Order("Move", mcv, true) { TargetLocation = desiredLocation.Value }); - World.IssueOrder(new Order("DeployTransform", mcv, true)); + QueueOrder(new Order("Move", mcv, true) { TargetLocation = desiredLocation.Value }); + QueueOrder(new Order("DeployTransform", mcv, true)); } } @@ -814,7 +828,7 @@ namespace OpenRA.Mods.Common.AI // Valid target found, delay by a few ticks to avoid rescanning before power fires via order BotDebug("AI: {2} found new target location {0} for support power {1}.", attackLocation, sp.Info.OrderName, Player.PlayerName); waitingPowers[sp] += 10; - World.IssueOrder(new Order(sp.Info.OrderName, supportPowerMngr.Self, false) { TargetLocation = attackLocation.Value, SuppressVisualFeedback = true }); + QueueOrder(new Order(sp.Info.OrderName, supportPowerMngr.Self, false) { TargetLocation = attackLocation.Value, SuppressVisualFeedback = true }); } } } @@ -923,7 +937,7 @@ namespace OpenRA.Mods.Common.AI ChooseUnitToBuild(queue); if (unit != null && Info.UnitsToBuild.Any(u => u.Key == unit.Name)) - World.IssueOrder(Order.StartProduction(queue.Actor, unit.Name, 1)); + QueueOrder(Order.StartProduction(queue.Actor, unit.Name, 1)); } void BuildUnit(string category, string name) @@ -933,7 +947,7 @@ namespace OpenRA.Mods.Common.AI return; if (Map.Rules.Actors[name] != null) - World.IssueOrder(Order.StartProduction(queue.Actor, name, 1)); + QueueOrder(Order.StartProduction(queue.Actor, name, 1)); } public void Damaged(Actor self, AttackInfo e) @@ -952,7 +966,7 @@ namespace OpenRA.Mods.Common.AI { BotDebug("Bot noticed damage {0} {1}->{2}, repairing.", self, e.PreviousDamageState, e.DamageState); - World.IssueOrder(new Order("RepairBuilding", self.Owner.PlayerActor, false) { TargetActor = self }); + QueueOrder(new Order("RepairBuilding", self.Owner.PlayerActor, false) { TargetActor = self }); } } diff --git a/OpenRA.Mods.Common/AI/States/AirStates.cs b/OpenRA.Mods.Common/AI/States/AirStates.cs index 62432e90ed..9cb08991bc 100644 --- a/OpenRA.Mods.Common/AI/States/AirStates.cs +++ b/OpenRA.Mods.Common/AI/States/AirStates.cs @@ -215,7 +215,7 @@ namespace OpenRA.Mods.Common.AI { if (IsRearm(a)) continue; - owner.World.IssueOrder(new Order("ReturnToBase", a, false)); + owner.Bot.QueueOrder(new Order("ReturnToBase", a, false)); continue; } @@ -224,7 +224,7 @@ namespace OpenRA.Mods.Common.AI } if (owner.TargetActor.HasTrait() && CanAttackTarget(a, owner.TargetActor)) - owner.World.IssueOrder(new Order("Attack", a, false) { TargetActor = owner.TargetActor }); + owner.Bot.QueueOrder(new Order("Attack", a, false) { TargetActor = owner.TargetActor }); } } @@ -247,11 +247,11 @@ namespace OpenRA.Mods.Common.AI if (IsRearm(a)) continue; - owner.World.IssueOrder(new Order("ReturnToBase", a, false)); + owner.Bot.QueueOrder(new Order("ReturnToBase", a, false)); continue; } - owner.World.IssueOrder(new Order("Move", a, false) { TargetLocation = RandomBuildingLocation(owner) }); + owner.Bot.QueueOrder(new Order("Move", a, false) { TargetLocation = RandomBuildingLocation(owner) }); } owner.FuzzyStateMachine.ChangeState(owner, new AirIdleState(), true); diff --git a/OpenRA.Mods.Common/AI/States/GroundStates.cs b/OpenRA.Mods.Common/AI/States/GroundStates.cs index ea8fc18297..fdb022a56f 100644 --- a/OpenRA.Mods.Common/AI/States/GroundStates.cs +++ b/OpenRA.Mods.Common/AI/States/GroundStates.cs @@ -45,7 +45,7 @@ namespace OpenRA.Mods.Common.AI if (owner.AttackOrFleeFuzzy.CanAttack(owner.Units, enemyUnits)) { foreach (var u in owner.Units) - owner.World.IssueOrder(new Order("AttackMove", u, false) { TargetLocation = owner.TargetActor.Location }); + owner.Bot.QueueOrder(new Order("AttackMove", u, false) { TargetLocation = owner.TargetActor.Location }); // We have gathered sufficient units. Attack the nearest enemy unit. owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsAttackMoveState(), true); @@ -87,9 +87,9 @@ namespace OpenRA.Mods.Common.AI .Where(a => a.Owner == owner.Units.FirstOrDefault().Owner && owner.Units.Contains(a)).ToHashSet(); if (ownUnits.Count < owner.Units.Count) { - owner.World.IssueOrder(new Order("Stop", leader, false)); + owner.Bot.QueueOrder(new Order("Stop", leader, false)); foreach (var unit in owner.Units.Where(a => !ownUnits.Contains(a))) - owner.World.IssueOrder(new Order("AttackMove", unit, false) { TargetLocation = leader.Location }); + owner.Bot.QueueOrder(new Order("AttackMove", unit, false) { TargetLocation = leader.Location }); } else { @@ -105,7 +105,7 @@ namespace OpenRA.Mods.Common.AI } else foreach (var a in owner.Units) - owner.World.IssueOrder(new Order("AttackMove", a, false) { TargetLocation = owner.TargetActor.Location }); + owner.Bot.QueueOrder(new Order("AttackMove", a, false) { TargetLocation = owner.TargetActor.Location }); } if (ShouldFlee(owner)) @@ -141,7 +141,7 @@ namespace OpenRA.Mods.Common.AI foreach (var a in owner.Units) if (!BusyAttack(a)) - owner.World.IssueOrder(new Order("Attack", a, false) { TargetActor = owner.Bot.FindClosestEnemy(a.CenterPosition) }); + owner.Bot.QueueOrder(new Order("Attack", a, false) { TargetActor = owner.Bot.FindClosestEnemy(a.CenterPosition) }); if (ShouldFlee(owner)) { diff --git a/OpenRA.Mods.Common/AI/States/ProtectionStates.cs b/OpenRA.Mods.Common/AI/States/ProtectionStates.cs index cf1a99035c..22423e62d6 100644 --- a/OpenRA.Mods.Common/AI/States/ProtectionStates.cs +++ b/OpenRA.Mods.Common/AI/States/ProtectionStates.cs @@ -54,7 +54,7 @@ namespace OpenRA.Mods.Common.AI else { foreach (var a in owner.Units) - owner.World.IssueOrder(new Order("AttackMove", a, false) { TargetLocation = owner.TargetActor.Location }); + owner.Bot.QueueOrder(new Order("AttackMove", a, false) { TargetLocation = owner.TargetActor.Location }); } } diff --git a/OpenRA.Mods.Common/AI/States/StateBase.cs b/OpenRA.Mods.Common/AI/States/StateBase.cs index 868f9a1727..7c898db728 100644 --- a/OpenRA.Mods.Common/AI/States/StateBase.cs +++ b/OpenRA.Mods.Common/AI/States/StateBase.cs @@ -25,7 +25,7 @@ namespace OpenRA.Mods.Common.AI { var loc = RandomBuildingLocation(squad); foreach (var a in squad.Units) - squad.World.IssueOrder(new Order("Move", a, false) { TargetLocation = loc }); + squad.Bot.QueueOrder(new Order("Move", a, false) { TargetLocation = loc }); } protected static CPos RandomBuildingLocation(Squad squad)