diff --git a/OpenRA.Mods.RA/AI/BaseBuilder.cs b/OpenRA.Mods.RA/AI/BaseBuilder.cs index ab1c5ca8fa..2c6648e3c1 100644 --- a/OpenRA.Mods.RA/AI/BaseBuilder.cs +++ b/OpenRA.Mods.RA/AI/BaseBuilder.cs @@ -9,95 +9,215 @@ #endregion using System; +using System.Collections.Generic; using System.Linq; +using OpenRA.Mods.RA.Buildings; +using OpenRA.Traits; namespace OpenRA.Mods.RA.AI { class BaseBuilder { - enum BuildState { ChooseItem, WaitForProduction, WaitForFeedback } + readonly string category; - BuildState state = BuildState.WaitForFeedback; - string category; - HackyAI ai; - int lastThinkTick; - Func chooseItem; + readonly HackyAI ai; + readonly World world; + readonly Player player; + readonly PowerManager playerPower; + readonly PlayerResources playerResources; - public BaseBuilder(HackyAI ai, string category, Func chooseItem) + int waitTicks; + Actor[] playerBuildings; + + public BaseBuilder(HackyAI ai, string category, Player p, PowerManager pm, PlayerResources pr) { this.ai = ai; + world = p.World; + player = p; + playerPower = pm; + playerResources = pr; this.category = category; - this.chooseItem = chooseItem; } public void Tick() { - // Pick a free queue - var queue = ai.FindQueues(category).FirstOrDefault(); - if (queue == null) + // Only update once per second or so + if (--waitTicks > 0) return; + playerBuildings = world.ActorsWithTrait() + .Where(a => a.Actor.Owner == player) + .Select(a => a.Actor) + .ToArray(); + + var active = false; + foreach (var queue in ai.FindQueues(category)) + if (TickQueue(queue)) + active = true; + + waitTicks = active ? ai.Info.StructureProductionActiveDelay : ai.Info.StructureProductionInactiveDelay; + } + + bool TickQueue(ProductionQueue queue) + { var currentBuilding = queue.CurrentItem(); - switch (state) + + // Waiting to build something + if (currentBuilding == null) { - case BuildState.ChooseItem: - var item = chooseItem(queue); + var item = ChooseBuildingToBuild(queue); if (item == null) + return false; + + HackyAI.BotDebug("AI: {0} is starting production of {1}".F(player, item.Name)); + world.IssueOrder(Order.StartProduction(queue.Actor, item.Name, 1)); + } + + // Production is complete + else if (currentBuilding.Done) + { + // Choose the placement logic + // HACK: HACK HACK HACK + var type = BuildingType.Building; + if (world.Map.Rules.Actors[currentBuilding.Item].Traits.Contains()) + type = BuildingType.Defense; + else if (world.Map.Rules.Actors[currentBuilding.Item].Traits.Contains()) + type = BuildingType.Refinery; + + var location = ai.ChooseBuildLocation(currentBuilding.Item, true, type); + if (location == null) { - state = BuildState.WaitForFeedback; - lastThinkTick = ai.ticks; + HackyAI.BotDebug("AI: {0} has nowhere to place {1}".F(player, currentBuilding.Item)); + world.IssueOrder(Order.CancelProduction(queue.Actor, currentBuilding.Item, 1)); } else { - HackyAI.BotDebug("AI: Starting production of {0}".F(item.Name)); - state = BuildState.WaitForProduction; - ai.world.IssueOrder(Order.StartProduction(queue.Actor, item.Name, 1)); - } - break; - - case BuildState.WaitForProduction: - if (currentBuilding == null) - return; - - if (currentBuilding.Paused) - ai.world.IssueOrder(Order.PauseProduction(queue.Actor, currentBuilding.Item, false)); - else if (currentBuilding.Done) - { - state = BuildState.WaitForFeedback; - lastThinkTick = ai.ticks; - - // Place the building - var type = BuildingType.Building; - if (ai.Map.Rules.Actors[currentBuilding.Item].Traits.Contains()) - type = BuildingType.Defense; - else if (ai.Map.Rules.Actors[currentBuilding.Item].Traits.Contains()) - type = BuildingType.Refinery; - - var location = ai.ChooseBuildLocation(currentBuilding.Item, type); - if (location == null) + world.IssueOrder(new Order("PlaceBuilding", player.PlayerActor, false) { - HackyAI.BotDebug("AI: Nowhere to place {0}".F(currentBuilding.Item)); - ai.world.IssueOrder(Order.CancelProduction(queue.Actor, currentBuilding.Item, 1)); - } - else - { - ai.world.IssueOrder(new Order("PlaceBuilding", ai.p.PlayerActor, false) - { - TargetLocation = location.Value, - TargetString = currentBuilding.Item, - TargetActor = queue.Actor, - SuppressVisualFeedback = true - }); - } + TargetLocation = location.Value, + TargetString = currentBuilding.Item, + TargetActor = queue.Actor, + SuppressVisualFeedback = true + }); + + return true; } - - break; - - case BuildState.WaitForFeedback: - if (ai.ticks - lastThinkTick > HackyAI.feedbackTime) - state = BuildState.ChooseItem; - break; } + + return true; + } + + ActorInfo GetProducibleBuilding(string commonName, IEnumerable buildables, Func orderBy = null) + { + string[] actors; + if (!ai.Info.BuildingCommonNames.TryGetValue(commonName, out actors)) + throw new InvalidOperationException("Can't find {0} in the HackyAI BuildingCommonNames definition.".F(commonName)); + + var available = buildables.Where(actor => + { + // Are we able to build this? + if (!actors.Contains(actor.Name)) + return false; + + if (!ai.Info.BuildingLimits.ContainsKey(actor.Name)) + return true; + + return playerBuildings.Count(a => a.Info.Name == actor.Name) <= ai.Info.BuildingLimits[actor.Name]; + }); + + if (orderBy != null) + return available.MaxByOrDefault(orderBy); + + return available.RandomOrDefault(ai.random); + } + + ActorInfo ChooseBuildingToBuild(ProductionQueue queue) + { + var buildableThings = queue.BuildableItems(); + + // First priority is to get out of a low power situation + if (playerPower.ExcessPower < 0) + { + var power = GetProducibleBuilding("Power", buildableThings, a => a.Traits.Get().Power); + if (power != null && power.Traits.Get().Power > 0) + { + HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (low power)", queue.Actor.Owner, power.Name); + return power; + } + } + + // Next is to build up a strong economy + if (!ai.HasAdequateProc() || !ai.HasMinimumProc()) + { + var refinery = GetProducibleBuilding("Refinery", buildableThings); + if (refinery != null) + { + HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (refinery)", queue.Actor.Owner, refinery.Name); + return refinery; + } + } + + // Make sure that we can can spend as fast as we are earning + if (ai.Info.NewProductionCashThreshold > 0 && playerResources.Resources > ai.Info.NewProductionCashThreshold) + { + var production = GetProducibleBuilding("Production", buildableThings); + if (production != null) + { + HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (production)", queue.Actor.Owner, production.Name); + return production; + } + } + + // Create some head room for resource storage if we really need it + if (playerResources.AlertSilo) + { + var silo = GetProducibleBuilding("Silo", buildableThings); + if (silo != null) + { + HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (silo)", queue.Actor.Owner, silo.Name); + return silo; + } + } + + // Build everything else + foreach (var frac in ai.Info.BuildingFractions.Shuffle(ai.random)) + { + var name = frac.Key; + + // Can we build this structure? + if (!buildableThings.Any(b => b.Name == name)) + continue; + + // Do we want to build this structure? + var count = playerBuildings.Count(a => a.Info.Name == name); + if (count > frac.Value * playerBuildings.Length) + continue; + + if (ai.Info.BuildingLimits.ContainsKey(name) && ai.Info.BuildingLimits[name] <= count) + continue; + + // Will this put us into low power? + var actor = world.Map.Rules.Actors[frac.Key]; + var bi = actor.Traits.Get(); + if (playerPower.ExcessPower < 0 || playerPower.ExcessPower < bi.Power) + { + // Try building a power plant instead + var power = GetProducibleBuilding("Power", buildableThings, a => a.Traits.Get().Power); + if (power != null && power.Traits.Get().Power > 0) + { + HackyAI.BotDebug("{0} decided to build {1}: Priority override (would be low power)", queue.Actor.Owner, power.Name); + return power; + } + } + + // Lets build this + HackyAI.BotDebug("{0} decided to build {1}: Desired is {2} ({3} / {4}); current is {5} / {4}", queue.Actor.Owner, name, frac.Value, frac.Value * playerBuildings.Length, playerBuildings.Length, count); + return actor; + } + + // Too spammy to keep enabled all the time, but very useful when debugging specific issues. + // HackyAI.BotDebug("{0} couldn't decide what to build for queue {1}.", queue.Actor.Owner, queue.Info.Group); + return null; } } } diff --git a/OpenRA.Mods.RA/AI/HackyAI.cs b/OpenRA.Mods.RA/AI/HackyAI.cs index 3022d79086..740ee0954b 100644 --- a/OpenRA.Mods.RA/AI/HackyAI.cs +++ b/OpenRA.Mods.RA/AI/HackyAI.cs @@ -33,19 +33,36 @@ namespace OpenRA.Mods.RA.AI public readonly int RushInterval = 600; public readonly int AttackForceInterval = 30; - [Desc("By what factor should power output exceed power consumption.")] - public readonly float ExcessPowerFactor = 1.2f; - [Desc("By what minimum amount should power output exceed power consumption.")] - public readonly int MinimumExcessPower = 50; + [Desc("How long to wait (in ticks) between structure production checks when there is no active production.")] + public readonly int StructureProductionInactiveDelay = 125; + + [Desc("How long to wait (in ticks) between structure production checks ticks when actively building things.")] + public readonly int StructureProductionActiveDelay = 10; + + [Desc("Minimum range at which to build defensive structures near a combat hotspot.")] + public readonly int MinimumDefenseRadius = 5; + + [Desc("Maximum range at which to build defensive structures near a combat hotspot.")] + public readonly int MaximumDefenseRadius = 20; + + [Desc("Try to build another production building if there is too much cash.")] + public readonly int NewProductionCashThreshold = 5000; + [Desc("Only produce units as long as there are less than this amount of units idling inside the base.")] public readonly int IdleBaseUnitsMaximum = 12; + [Desc("Radius in cells around enemy BaseBuilder (Construction Yard) where AI scans for targets to rush.")] public readonly int RushAttackScanRadius = 15; + [Desc("Radius in cells around the base that should be scanned for units to be protected.")] public readonly int ProtectUnitScanRadius = 15; + [Desc("Radius in cells around a factory scanned for rally points by the AI.")] public readonly int RallyPointScanRadius = 8; + [Desc("Radius in cells around the center of the base to expand.")] + public readonly int MaxBaseRadius = 20; + public readonly string[] UnitQueues = { "Vehicle", "Infantry", "Plane", "Ship", "Aircraft" }; public readonly bool ShouldRepairBuildings = true; @@ -91,15 +108,16 @@ namespace OpenRA.Mods.RA.AI public sealed class HackyAI : ITick, IBot, INotifyDamage { - bool enabled; - public int ticks; - public Player p; - public MersenneTwister random; - public CPos baseCenter; + public MersenneTwister random { get; private set; } + public readonly HackyAIInfo Info; + public CPos baseCenter { get; private set; } + public Player p { get; private set; } + PowerManager playerPower; SupportPowerManager supportPowerMngr; PlayerResources playerResource; - internal readonly HackyAIInfo Info; + bool enabled; + int ticks; HashSet resourceTypeIndices; @@ -114,7 +132,6 @@ namespace OpenRA.Mods.RA.AI // Units that the ai already knows about. Any unit not on this list needs to be given a role. List activeUnits = new List(); - const int MaxBaseDistance = 40; public const int feedbackTime = 30; // ticks; = a bit over 1s. must be >= netlag. public readonly World world; @@ -143,9 +160,9 @@ namespace OpenRA.Mods.RA.AI playerResource = p.PlayerActor.Trait(); foreach (var building in Info.BuildingQueues) - builders.Add(new BaseBuilder(this, building, q => ChooseBuildingToBuild(q, false))); + builders.Add(new BaseBuilder(this, building, p, playerPower, playerResource)); foreach (var defense in Info.DefenseQueues) - builders.Add(new BaseBuilder(this, defense, q => ChooseBuildingToBuild(q, true))); + builders.Add(new BaseBuilder(this, defense, p, playerPower, playerResource)); random = new MersenneTwister((int)p.PlayerActor.ActorID); @@ -155,12 +172,6 @@ namespace OpenRA.Mods.RA.AI .Select(t => world.TileSet.GetTerrainIndex(t.TerrainType))); } - static int GetPowerProvidedBy(ActorInfo building) - { - var bi = building.Traits.GetOrDefault(); - return bi != null ? bi.Power : 0; - } - ActorInfo ChooseRandomUnitToBuild(ProductionQueue queue) { var buildableThings = queue.BuildableItems(); @@ -182,7 +193,7 @@ namespace OpenRA.Mods.RA.AI .Where(a => a.Actor.Owner == p) .Select(a => a.Actor.Info.Name).ToArray(); - foreach (var unit in Info.UnitsToBuild) + foreach (var unit in Info.UnitsToBuild.Shuffle(random)) if (buildableThings.Any(b => b.Name == unit.Key)) if (myUnits.Count(a => a == unit.Key) < unit.Value * myUnits.Length) if (HasAdequateAirUnits(Map.Rules.Actors[unit.Key])) @@ -212,7 +223,7 @@ namespace OpenRA.Mods.RA.AI .Count(a => a.Actor.Owner == owner && Info.BuildingCommonNames[commonName].Contains(a.Actor.Info.Name)); } - ActorInfo GetBuildingInfoByCommonName(string commonName, Player owner) + public ActorInfo GetBuildingInfoByCommonName(string commonName, Player owner) { if (commonName == "ConstructionYard") return Map.Rules.Actors.Where(k => Info.BuildingCommonNames[commonName].Contains(k.Key)).Random(random).Value; @@ -220,12 +231,12 @@ namespace OpenRA.Mods.RA.AI return GetInfoByCommonName(Info.BuildingCommonNames, commonName, owner); } - ActorInfo GetUnitInfoByCommonName(string commonName, Player owner) + public ActorInfo GetUnitInfoByCommonName(string commonName, Player owner) { return GetInfoByCommonName(Info.UnitsCommonNames, commonName, owner); } - ActorInfo GetInfoByCommonName(Dictionary names, string commonName, Player owner) + public ActorInfo GetInfoByCommonName(Dictionary names, string commonName, Player owner) { if (!names.Any() || !names.ContainsKey(commonName)) throw new InvalidOperationException("Can't find {0} in the HackyAI UnitsCommonNames definition.".F(commonName)); @@ -233,28 +244,21 @@ namespace OpenRA.Mods.RA.AI return Map.Rules.Actors.Where(k => names[commonName].Contains(k.Key)).Random(random).Value; } - bool HasAdequatePower() - { - // note: CNC `fact` provides a small amount of power. don't get jammed because of that. - return playerPower.PowerProvided > Info.MinimumExcessPower && - playerPower.PowerProvided > playerPower.PowerDrained * Info.ExcessPowerFactor; - } - - bool HasAdequateFact() + public bool HasAdequateFact() { // Require at least one construction yard, unless we have no vehicles factory (can't build it). return CountBuildingByCommonName("ConstructionYard", p) > 0 || CountBuildingByCommonName("VehiclesFactory", p) == 0; } - bool HasAdequateProc() + public bool HasAdequateProc() { // Require at least one refinery, unless we have no power (can't build it). return CountBuildingByCommonName("Refinery", p) > 0 || CountBuildingByCommonName("Power", p) == 0; } - bool HasMinimumProc() + public bool HasMinimumProc() { // Require at least two refineries, unless we have no power (can't build it) // or barracks (higher priority?) @@ -263,14 +267,6 @@ namespace OpenRA.Mods.RA.AI CountBuildingByCommonName("Barracks", p) == 0; } - bool HasAdequateNumber(string frac, Player owner) - { - if (Info.BuildingLimits.ContainsKey(frac)) - return CountBuilding(frac, owner) < Info.BuildingLimits[frac]; - - return true; - } - // For mods like RA (number of building must match the number of aircraft) bool HasAdequateAirUnits(ActorInfo actorInfo) { @@ -286,108 +282,68 @@ namespace OpenRA.Mods.RA.AI return true; } - ActorInfo ChooseBuildingToBuild(ProductionQueue queue, bool isDefense) - { - var buildableThings = queue.BuildableItems(); - - if (!isDefense) - { - // Try to maintain 20% excess power - if (!HasAdequatePower()) - return buildableThings.Where(a => GetPowerProvidedBy(a) > 0) - .MaxByOrDefault(a => GetPowerProvidedBy(a)); - - if (playerResource.AlertSilo) - return GetBuildingInfoByCommonName("Silo", p); - - if (!HasAdequateProc() || !HasMinimumProc()) - return GetBuildingInfoByCommonName("Refinery", p); - } - - var myBuildings = p.World - .ActorsWithTrait() - .Where(a => a.Actor.Owner == p) - .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 && HasAdequateNumber(frac.Key, p) && - playerPower.ExcessPower >= Map.Rules.Actors[frac.Key].Traits.Get().Power) - return Map.Rules.Actors[frac.Key]; - - return null; - } - - bool NoBuildingsUnder(IEnumerable cells) - { - var bi = world.WorldActor.Trait(); - return cells.All(c => bi.GetBuildingAt(c) == null); - } - CPos defenseCenter; - public CPos? ChooseBuildLocation(string actorType, BuildingType type) - { - return ChooseBuildLocation(actorType, true, MaxBaseDistance, type); - } - - public CPos? ChooseBuildLocation(string actorType, bool distanceToBaseIsImportant, int maxBaseDistance, BuildingType type) + public CPos? ChooseBuildLocation(string actorType, bool distanceToBaseIsImportant, BuildingType type) { var bi = Map.Rules.Actors[actorType].Traits.GetOrDefault(); if (bi == null) return null; - Func findPos = (pos, center) => + // Find the buildable cell that is closest to pos and centered around center + Func findPos = (center, target, minRange, maxRange) => { - for (var k = MaxBaseDistance; k >= 0; k--) - { - var tlist = Map.FindTilesInCircle(center, k); + var cells = Map.FindTilesInAnnulus(center, minRange, maxRange); - foreach (var t in tlist) - if (world.CanPlaceBuilding(actorType, bi, t, null)) - if (bi.IsCloseEnoughToBase(world, p, actorType, t)) - if (NoBuildingsUnder(Util.ExpandFootprint(FootprintUtils.Tiles(Map.Rules, actorType, bi, t), false))) - return t; + // Sort by distance to target if we have one + if (center != target) + cells = cells.OrderBy(c => (center - target).LengthSquared); + else + cells = cells.Shuffle(random); + + foreach (var cell in cells) + { + if (!world.CanPlaceBuilding(actorType, bi, cell, null)) + continue; + + if (distanceToBaseIsImportant && !bi.IsCloseEnoughToBase(world, p, actorType, cell)) + continue; + + return cell; } return null; }; - var baseCenterPos = world.Map.CenterOfCell(baseCenter); switch (type) { case BuildingType.Defense: - var enemyBase = FindEnemyBuildingClosestToPos(baseCenterPos); - return enemyBase != null ? findPos(enemyBase.CenterPosition, defenseCenter) : null; + + // Build near the closest enemy structure + var closestEnemy = world.Actors.Where(a => !a.Destroyed && a.HasTrait() && p.Stances[a.Owner] == Stance.Enemy) + .ClosestTo(world.Map.CenterOfCell(defenseCenter)); + + var targetCell = closestEnemy != null ? closestEnemy.Location : baseCenter; + return findPos(defenseCenter, targetCell, Info.MinimumDefenseRadius, Info.MaximumDefenseRadius); case BuildingType.Refinery: - var tilesPos = Map.FindTilesInCircle(baseCenter, MaxBaseDistance) - .Where(a => resourceTypeIndices.Contains(Map.GetTerrainIndex(new CPos(a.X, a.Y)))); - if (tilesPos.Any()) - { - var pos = tilesPos.MinBy(a => (world.Map.CenterOfCell(a) - baseCenterPos).LengthSquared); - return findPos(world.Map.CenterOfCell(pos), baseCenter); - } - return null; + // Try and place the refinery near a resource field + var nearbyResources = Map.FindTilesInCircle(baseCenter, Info.MaxBaseRadius) + .Where(a => resourceTypeIndices.Contains(Map.GetTerrainIndex(a))) + .Shuffle(random); + + foreach (var c in nearbyResources) + { + var found = findPos(c, baseCenter, 0, Info.MaxBaseRadius); + if (found != null) + return found; + } + + // Try and find a free spot somewhere else in the base + return findPos(baseCenter, baseCenter, 0, Info.MaxBaseRadius); case BuildingType.Building: - for (var k = 0; k < maxBaseDistance; k++) - { - foreach (var t in Map.FindTilesInCircle(baseCenter, k)) - { - if (world.CanPlaceBuilding(actorType, bi, t, null)) - { - if (distanceToBaseIsImportant && !bi.IsCloseEnoughToBase(world, p, actorType, t)) - continue; - - if (NoBuildingsUnder(Util.ExpandFootprint(FootprintUtils.Tiles(Map.Rules, actorType, bi, t), false))) - return t; - } - } - } - - break; + return findPos(baseCenter, baseCenter, 0, distanceToBaseIsImportant ? Info.MaxBaseRadius : Map.MaxTilesInCircleRange); } // Can't find a build location @@ -481,14 +437,6 @@ namespace OpenRA.Mods.RA.AI && a.HasTrait() && !a.HasTrait()).ToList(); } - Actor FindEnemyBuildingClosestToPos(WPos pos) - { - var closestBuilding = world.Actors.Where(a => p.Stances[a.Owner] == Stance.Enemy - && !a.Destroyed && a.HasTrait()).ClosestTo(pos); - - return closestBuilding; - } - void CleanSquads() { squads.RemoveAll(s => !s.IsValid); @@ -718,8 +666,6 @@ namespace OpenRA.Mods.RA.AI // backup location within the main base. void FindAndDeployBackupMcv(Actor self) { - var maxBaseDistance = Math.Max(world.Map.MapSize.X, world.Map.MapSize.Y); - // HACK: This needs to query against MCVs directly var mcvs = self.World.Actors.Where(a => a.Owner == p && a.HasTrait() && a.HasTrait()); if (!mcvs.Any()) @@ -731,7 +677,7 @@ namespace OpenRA.Mods.RA.AI continue; var factType = mcv.Info.Traits.Get().IntoActor; - var desiredLocation = ChooseBuildLocation(factType, false, maxBaseDistance, BuildingType.Building); + var desiredLocation = ChooseBuildLocation(factType, false, BuildingType.Building); if (desiredLocation == null) continue; diff --git a/mods/cnc/rules/ai.yaml b/mods/cnc/rules/ai.yaml index 3f776d94ac..91315106b6 100644 --- a/mods/cnc/rules/ai.yaml +++ b/mods/cnc/rules/ai.yaml @@ -7,6 +7,7 @@ Player: Power: nuke,nuk2 Barracks: pyle,hand VehiclesFactory: weap,afld + Production: pyle,hand,weap,afld,hpad Silo: silo UnitsCommonNames: Mcv: mcv @@ -15,11 +16,11 @@ Player: UnitQueues: Vehicle.Nod, Vehicle.GDI, Infantry.Nod, Infantry.GDI, Aircraft.Nod, Aircraft.GDI BuildingLimits: proc: 4 - pyle: 2 - hand: 2 + pyle: 3 + hand: 3 hq: 1 - weap: 2 - afld: 2 + weap: 3 + afld: 3 hpad: 0 eye: 1 tmpl: 1 @@ -72,6 +73,7 @@ Player: Power: nuke,nuk2 Barracks: pyle,hand VehiclesFactory: weap,afld + Production: pyle,hand,weap,afld,hpad Silo: silo UnitsCommonNames: Mcv: mcv @@ -80,12 +82,12 @@ Player: UnitQueues: Vehicle.Nod, Vehicle.GDI, Infantry.Nod, Infantry.GDI, Aircraft.Nod, Aircraft.GDI BuildingLimits: proc: 4 - pyle: 2 - hand: 2 + pyle: 3 + hand: 3 hq: 1 - weap: 2 - afld: 2 - hpad: 1 + weap: 3 + afld: 3 + hpad: 2 eye: 1 tmpl: 1 fix: 0 @@ -137,6 +139,7 @@ Player: Power: nuke,nuk2 Barracks: pyle,hand VehiclesFactory: weap,afld + Production: pyle,hand,weap,afld,hpad Silo: silo UnitsCommonNames: Mcv: mcv @@ -145,12 +148,12 @@ Player: UnitQueues: Vehicle.Nod, Vehicle.GDI, Infantry.Nod, Infantry.GDI, Aircraft.Nod, Aircraft.GDI BuildingLimits: proc: 4 - pyle: 2 + pyle: 4 hand: 4 hq: 1 - weap: 3 - afld: 2 - hpad: 1 + weap: 4 + afld: 4 + hpad: 2 eye: 1 tmpl: 1 fix: 0 diff --git a/mods/d2k/rules/ai.yaml b/mods/d2k/rules/ai.yaml index 08cc977824..59b64a2008 100644 --- a/mods/d2k/rules/ai.yaml +++ b/mods/d2k/rules/ai.yaml @@ -6,7 +6,9 @@ Player: ConstructionYard: conyarda,conyardh,conyardo Refinery: refa,refh,refo Power: pwra,pwrh,pwro + Barracks: barra,barrh,barro VehiclesFactory: lighta,lighth,lighto,heavya,heavyh,heavyo + Production: lighta,lighth,lighto,heavya,heavyh,heavyo,barra,barrh,barro Silo: siloa, siloh, siloo UnitsCommonNames: Mcv: mcva,mcvh,mcvo @@ -100,6 +102,7 @@ Player: combath: 100% combato: 100% SquadSize: 8 + MaxBaseRadius: 40 HackyAI@Vidius: Name: Vidious UnitQueues: Infantry, Vehicle, Armor, Starport @@ -107,7 +110,9 @@ Player: ConstructionYard: conyarda,conyardh,conyardo Refinery: refa,refh,refo Power: pwra,pwrh,pwro + Barracks: barra,barrh,barro VehiclesFactory: lighta,lighth,lighto,heavya,heavyh,heavyo + Production: lighta,lighth,lighto,heavya,heavyh,heavyo,barra,barrh,barro Silo: siloa, siloh, siloo UnitsCommonNames: Mcv: mcva,mcvh,mcvo @@ -202,6 +207,7 @@ Player: combath: 100% combato: 100% SquadSize: 6 + MaxBaseRadius: 40 HackyAI@Gladius: Name: Gladius UnitQueues: Infantry, Vehicle, Armor, Starport @@ -209,7 +215,9 @@ Player: ConstructionYard: conyarda,conyardh,conyardo Refinery: refa,refh,refo Power: pwra,pwrh,pwro + Barracks: barra,barrh,barro VehiclesFactory: lighta,lighth,lighto,heavya,heavyh,heavyo + Production: lighta,lighth,lighto,heavya,heavyh,heavyo,barra,barrh,barro Silo: siloa, siloh, siloo UnitsCommonNames: Mcv: mcva,mcvh,mcvo @@ -300,4 +308,4 @@ Player: combath: 100% combato: 100% SquadSize: 10 - + MaxBaseRadius: 40 diff --git a/mods/ra/rules/ai.yaml b/mods/ra/rules/ai.yaml index 12ae61c4f0..c175b2791c 100644 --- a/mods/ra/rules/ai.yaml +++ b/mods/ra/rules/ai.yaml @@ -7,6 +7,7 @@ Player: Power: powr,apwr Barracks: barr,tent VehiclesFactory: weap + Production: barr,tent,weap,afld,hpad,spen,syrd Silo: silo UnitsCommonNames: Mcv: mcv @@ -29,7 +30,7 @@ Player: barr: 1% tent: 1% weap: 1% - pbox.e1: 7% + pbox: 7% gun: 7% tsla: 5% ftur: 10% @@ -59,6 +60,7 @@ Player: Power: powr,apwr Barracks: barr,tent VehiclesFactory: weap + Production: barr,tent,weap,afld,hpad,spen,syrd Silo: silo UnitsCommonNames: Mcv: mcv @@ -87,7 +89,7 @@ Player: spen: 1% syrd: 1% afld: 4% - pbox.e1: 7% + pbox: 7% gun: 7% ftur: 10% tsla: 5% @@ -125,6 +127,7 @@ Player: Power: powr,apwr Barracks: barr,tent VehiclesFactory: weap + Production: barr,tent,weap,afld,hpad,spen,syrd Silo: silo UnitsCommonNames: Mcv: mcv @@ -151,7 +154,7 @@ Player: hpad: 2% spen: 1% syrd: 1% - pbox.e1: 7% + pbox: 7% gun: 7% ftur: 10% tsla: 5% @@ -188,6 +191,9 @@ Player: ConstructionYard: fact Refinery: proc Power: powr,apwr + Barracks: barr,tent + VehiclesFactory: weap + Production: barr,tent,weap,afld,hpad,spen,syrd Silo: silo UnitsCommonNames: Mcv: mcv @@ -213,7 +219,7 @@ Player: stek: 1% spen: 1% syrd: 1% - pbox.e1: 12% + pbox: 12% gun: 12% ftur: 12% tsla: 12%