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 251f25e660..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); @@ -579,7 +527,6 @@ namespace OpenRA.Mods.RA.AI foreach (var a in newUnits) { - BotDebug("AI: Found a newly built unit"); if (a.HasTrait()) world.IssueOrder(new Order("Harvest", a, false)); else @@ -676,10 +623,6 @@ namespace OpenRA.Mods.RA.AI .Where(rp => rp.Actor.Owner == p && !IsRallyPointValid(rp.Trait.Location, rp.Actor.Info.Traits.GetOrDefault())).ToArray(); - if (buildings.Length > 0) - BotDebug("Bot {0} needs to find rallypoints for {1} buildings.", - p.PlayerName, buildings.Length); - foreach (var a in buildings) world.IssueOrder(new Order("SetRallyPoint", a.Actor, false) { TargetLocation = ChooseRallyLocationNear(a.Actor), SuppressVisualFeedback = true }); } @@ -723,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()) @@ -736,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/mod.yaml b/mods/cnc/mod.yaml index b62ceafb47..29e3893fbb 100644 --- a/mods/cnc/mod.yaml +++ b/mods/cnc/mod.yaml @@ -156,6 +156,7 @@ LobbyDefaults: FragileAlliances: false Shroud: true Fog: true + TechLevel: Unrestricted ChromeMetrics: mods/cnc/metrics.yaml 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/cnc/rules/aircraft.yaml b/mods/cnc/rules/aircraft.yaml index d229b496d4..e274ea21ec 100644 --- a/mods/cnc/rules/aircraft.yaml +++ b/mods/cnc/rules/aircraft.yaml @@ -52,7 +52,7 @@ HELI: Description: Helicopter Gunship with Chainguns.\n Strong vs Infantry, Light Vehicles\n Weak vs Tanks Buildable: BuildPaletteOrder: 20 - Prerequisites: hpad, anyhq + Prerequisites: hpad, anyhq, ~techlevel.medium Queue: Aircraft.Nod Selectable: Bounds: 30,24 @@ -104,7 +104,7 @@ ORCA: Description: Helicopter Gunship with AG Missiles.\n Strong vs Buildings, Tanks\n Weak vs Infantry Buildable: BuildPaletteOrder: 20 - Prerequisites: hpad, anyhq + Prerequisites: hpad, anyhq, ~techlevel.medium Queue: Aircraft.GDI Selectable: Bounds: 30,24 diff --git a/mods/cnc/rules/infantry.yaml b/mods/cnc/rules/infantry.yaml index 6bbfab34fd..7a3ec38cfd 100644 --- a/mods/cnc/rules/infantry.yaml +++ b/mods/cnc/rules/infantry.yaml @@ -30,7 +30,7 @@ E2: Description: Fast infantry armed with grenades. \n Strong vs Buildings, slow-moving targets Buildable: BuildPaletteOrder: 40 - Prerequisites: anyhq + Prerequisites: anyhq, ~techlevel.medium Queue: Infantry.GDI Selectable: Bounds: 12,17,0,-6 @@ -87,7 +87,7 @@ E4: Description: Advanced Anti-infantry unit.\n Strong vs Infantry, Buildings\n Weak vs Tanks Buildable: BuildPaletteOrder: 40 - Prerequisites: anyhq + Prerequisites: anyhq, ~techlevel.medium Queue: Infantry.Nod Selectable: Bounds: 12,17,0,-6 @@ -117,7 +117,7 @@ E5: Description: Advanced general-purpose infantry.\n Strong vs all Ground units Buildable: BuildPaletteOrder: 50 - Prerequisites: tmpl + Prerequisites: tmpl, ~techlevel.high Queue: Infantry.Nod Selectable: Bounds: 12,17,0,-6 @@ -181,7 +181,7 @@ RMBO: Description: Elite sniper infantry unit.\n Strong vs Infantry, Buildings\n Weak vs Vehicles Buildable: BuildPaletteOrder: 50 - Prerequisites: eye + Prerequisites: eye, ~techlevel.high Queue: Infantry.GDI Selectable: Bounds: 12,17,0,-6 diff --git a/mods/cnc/rules/player.yaml b/mods/cnc/rules/player.yaml index e069fb86b8..d1e0dd8ff8 100644 --- a/mods/cnc/rules/player.yaml +++ b/mods/cnc/rules/player.yaml @@ -17,4 +17,15 @@ Player: PlayerStatistics: FrozenActorLayer: PlaceBeacon: - + ProvidesTechPrerequisite@low: + Name: Low + Prerequisites: techlevel.low + ProvidesTechPrerequisite@medium: + Name: Medium + Prerequisites: techlevel.low, techlevel.medium + ProvidesTechPrerequisite@nosuper: + Name: No Powers + Prerequisites: techlevel.low, techlevel.medium, techlevel.high + ProvidesTechPrerequisite@all: + Name: Unrestricted + Prerequisites: techlevel.low, techlevel.medium, techlevel.high, techlevel.superweapons \ No newline at end of file diff --git a/mods/cnc/rules/structures.yaml b/mods/cnc/rules/structures.yaml index 62a41172c7..bf141851b0 100644 --- a/mods/cnc/rules/structures.yaml +++ b/mods/cnc/rules/structures.yaml @@ -110,7 +110,7 @@ NUK2: Prerequisite: anypower Buildable: BuildPaletteOrder: 30 - Prerequisites: anyhq + Prerequisites: anyhq, ~techlevel.medium Queue: Building.GDI, Building.Nod Building: Power: 200 @@ -402,7 +402,7 @@ HQ: Prerequisite: anyhq Buildable: BuildPaletteOrder: 70 - Prerequisites: proc + Prerequisites: proc, ~techlevel.medium Queue: Building.GDI, Building.Nod Building: Power: -40 @@ -423,6 +423,7 @@ HQ: DetectCloaked: Range: 5 AirstrikePower: + Prerequisites: ~techlevel.superweapons Icon: airstrike ChargeTime: 180 SquadSize: 3 @@ -477,7 +478,7 @@ EYE: Prerequisite: anyhq Buildable: BuildPaletteOrder: 100 - Prerequisites: anyhq + Prerequisites: anyhq, ~techlevel.high Queue: Building.GDI Building: Power: -200 @@ -498,6 +499,7 @@ EYE: DetectCloaked: Range: 5 IonCannonPower: + Prerequisites: ~techlevel.superweapons Icon: ioncannon ChargeTime: 180 Description: Ion Cannon @@ -522,7 +524,7 @@ TMPL: Prerequisite: anyhq Buildable: BuildPaletteOrder: 100 - Prerequisites: anyhq + Prerequisites: anyhq, ~techlevel.high Queue: Building.Nod Building: Power: -150 @@ -537,6 +539,7 @@ TMPL: Range: 6c0 Bib: NukePower: + Prerequisites: ~techlevel.superweapons Icon: abomb ChargeTime: 300 Description: Nuclear Strike @@ -646,7 +649,7 @@ OBLI: Description: Advanced base defense. \nRequires power to operate.\n Strong vs all Ground units\n Cannot target Aircraft Buildable: BuildPaletteOrder: 60 - Prerequisites: tmpl + Prerequisites: tmpl, ~techlevel.high Queue: Defence.Nod Building: Power: -150 @@ -729,7 +732,7 @@ ATWR: Description: All-purpose defensive structure.\n Strong vs Aircraft, Tanks\n Weak vs Infantry Buildable: BuildPaletteOrder: 60 - Prerequisites: anyhq + Prerequisites: anyhq, ~techlevel.medium Queue: Defence.GDI Building: Power: -40 diff --git a/mods/cnc/rules/vehicles.yaml b/mods/cnc/rules/vehicles.yaml index f6cfeea34b..6c4d4ab7f3 100644 --- a/mods/cnc/rules/vehicles.yaml +++ b/mods/cnc/rules/vehicles.yaml @@ -7,7 +7,7 @@ MCV: Description: Deploys into another Construction Yard.\n Unarmed Buildable: BuildPaletteOrder: 100 - Prerequisites: anyhq + Prerequisites: anyhq, ~techlevel.medium Queue: Vehicle.GDI, Vehicle.Nod Selectable: Priority: 3 @@ -130,7 +130,7 @@ ARTY: Description: Long-range artillery.\n Strong vs Infantry, Vehicles & Buildings Buildable: BuildPaletteOrder: 60 - Prerequisites: anyhq + Prerequisites: anyhq, ~techlevel.medium Queue: Vehicle.Nod Mobile: ROT: 2 @@ -166,7 +166,7 @@ FTNK: Description: Heavily armored flame-throwing vehicle.\n Strong vs Infantry, Buildings & Vehicles\n Weak vs Tanks Buildable: BuildPaletteOrder: 50 - Prerequisites: anyhq + Prerequisites: anyhq, ~techlevel.medium Queue: Vehicle.Nod Mobile: ROT: 7 @@ -309,7 +309,7 @@ LTNK: Description: Fast, light tank.\n Strong vs Vehicles, Tanks\n Weak vs Infantry Buildable: BuildPaletteOrder: 40 - Prerequisites: anyhq + Prerequisites: anyhq, ~techlevel.medium Queue: Vehicle.Nod Mobile: ROT: 7 @@ -348,7 +348,7 @@ MTNK: Description: General-Purpose GDI Tank.\n Strong vs Tanks, Vehicles\n Weak vs Infantry Buildable: BuildPaletteOrder: 40 - Prerequisites: anyhq + Prerequisites: anyhq, ~techlevel.medium Queue: Vehicle.GDI Mobile: Speed: 85 @@ -388,7 +388,7 @@ HTNK: Description: Heavily armored GDI Tank. \nCan attack Aircraft.\n Strong vs Everything Buildable: BuildPaletteOrder: 60 - Prerequisites: eye + Prerequisites: eye, ~techlevel.high Queue: Vehicle.GDI Mobile: Crushes: wall, heavywall, crate, infantry @@ -441,7 +441,7 @@ MSAM: Description: Long range rocket artillery.\n Strong vs all Ground units. Buildable: BuildPaletteOrder: 50 - Prerequisites: anyhq + Prerequisites: anyhq, ~techlevel.medium Queue: Vehicle.GDI Mobile: Speed: 85 @@ -478,7 +478,7 @@ MLRS: Description: Powerful anti-air unit. \nCannot attack Ground units. Buildable: BuildPaletteOrder: 70 - Prerequisites: anyhq + Prerequisites: anyhq, ~techlevel.medium Queue: Vehicle.Nod Mobile: Speed: 99 @@ -518,7 +518,7 @@ STNK: Description: Long-range missile tank that can cloak. \nCan attack Aircraft. \nHas weak armor. Can be spotted by infantry.\n Strong vs Vehicles, Tanks\n Weak vs Infantry. Buildable: BuildPaletteOrder: 90 - Prerequisites: tmpl + Prerequisites: tmpl, ~techlevel.high Queue: Vehicle.Nod Mobile: ROT: 10 diff --git a/mods/d2k/rules/ai.yaml b/mods/d2k/rules/ai.yaml index 08cc977824..ee52b8aa6e 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 @@ -72,9 +74,9 @@ Player: rockettowera: 7% rockettowerh: 7% rockettowero: 7% - powra: 10% - powrh: 10% - powro: 10% + pwra: 10% + pwrh: 10% + pwro: 10% UnitsToBuild: rifle: 6% bazooka: 5% @@ -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 @@ -173,9 +178,9 @@ Player: rockettowera: 10% rockettowerh: 10% rockettowero: 10% - powra: 12% - powrh: 12% - powro: 12% + pwra: 12% + pwrh: 12% + pwro: 12% UnitsToBuild: rifle: 2% bazooka: 2% @@ -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 @@ -271,9 +279,9 @@ Player: palaceo: 0.1% guntower: 10% rockettower: 5% - powra: 10% - powrh: 10% - powro: 10% + pwra: 10% + pwrh: 10% + pwro: 10% UnitsToBuild: rifle: 15% bazooka: 13% @@ -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%