@@ -9,95 +9,215 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using OpenRA.Mods.RA.Buildings;
|
||||||
|
using OpenRA.Traits;
|
||||||
|
|
||||||
namespace OpenRA.Mods.RA.AI
|
namespace OpenRA.Mods.RA.AI
|
||||||
{
|
{
|
||||||
class BaseBuilder
|
class BaseBuilder
|
||||||
{
|
{
|
||||||
enum BuildState { ChooseItem, WaitForProduction, WaitForFeedback }
|
readonly string category;
|
||||||
|
|
||||||
BuildState state = BuildState.WaitForFeedback;
|
readonly HackyAI ai;
|
||||||
string category;
|
readonly World world;
|
||||||
HackyAI ai;
|
readonly Player player;
|
||||||
int lastThinkTick;
|
readonly PowerManager playerPower;
|
||||||
Func<ProductionQueue, ActorInfo> chooseItem;
|
readonly PlayerResources playerResources;
|
||||||
|
|
||||||
public BaseBuilder(HackyAI ai, string category, Func<ProductionQueue, ActorInfo> chooseItem)
|
int waitTicks;
|
||||||
|
Actor[] playerBuildings;
|
||||||
|
|
||||||
|
public BaseBuilder(HackyAI ai, string category, Player p, PowerManager pm, PlayerResources pr)
|
||||||
{
|
{
|
||||||
this.ai = ai;
|
this.ai = ai;
|
||||||
|
world = p.World;
|
||||||
|
player = p;
|
||||||
|
playerPower = pm;
|
||||||
|
playerResources = pr;
|
||||||
this.category = category;
|
this.category = category;
|
||||||
this.chooseItem = chooseItem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Tick()
|
public void Tick()
|
||||||
{
|
{
|
||||||
// Pick a free queue
|
// Only update once per second or so
|
||||||
var queue = ai.FindQueues(category).FirstOrDefault();
|
if (--waitTicks > 0)
|
||||||
if (queue == null)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
playerBuildings = world.ActorsWithTrait<Building>()
|
||||||
|
.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();
|
var currentBuilding = queue.CurrentItem();
|
||||||
switch (state)
|
|
||||||
|
// Waiting to build something
|
||||||
|
if (currentBuilding == null)
|
||||||
{
|
{
|
||||||
case BuildState.ChooseItem:
|
var item = ChooseBuildingToBuild(queue);
|
||||||
var item = chooseItem(queue);
|
|
||||||
if (item == null)
|
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<AttackBaseInfo>())
|
||||||
|
type = BuildingType.Defense;
|
||||||
|
else if (world.Map.Rules.Actors[currentBuilding.Item].Traits.Contains<OreRefineryInfo>())
|
||||||
|
type = BuildingType.Refinery;
|
||||||
|
|
||||||
|
var location = ai.ChooseBuildLocation(currentBuilding.Item, true, type);
|
||||||
|
if (location == null)
|
||||||
{
|
{
|
||||||
state = BuildState.WaitForFeedback;
|
HackyAI.BotDebug("AI: {0} has nowhere to place {1}".F(player, currentBuilding.Item));
|
||||||
lastThinkTick = ai.ticks;
|
world.IssueOrder(Order.CancelProduction(queue.Actor, currentBuilding.Item, 1));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HackyAI.BotDebug("AI: Starting production of {0}".F(item.Name));
|
world.IssueOrder(new Order("PlaceBuilding", player.PlayerActor, false)
|
||||||
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<AttackBaseInfo>())
|
|
||||||
type = BuildingType.Defense;
|
|
||||||
else if (ai.Map.Rules.Actors[currentBuilding.Item].Traits.Contains<OreRefineryInfo>())
|
|
||||||
type = BuildingType.Refinery;
|
|
||||||
|
|
||||||
var location = ai.ChooseBuildLocation(currentBuilding.Item, type);
|
|
||||||
if (location == null)
|
|
||||||
{
|
{
|
||||||
HackyAI.BotDebug("AI: Nowhere to place {0}".F(currentBuilding.Item));
|
TargetLocation = location.Value,
|
||||||
ai.world.IssueOrder(Order.CancelProduction(queue.Actor, currentBuilding.Item, 1));
|
TargetString = currentBuilding.Item,
|
||||||
}
|
TargetActor = queue.Actor,
|
||||||
else
|
SuppressVisualFeedback = true
|
||||||
{
|
});
|
||||||
ai.world.IssueOrder(new Order("PlaceBuilding", ai.p.PlayerActor, false)
|
|
||||||
{
|
return true;
|
||||||
TargetLocation = location.Value,
|
|
||||||
TargetString = currentBuilding.Item,
|
|
||||||
TargetActor = queue.Actor,
|
|
||||||
SuppressVisualFeedback = true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case BuildState.WaitForFeedback:
|
|
||||||
if (ai.ticks - lastThinkTick > HackyAI.feedbackTime)
|
|
||||||
state = BuildState.ChooseItem;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActorInfo GetProducibleBuilding(string commonName, IEnumerable<ActorInfo> buildables, Func<ActorInfo, int> 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<BuildingInfo>().Power);
|
||||||
|
if (power != null && power.Traits.Get<BuildingInfo>().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<BuildingInfo>();
|
||||||
|
if (playerPower.ExcessPower < 0 || playerPower.ExcessPower < bi.Power)
|
||||||
|
{
|
||||||
|
// Try building a power plant instead
|
||||||
|
var power = GetProducibleBuilding("Power", buildableThings, a => a.Traits.Get<BuildingInfo>().Power);
|
||||||
|
if (power != null && power.Traits.Get<BuildingInfo>().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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,19 +33,36 @@ namespace OpenRA.Mods.RA.AI
|
|||||||
public readonly int RushInterval = 600;
|
public readonly int RushInterval = 600;
|
||||||
public readonly int AttackForceInterval = 30;
|
public readonly int AttackForceInterval = 30;
|
||||||
|
|
||||||
[Desc("By what factor should power output exceed power consumption.")]
|
[Desc("How long to wait (in ticks) between structure production checks when there is no active production.")]
|
||||||
public readonly float ExcessPowerFactor = 1.2f;
|
public readonly int StructureProductionInactiveDelay = 125;
|
||||||
[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 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.")]
|
[Desc("Only produce units as long as there are less than this amount of units idling inside the base.")]
|
||||||
public readonly int IdleBaseUnitsMaximum = 12;
|
public readonly int IdleBaseUnitsMaximum = 12;
|
||||||
|
|
||||||
[Desc("Radius in cells around enemy BaseBuilder (Construction Yard) where AI scans for targets to rush.")]
|
[Desc("Radius in cells around enemy BaseBuilder (Construction Yard) where AI scans for targets to rush.")]
|
||||||
public readonly int RushAttackScanRadius = 15;
|
public readonly int RushAttackScanRadius = 15;
|
||||||
|
|
||||||
[Desc("Radius in cells around the base that should be scanned for units to be protected.")]
|
[Desc("Radius in cells around the base that should be scanned for units to be protected.")]
|
||||||
public readonly int ProtectUnitScanRadius = 15;
|
public readonly int ProtectUnitScanRadius = 15;
|
||||||
|
|
||||||
[Desc("Radius in cells around a factory scanned for rally points by the AI.")]
|
[Desc("Radius in cells around a factory scanned for rally points by the AI.")]
|
||||||
public readonly int RallyPointScanRadius = 8;
|
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 string[] UnitQueues = { "Vehicle", "Infantry", "Plane", "Ship", "Aircraft" };
|
||||||
public readonly bool ShouldRepairBuildings = true;
|
public readonly bool ShouldRepairBuildings = true;
|
||||||
|
|
||||||
@@ -91,15 +108,16 @@ namespace OpenRA.Mods.RA.AI
|
|||||||
|
|
||||||
public sealed class HackyAI : ITick, IBot, INotifyDamage
|
public sealed class HackyAI : ITick, IBot, INotifyDamage
|
||||||
{
|
{
|
||||||
bool enabled;
|
public MersenneTwister random { get; private set; }
|
||||||
public int ticks;
|
public readonly HackyAIInfo Info;
|
||||||
public Player p;
|
public CPos baseCenter { get; private set; }
|
||||||
public MersenneTwister random;
|
public Player p { get; private set; }
|
||||||
public CPos baseCenter;
|
|
||||||
PowerManager playerPower;
|
PowerManager playerPower;
|
||||||
SupportPowerManager supportPowerMngr;
|
SupportPowerManager supportPowerMngr;
|
||||||
PlayerResources playerResource;
|
PlayerResources playerResource;
|
||||||
internal readonly HackyAIInfo Info;
|
bool enabled;
|
||||||
|
int ticks;
|
||||||
|
|
||||||
HashSet<int> resourceTypeIndices;
|
HashSet<int> 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.
|
// Units that the ai already knows about. Any unit not on this list needs to be given a role.
|
||||||
List<Actor> activeUnits = new List<Actor>();
|
List<Actor> activeUnits = new List<Actor>();
|
||||||
|
|
||||||
const int MaxBaseDistance = 40;
|
|
||||||
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 readonly World world;
|
||||||
@@ -143,9 +160,9 @@ namespace OpenRA.Mods.RA.AI
|
|||||||
playerResource = p.PlayerActor.Trait<PlayerResources>();
|
playerResource = p.PlayerActor.Trait<PlayerResources>();
|
||||||
|
|
||||||
foreach (var building in Info.BuildingQueues)
|
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)
|
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);
|
random = new MersenneTwister((int)p.PlayerActor.ActorID);
|
||||||
|
|
||||||
@@ -155,12 +172,6 @@ namespace OpenRA.Mods.RA.AI
|
|||||||
.Select(t => world.TileSet.GetTerrainIndex(t.TerrainType)));
|
.Select(t => world.TileSet.GetTerrainIndex(t.TerrainType)));
|
||||||
}
|
}
|
||||||
|
|
||||||
static int GetPowerProvidedBy(ActorInfo building)
|
|
||||||
{
|
|
||||||
var bi = building.Traits.GetOrDefault<BuildingInfo>();
|
|
||||||
return bi != null ? bi.Power : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ActorInfo ChooseRandomUnitToBuild(ProductionQueue queue)
|
ActorInfo ChooseRandomUnitToBuild(ProductionQueue queue)
|
||||||
{
|
{
|
||||||
var buildableThings = queue.BuildableItems();
|
var buildableThings = queue.BuildableItems();
|
||||||
@@ -182,7 +193,7 @@ namespace OpenRA.Mods.RA.AI
|
|||||||
.Where(a => a.Actor.Owner == p)
|
.Where(a => a.Actor.Owner == p)
|
||||||
.Select(a => a.Actor.Info.Name).ToArray();
|
.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 (buildableThings.Any(b => b.Name == unit.Key))
|
||||||
if (myUnits.Count(a => a == unit.Key) < unit.Value * myUnits.Length)
|
if (myUnits.Count(a => a == unit.Key) < unit.Value * myUnits.Length)
|
||||||
if (HasAdequateAirUnits(Map.Rules.Actors[unit.Key]))
|
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));
|
.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")
|
if (commonName == "ConstructionYard")
|
||||||
return Map.Rules.Actors.Where(k => Info.BuildingCommonNames[commonName].Contains(k.Key)).Random(random).Value;
|
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);
|
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);
|
return GetInfoByCommonName(Info.UnitsCommonNames, commonName, owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
ActorInfo GetInfoByCommonName(Dictionary<string, string[]> names, string commonName, Player owner)
|
public ActorInfo GetInfoByCommonName(Dictionary<string, string[]> names, string commonName, Player owner)
|
||||||
{
|
{
|
||||||
if (!names.Any() || !names.ContainsKey(commonName))
|
if (!names.Any() || !names.ContainsKey(commonName))
|
||||||
throw new InvalidOperationException("Can't find {0} in the HackyAI UnitsCommonNames definition.".F(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;
|
return Map.Rules.Actors.Where(k => names[commonName].Contains(k.Key)).Random(random).Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HasAdequatePower()
|
public bool HasAdequateFact()
|
||||||
{
|
|
||||||
// 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()
|
|
||||||
{
|
{
|
||||||
// Require at least one construction yard, unless we have no vehicles factory (can't build it).
|
// Require at least one construction yard, unless we have no vehicles factory (can't build it).
|
||||||
return CountBuildingByCommonName("ConstructionYard", p) > 0 ||
|
return CountBuildingByCommonName("ConstructionYard", p) > 0 ||
|
||||||
CountBuildingByCommonName("VehiclesFactory", 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).
|
// Require at least one refinery, unless we have no power (can't build it).
|
||||||
return CountBuildingByCommonName("Refinery", p) > 0 ||
|
return CountBuildingByCommonName("Refinery", p) > 0 ||
|
||||||
CountBuildingByCommonName("Power", 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)
|
// Require at least two refineries, unless we have no power (can't build it)
|
||||||
// or barracks (higher priority?)
|
// or barracks (higher priority?)
|
||||||
@@ -263,14 +267,6 @@ namespace OpenRA.Mods.RA.AI
|
|||||||
CountBuildingByCommonName("Barracks", p) == 0;
|
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)
|
// For mods like RA (number of building must match the number of aircraft)
|
||||||
bool HasAdequateAirUnits(ActorInfo actorInfo)
|
bool HasAdequateAirUnits(ActorInfo actorInfo)
|
||||||
{
|
{
|
||||||
@@ -286,108 +282,68 @@ namespace OpenRA.Mods.RA.AI
|
|||||||
return true;
|
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<Building>()
|
|
||||||
.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<BuildingInfo>().Power)
|
|
||||||
return Map.Rules.Actors[frac.Key];
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NoBuildingsUnder(IEnumerable<CPos> cells)
|
|
||||||
{
|
|
||||||
var bi = world.WorldActor.Trait<BuildingInfluence>();
|
|
||||||
return cells.All(c => bi.GetBuildingAt(c) == null);
|
|
||||||
}
|
|
||||||
|
|
||||||
CPos defenseCenter;
|
CPos defenseCenter;
|
||||||
public CPos? ChooseBuildLocation(string actorType, BuildingType type)
|
public CPos? ChooseBuildLocation(string actorType, bool distanceToBaseIsImportant, BuildingType type)
|
||||||
{
|
|
||||||
return ChooseBuildLocation(actorType, true, MaxBaseDistance, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CPos? ChooseBuildLocation(string actorType, bool distanceToBaseIsImportant, int maxBaseDistance, BuildingType type)
|
|
||||||
{
|
{
|
||||||
var bi = Map.Rules.Actors[actorType].Traits.GetOrDefault<BuildingInfo>();
|
var bi = Map.Rules.Actors[actorType].Traits.GetOrDefault<BuildingInfo>();
|
||||||
if (bi == null)
|
if (bi == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
Func<WPos, CPos, CPos?> findPos = (pos, center) =>
|
// Find the buildable cell that is closest to pos and centered around center
|
||||||
|
Func<CPos, CPos, int, int, CPos?> findPos = (center, target, minRange, maxRange) =>
|
||||||
{
|
{
|
||||||
for (var k = MaxBaseDistance; k >= 0; k--)
|
var cells = Map.FindTilesInAnnulus(center, minRange, maxRange);
|
||||||
{
|
|
||||||
var tlist = Map.FindTilesInCircle(center, k);
|
|
||||||
|
|
||||||
foreach (var t in tlist)
|
// Sort by distance to target if we have one
|
||||||
if (world.CanPlaceBuilding(actorType, bi, t, null))
|
if (center != target)
|
||||||
if (bi.IsCloseEnoughToBase(world, p, actorType, t))
|
cells = cells.OrderBy(c => (center - target).LengthSquared);
|
||||||
if (NoBuildingsUnder(Util.ExpandFootprint(FootprintUtils.Tiles(Map.Rules, actorType, bi, t), false)))
|
else
|
||||||
return t;
|
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;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
var baseCenterPos = world.Map.CenterOfCell(baseCenter);
|
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case BuildingType.Defense:
|
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<Building>() && 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:
|
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:
|
case BuildingType.Building:
|
||||||
for (var k = 0; k < maxBaseDistance; k++)
|
return findPos(baseCenter, baseCenter, 0, distanceToBaseIsImportant ? Info.MaxBaseRadius : Map.MaxTilesInCircleRange);
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Can't find a build location
|
// Can't find a build location
|
||||||
@@ -481,14 +437,6 @@ namespace OpenRA.Mods.RA.AI
|
|||||||
&& a.HasTrait<BaseBuilding>() && !a.HasTrait<Mobile>()).ToList();
|
&& a.HasTrait<BaseBuilding>() && !a.HasTrait<Mobile>()).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
Actor FindEnemyBuildingClosestToPos(WPos pos)
|
|
||||||
{
|
|
||||||
var closestBuilding = world.Actors.Where(a => p.Stances[a.Owner] == Stance.Enemy
|
|
||||||
&& !a.Destroyed && a.HasTrait<Building>()).ClosestTo(pos);
|
|
||||||
|
|
||||||
return closestBuilding;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CleanSquads()
|
void CleanSquads()
|
||||||
{
|
{
|
||||||
squads.RemoveAll(s => !s.IsValid);
|
squads.RemoveAll(s => !s.IsValid);
|
||||||
@@ -579,7 +527,6 @@ namespace OpenRA.Mods.RA.AI
|
|||||||
|
|
||||||
foreach (var a in newUnits)
|
foreach (var a in newUnits)
|
||||||
{
|
{
|
||||||
BotDebug("AI: Found a newly built unit");
|
|
||||||
if (a.HasTrait<Harvester>())
|
if (a.HasTrait<Harvester>())
|
||||||
world.IssueOrder(new Order("Harvest", a, false));
|
world.IssueOrder(new Order("Harvest", a, false));
|
||||||
else
|
else
|
||||||
@@ -676,10 +623,6 @@ namespace OpenRA.Mods.RA.AI
|
|||||||
.Where(rp => rp.Actor.Owner == p &&
|
.Where(rp => rp.Actor.Owner == p &&
|
||||||
!IsRallyPointValid(rp.Trait.Location, rp.Actor.Info.Traits.GetOrDefault<BuildingInfo>())).ToArray();
|
!IsRallyPointValid(rp.Trait.Location, rp.Actor.Info.Traits.GetOrDefault<BuildingInfo>())).ToArray();
|
||||||
|
|
||||||
if (buildings.Length > 0)
|
|
||||||
BotDebug("Bot {0} needs to find rallypoints for {1} buildings.",
|
|
||||||
p.PlayerName, buildings.Length);
|
|
||||||
|
|
||||||
foreach (var a in buildings)
|
foreach (var a in buildings)
|
||||||
world.IssueOrder(new Order("SetRallyPoint", a.Actor, false) { TargetLocation = ChooseRallyLocationNear(a.Actor), SuppressVisualFeedback = true });
|
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.
|
// backup location within the main base.
|
||||||
void FindAndDeployBackupMcv(Actor self)
|
void FindAndDeployBackupMcv(Actor self)
|
||||||
{
|
{
|
||||||
var maxBaseDistance = Math.Max(world.Map.MapSize.X, world.Map.MapSize.Y);
|
|
||||||
|
|
||||||
// HACK: This needs to query against MCVs directly
|
// HACK: This needs to query against MCVs directly
|
||||||
var mcvs = self.World.Actors.Where(a => a.Owner == p && a.HasTrait<BaseBuilding>() && a.HasTrait<Mobile>());
|
var mcvs = self.World.Actors.Where(a => a.Owner == p && a.HasTrait<BaseBuilding>() && a.HasTrait<Mobile>());
|
||||||
if (!mcvs.Any())
|
if (!mcvs.Any())
|
||||||
@@ -736,7 +677,7 @@ namespace OpenRA.Mods.RA.AI
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
var factType = mcv.Info.Traits.Get<TransformsInfo>().IntoActor;
|
var factType = mcv.Info.Traits.Get<TransformsInfo>().IntoActor;
|
||||||
var desiredLocation = ChooseBuildLocation(factType, false, maxBaseDistance, BuildingType.Building);
|
var desiredLocation = ChooseBuildLocation(factType, false, BuildingType.Building);
|
||||||
if (desiredLocation == null)
|
if (desiredLocation == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ LobbyDefaults:
|
|||||||
FragileAlliances: false
|
FragileAlliances: false
|
||||||
Shroud: true
|
Shroud: true
|
||||||
Fog: true
|
Fog: true
|
||||||
|
TechLevel: Unrestricted
|
||||||
|
|
||||||
ChromeMetrics:
|
ChromeMetrics:
|
||||||
mods/cnc/metrics.yaml
|
mods/cnc/metrics.yaml
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ Player:
|
|||||||
Power: nuke,nuk2
|
Power: nuke,nuk2
|
||||||
Barracks: pyle,hand
|
Barracks: pyle,hand
|
||||||
VehiclesFactory: weap,afld
|
VehiclesFactory: weap,afld
|
||||||
|
Production: pyle,hand,weap,afld,hpad
|
||||||
Silo: silo
|
Silo: silo
|
||||||
UnitsCommonNames:
|
UnitsCommonNames:
|
||||||
Mcv: mcv
|
Mcv: mcv
|
||||||
@@ -15,11 +16,11 @@ Player:
|
|||||||
UnitQueues: Vehicle.Nod, Vehicle.GDI, Infantry.Nod, Infantry.GDI, Aircraft.Nod, Aircraft.GDI
|
UnitQueues: Vehicle.Nod, Vehicle.GDI, Infantry.Nod, Infantry.GDI, Aircraft.Nod, Aircraft.GDI
|
||||||
BuildingLimits:
|
BuildingLimits:
|
||||||
proc: 4
|
proc: 4
|
||||||
pyle: 2
|
pyle: 3
|
||||||
hand: 2
|
hand: 3
|
||||||
hq: 1
|
hq: 1
|
||||||
weap: 2
|
weap: 3
|
||||||
afld: 2
|
afld: 3
|
||||||
hpad: 0
|
hpad: 0
|
||||||
eye: 1
|
eye: 1
|
||||||
tmpl: 1
|
tmpl: 1
|
||||||
@@ -72,6 +73,7 @@ Player:
|
|||||||
Power: nuke,nuk2
|
Power: nuke,nuk2
|
||||||
Barracks: pyle,hand
|
Barracks: pyle,hand
|
||||||
VehiclesFactory: weap,afld
|
VehiclesFactory: weap,afld
|
||||||
|
Production: pyle,hand,weap,afld,hpad
|
||||||
Silo: silo
|
Silo: silo
|
||||||
UnitsCommonNames:
|
UnitsCommonNames:
|
||||||
Mcv: mcv
|
Mcv: mcv
|
||||||
@@ -80,12 +82,12 @@ Player:
|
|||||||
UnitQueues: Vehicle.Nod, Vehicle.GDI, Infantry.Nod, Infantry.GDI, Aircraft.Nod, Aircraft.GDI
|
UnitQueues: Vehicle.Nod, Vehicle.GDI, Infantry.Nod, Infantry.GDI, Aircraft.Nod, Aircraft.GDI
|
||||||
BuildingLimits:
|
BuildingLimits:
|
||||||
proc: 4
|
proc: 4
|
||||||
pyle: 2
|
pyle: 3
|
||||||
hand: 2
|
hand: 3
|
||||||
hq: 1
|
hq: 1
|
||||||
weap: 2
|
weap: 3
|
||||||
afld: 2
|
afld: 3
|
||||||
hpad: 1
|
hpad: 2
|
||||||
eye: 1
|
eye: 1
|
||||||
tmpl: 1
|
tmpl: 1
|
||||||
fix: 0
|
fix: 0
|
||||||
@@ -137,6 +139,7 @@ Player:
|
|||||||
Power: nuke,nuk2
|
Power: nuke,nuk2
|
||||||
Barracks: pyle,hand
|
Barracks: pyle,hand
|
||||||
VehiclesFactory: weap,afld
|
VehiclesFactory: weap,afld
|
||||||
|
Production: pyle,hand,weap,afld,hpad
|
||||||
Silo: silo
|
Silo: silo
|
||||||
UnitsCommonNames:
|
UnitsCommonNames:
|
||||||
Mcv: mcv
|
Mcv: mcv
|
||||||
@@ -145,12 +148,12 @@ Player:
|
|||||||
UnitQueues: Vehicle.Nod, Vehicle.GDI, Infantry.Nod, Infantry.GDI, Aircraft.Nod, Aircraft.GDI
|
UnitQueues: Vehicle.Nod, Vehicle.GDI, Infantry.Nod, Infantry.GDI, Aircraft.Nod, Aircraft.GDI
|
||||||
BuildingLimits:
|
BuildingLimits:
|
||||||
proc: 4
|
proc: 4
|
||||||
pyle: 2
|
pyle: 4
|
||||||
hand: 4
|
hand: 4
|
||||||
hq: 1
|
hq: 1
|
||||||
weap: 3
|
weap: 4
|
||||||
afld: 2
|
afld: 4
|
||||||
hpad: 1
|
hpad: 2
|
||||||
eye: 1
|
eye: 1
|
||||||
tmpl: 1
|
tmpl: 1
|
||||||
fix: 0
|
fix: 0
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ HELI:
|
|||||||
Description: Helicopter Gunship with Chainguns.\n Strong vs Infantry, Light Vehicles\n Weak vs Tanks
|
Description: Helicopter Gunship with Chainguns.\n Strong vs Infantry, Light Vehicles\n Weak vs Tanks
|
||||||
Buildable:
|
Buildable:
|
||||||
BuildPaletteOrder: 20
|
BuildPaletteOrder: 20
|
||||||
Prerequisites: hpad, anyhq
|
Prerequisites: hpad, anyhq, ~techlevel.medium
|
||||||
Queue: Aircraft.Nod
|
Queue: Aircraft.Nod
|
||||||
Selectable:
|
Selectable:
|
||||||
Bounds: 30,24
|
Bounds: 30,24
|
||||||
@@ -104,7 +104,7 @@ ORCA:
|
|||||||
Description: Helicopter Gunship with AG Missiles.\n Strong vs Buildings, Tanks\n Weak vs Infantry
|
Description: Helicopter Gunship with AG Missiles.\n Strong vs Buildings, Tanks\n Weak vs Infantry
|
||||||
Buildable:
|
Buildable:
|
||||||
BuildPaletteOrder: 20
|
BuildPaletteOrder: 20
|
||||||
Prerequisites: hpad, anyhq
|
Prerequisites: hpad, anyhq, ~techlevel.medium
|
||||||
Queue: Aircraft.GDI
|
Queue: Aircraft.GDI
|
||||||
Selectable:
|
Selectable:
|
||||||
Bounds: 30,24
|
Bounds: 30,24
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ E2:
|
|||||||
Description: Fast infantry armed with grenades. \n Strong vs Buildings, slow-moving targets
|
Description: Fast infantry armed with grenades. \n Strong vs Buildings, slow-moving targets
|
||||||
Buildable:
|
Buildable:
|
||||||
BuildPaletteOrder: 40
|
BuildPaletteOrder: 40
|
||||||
Prerequisites: anyhq
|
Prerequisites: anyhq, ~techlevel.medium
|
||||||
Queue: Infantry.GDI
|
Queue: Infantry.GDI
|
||||||
Selectable:
|
Selectable:
|
||||||
Bounds: 12,17,0,-6
|
Bounds: 12,17,0,-6
|
||||||
@@ -87,7 +87,7 @@ E4:
|
|||||||
Description: Advanced Anti-infantry unit.\n Strong vs Infantry, Buildings\n Weak vs Tanks
|
Description: Advanced Anti-infantry unit.\n Strong vs Infantry, Buildings\n Weak vs Tanks
|
||||||
Buildable:
|
Buildable:
|
||||||
BuildPaletteOrder: 40
|
BuildPaletteOrder: 40
|
||||||
Prerequisites: anyhq
|
Prerequisites: anyhq, ~techlevel.medium
|
||||||
Queue: Infantry.Nod
|
Queue: Infantry.Nod
|
||||||
Selectable:
|
Selectable:
|
||||||
Bounds: 12,17,0,-6
|
Bounds: 12,17,0,-6
|
||||||
@@ -117,7 +117,7 @@ E5:
|
|||||||
Description: Advanced general-purpose infantry.\n Strong vs all Ground units
|
Description: Advanced general-purpose infantry.\n Strong vs all Ground units
|
||||||
Buildable:
|
Buildable:
|
||||||
BuildPaletteOrder: 50
|
BuildPaletteOrder: 50
|
||||||
Prerequisites: tmpl
|
Prerequisites: tmpl, ~techlevel.high
|
||||||
Queue: Infantry.Nod
|
Queue: Infantry.Nod
|
||||||
Selectable:
|
Selectable:
|
||||||
Bounds: 12,17,0,-6
|
Bounds: 12,17,0,-6
|
||||||
@@ -181,7 +181,7 @@ RMBO:
|
|||||||
Description: Elite sniper infantry unit.\n Strong vs Infantry, Buildings\n Weak vs Vehicles
|
Description: Elite sniper infantry unit.\n Strong vs Infantry, Buildings\n Weak vs Vehicles
|
||||||
Buildable:
|
Buildable:
|
||||||
BuildPaletteOrder: 50
|
BuildPaletteOrder: 50
|
||||||
Prerequisites: eye
|
Prerequisites: eye, ~techlevel.high
|
||||||
Queue: Infantry.GDI
|
Queue: Infantry.GDI
|
||||||
Selectable:
|
Selectable:
|
||||||
Bounds: 12,17,0,-6
|
Bounds: 12,17,0,-6
|
||||||
|
|||||||
@@ -17,4 +17,15 @@ Player:
|
|||||||
PlayerStatistics:
|
PlayerStatistics:
|
||||||
FrozenActorLayer:
|
FrozenActorLayer:
|
||||||
PlaceBeacon:
|
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
|
||||||
@@ -110,7 +110,7 @@ NUK2:
|
|||||||
Prerequisite: anypower
|
Prerequisite: anypower
|
||||||
Buildable:
|
Buildable:
|
||||||
BuildPaletteOrder: 30
|
BuildPaletteOrder: 30
|
||||||
Prerequisites: anyhq
|
Prerequisites: anyhq, ~techlevel.medium
|
||||||
Queue: Building.GDI, Building.Nod
|
Queue: Building.GDI, Building.Nod
|
||||||
Building:
|
Building:
|
||||||
Power: 200
|
Power: 200
|
||||||
@@ -402,7 +402,7 @@ HQ:
|
|||||||
Prerequisite: anyhq
|
Prerequisite: anyhq
|
||||||
Buildable:
|
Buildable:
|
||||||
BuildPaletteOrder: 70
|
BuildPaletteOrder: 70
|
||||||
Prerequisites: proc
|
Prerequisites: proc, ~techlevel.medium
|
||||||
Queue: Building.GDI, Building.Nod
|
Queue: Building.GDI, Building.Nod
|
||||||
Building:
|
Building:
|
||||||
Power: -40
|
Power: -40
|
||||||
@@ -423,6 +423,7 @@ HQ:
|
|||||||
DetectCloaked:
|
DetectCloaked:
|
||||||
Range: 5
|
Range: 5
|
||||||
AirstrikePower:
|
AirstrikePower:
|
||||||
|
Prerequisites: ~techlevel.superweapons
|
||||||
Icon: airstrike
|
Icon: airstrike
|
||||||
ChargeTime: 180
|
ChargeTime: 180
|
||||||
SquadSize: 3
|
SquadSize: 3
|
||||||
@@ -477,7 +478,7 @@ EYE:
|
|||||||
Prerequisite: anyhq
|
Prerequisite: anyhq
|
||||||
Buildable:
|
Buildable:
|
||||||
BuildPaletteOrder: 100
|
BuildPaletteOrder: 100
|
||||||
Prerequisites: anyhq
|
Prerequisites: anyhq, ~techlevel.high
|
||||||
Queue: Building.GDI
|
Queue: Building.GDI
|
||||||
Building:
|
Building:
|
||||||
Power: -200
|
Power: -200
|
||||||
@@ -498,6 +499,7 @@ EYE:
|
|||||||
DetectCloaked:
|
DetectCloaked:
|
||||||
Range: 5
|
Range: 5
|
||||||
IonCannonPower:
|
IonCannonPower:
|
||||||
|
Prerequisites: ~techlevel.superweapons
|
||||||
Icon: ioncannon
|
Icon: ioncannon
|
||||||
ChargeTime: 180
|
ChargeTime: 180
|
||||||
Description: Ion Cannon
|
Description: Ion Cannon
|
||||||
@@ -522,7 +524,7 @@ TMPL:
|
|||||||
Prerequisite: anyhq
|
Prerequisite: anyhq
|
||||||
Buildable:
|
Buildable:
|
||||||
BuildPaletteOrder: 100
|
BuildPaletteOrder: 100
|
||||||
Prerequisites: anyhq
|
Prerequisites: anyhq, ~techlevel.high
|
||||||
Queue: Building.Nod
|
Queue: Building.Nod
|
||||||
Building:
|
Building:
|
||||||
Power: -150
|
Power: -150
|
||||||
@@ -537,6 +539,7 @@ TMPL:
|
|||||||
Range: 6c0
|
Range: 6c0
|
||||||
Bib:
|
Bib:
|
||||||
NukePower:
|
NukePower:
|
||||||
|
Prerequisites: ~techlevel.superweapons
|
||||||
Icon: abomb
|
Icon: abomb
|
||||||
ChargeTime: 300
|
ChargeTime: 300
|
||||||
Description: Nuclear Strike
|
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
|
Description: Advanced base defense. \nRequires power to operate.\n Strong vs all Ground units\n Cannot target Aircraft
|
||||||
Buildable:
|
Buildable:
|
||||||
BuildPaletteOrder: 60
|
BuildPaletteOrder: 60
|
||||||
Prerequisites: tmpl
|
Prerequisites: tmpl, ~techlevel.high
|
||||||
Queue: Defence.Nod
|
Queue: Defence.Nod
|
||||||
Building:
|
Building:
|
||||||
Power: -150
|
Power: -150
|
||||||
@@ -729,7 +732,7 @@ ATWR:
|
|||||||
Description: All-purpose defensive structure.\n Strong vs Aircraft, Tanks\n Weak vs Infantry
|
Description: All-purpose defensive structure.\n Strong vs Aircraft, Tanks\n Weak vs Infantry
|
||||||
Buildable:
|
Buildable:
|
||||||
BuildPaletteOrder: 60
|
BuildPaletteOrder: 60
|
||||||
Prerequisites: anyhq
|
Prerequisites: anyhq, ~techlevel.medium
|
||||||
Queue: Defence.GDI
|
Queue: Defence.GDI
|
||||||
Building:
|
Building:
|
||||||
Power: -40
|
Power: -40
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ MCV:
|
|||||||
Description: Deploys into another Construction Yard.\n Unarmed
|
Description: Deploys into another Construction Yard.\n Unarmed
|
||||||
Buildable:
|
Buildable:
|
||||||
BuildPaletteOrder: 100
|
BuildPaletteOrder: 100
|
||||||
Prerequisites: anyhq
|
Prerequisites: anyhq, ~techlevel.medium
|
||||||
Queue: Vehicle.GDI, Vehicle.Nod
|
Queue: Vehicle.GDI, Vehicle.Nod
|
||||||
Selectable:
|
Selectable:
|
||||||
Priority: 3
|
Priority: 3
|
||||||
@@ -130,7 +130,7 @@ ARTY:
|
|||||||
Description: Long-range artillery.\n Strong vs Infantry, Vehicles & Buildings
|
Description: Long-range artillery.\n Strong vs Infantry, Vehicles & Buildings
|
||||||
Buildable:
|
Buildable:
|
||||||
BuildPaletteOrder: 60
|
BuildPaletteOrder: 60
|
||||||
Prerequisites: anyhq
|
Prerequisites: anyhq, ~techlevel.medium
|
||||||
Queue: Vehicle.Nod
|
Queue: Vehicle.Nod
|
||||||
Mobile:
|
Mobile:
|
||||||
ROT: 2
|
ROT: 2
|
||||||
@@ -166,7 +166,7 @@ FTNK:
|
|||||||
Description: Heavily armored flame-throwing vehicle.\n Strong vs Infantry, Buildings & Vehicles\n Weak vs Tanks
|
Description: Heavily armored flame-throwing vehicle.\n Strong vs Infantry, Buildings & Vehicles\n Weak vs Tanks
|
||||||
Buildable:
|
Buildable:
|
||||||
BuildPaletteOrder: 50
|
BuildPaletteOrder: 50
|
||||||
Prerequisites: anyhq
|
Prerequisites: anyhq, ~techlevel.medium
|
||||||
Queue: Vehicle.Nod
|
Queue: Vehicle.Nod
|
||||||
Mobile:
|
Mobile:
|
||||||
ROT: 7
|
ROT: 7
|
||||||
@@ -309,7 +309,7 @@ LTNK:
|
|||||||
Description: Fast, light tank.\n Strong vs Vehicles, Tanks\n Weak vs Infantry
|
Description: Fast, light tank.\n Strong vs Vehicles, Tanks\n Weak vs Infantry
|
||||||
Buildable:
|
Buildable:
|
||||||
BuildPaletteOrder: 40
|
BuildPaletteOrder: 40
|
||||||
Prerequisites: anyhq
|
Prerequisites: anyhq, ~techlevel.medium
|
||||||
Queue: Vehicle.Nod
|
Queue: Vehicle.Nod
|
||||||
Mobile:
|
Mobile:
|
||||||
ROT: 7
|
ROT: 7
|
||||||
@@ -348,7 +348,7 @@ MTNK:
|
|||||||
Description: General-Purpose GDI Tank.\n Strong vs Tanks, Vehicles\n Weak vs Infantry
|
Description: General-Purpose GDI Tank.\n Strong vs Tanks, Vehicles\n Weak vs Infantry
|
||||||
Buildable:
|
Buildable:
|
||||||
BuildPaletteOrder: 40
|
BuildPaletteOrder: 40
|
||||||
Prerequisites: anyhq
|
Prerequisites: anyhq, ~techlevel.medium
|
||||||
Queue: Vehicle.GDI
|
Queue: Vehicle.GDI
|
||||||
Mobile:
|
Mobile:
|
||||||
Speed: 85
|
Speed: 85
|
||||||
@@ -388,7 +388,7 @@ HTNK:
|
|||||||
Description: Heavily armored GDI Tank. \nCan attack Aircraft.\n Strong vs Everything
|
Description: Heavily armored GDI Tank. \nCan attack Aircraft.\n Strong vs Everything
|
||||||
Buildable:
|
Buildable:
|
||||||
BuildPaletteOrder: 60
|
BuildPaletteOrder: 60
|
||||||
Prerequisites: eye
|
Prerequisites: eye, ~techlevel.high
|
||||||
Queue: Vehicle.GDI
|
Queue: Vehicle.GDI
|
||||||
Mobile:
|
Mobile:
|
||||||
Crushes: wall, heavywall, crate, infantry
|
Crushes: wall, heavywall, crate, infantry
|
||||||
@@ -441,7 +441,7 @@ MSAM:
|
|||||||
Description: Long range rocket artillery.\n Strong vs all Ground units.
|
Description: Long range rocket artillery.\n Strong vs all Ground units.
|
||||||
Buildable:
|
Buildable:
|
||||||
BuildPaletteOrder: 50
|
BuildPaletteOrder: 50
|
||||||
Prerequisites: anyhq
|
Prerequisites: anyhq, ~techlevel.medium
|
||||||
Queue: Vehicle.GDI
|
Queue: Vehicle.GDI
|
||||||
Mobile:
|
Mobile:
|
||||||
Speed: 85
|
Speed: 85
|
||||||
@@ -478,7 +478,7 @@ MLRS:
|
|||||||
Description: Powerful anti-air unit. \nCannot attack Ground units.
|
Description: Powerful anti-air unit. \nCannot attack Ground units.
|
||||||
Buildable:
|
Buildable:
|
||||||
BuildPaletteOrder: 70
|
BuildPaletteOrder: 70
|
||||||
Prerequisites: anyhq
|
Prerequisites: anyhq, ~techlevel.medium
|
||||||
Queue: Vehicle.Nod
|
Queue: Vehicle.Nod
|
||||||
Mobile:
|
Mobile:
|
||||||
Speed: 99
|
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.
|
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:
|
Buildable:
|
||||||
BuildPaletteOrder: 90
|
BuildPaletteOrder: 90
|
||||||
Prerequisites: tmpl
|
Prerequisites: tmpl, ~techlevel.high
|
||||||
Queue: Vehicle.Nod
|
Queue: Vehicle.Nod
|
||||||
Mobile:
|
Mobile:
|
||||||
ROT: 10
|
ROT: 10
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ Player:
|
|||||||
ConstructionYard: conyarda,conyardh,conyardo
|
ConstructionYard: conyarda,conyardh,conyardo
|
||||||
Refinery: refa,refh,refo
|
Refinery: refa,refh,refo
|
||||||
Power: pwra,pwrh,pwro
|
Power: pwra,pwrh,pwro
|
||||||
|
Barracks: barra,barrh,barro
|
||||||
VehiclesFactory: lighta,lighth,lighto,heavya,heavyh,heavyo
|
VehiclesFactory: lighta,lighth,lighto,heavya,heavyh,heavyo
|
||||||
|
Production: lighta,lighth,lighto,heavya,heavyh,heavyo,barra,barrh,barro
|
||||||
Silo: siloa, siloh, siloo
|
Silo: siloa, siloh, siloo
|
||||||
UnitsCommonNames:
|
UnitsCommonNames:
|
||||||
Mcv: mcva,mcvh,mcvo
|
Mcv: mcva,mcvh,mcvo
|
||||||
@@ -72,9 +74,9 @@ Player:
|
|||||||
rockettowera: 7%
|
rockettowera: 7%
|
||||||
rockettowerh: 7%
|
rockettowerh: 7%
|
||||||
rockettowero: 7%
|
rockettowero: 7%
|
||||||
powra: 10%
|
pwra: 10%
|
||||||
powrh: 10%
|
pwrh: 10%
|
||||||
powro: 10%
|
pwro: 10%
|
||||||
UnitsToBuild:
|
UnitsToBuild:
|
||||||
rifle: 6%
|
rifle: 6%
|
||||||
bazooka: 5%
|
bazooka: 5%
|
||||||
@@ -100,6 +102,7 @@ Player:
|
|||||||
combath: 100%
|
combath: 100%
|
||||||
combato: 100%
|
combato: 100%
|
||||||
SquadSize: 8
|
SquadSize: 8
|
||||||
|
MaxBaseRadius: 40
|
||||||
HackyAI@Vidius:
|
HackyAI@Vidius:
|
||||||
Name: Vidious
|
Name: Vidious
|
||||||
UnitQueues: Infantry, Vehicle, Armor, Starport
|
UnitQueues: Infantry, Vehicle, Armor, Starport
|
||||||
@@ -107,7 +110,9 @@ Player:
|
|||||||
ConstructionYard: conyarda,conyardh,conyardo
|
ConstructionYard: conyarda,conyardh,conyardo
|
||||||
Refinery: refa,refh,refo
|
Refinery: refa,refh,refo
|
||||||
Power: pwra,pwrh,pwro
|
Power: pwra,pwrh,pwro
|
||||||
|
Barracks: barra,barrh,barro
|
||||||
VehiclesFactory: lighta,lighth,lighto,heavya,heavyh,heavyo
|
VehiclesFactory: lighta,lighth,lighto,heavya,heavyh,heavyo
|
||||||
|
Production: lighta,lighth,lighto,heavya,heavyh,heavyo,barra,barrh,barro
|
||||||
Silo: siloa, siloh, siloo
|
Silo: siloa, siloh, siloo
|
||||||
UnitsCommonNames:
|
UnitsCommonNames:
|
||||||
Mcv: mcva,mcvh,mcvo
|
Mcv: mcva,mcvh,mcvo
|
||||||
@@ -173,9 +178,9 @@ Player:
|
|||||||
rockettowera: 10%
|
rockettowera: 10%
|
||||||
rockettowerh: 10%
|
rockettowerh: 10%
|
||||||
rockettowero: 10%
|
rockettowero: 10%
|
||||||
powra: 12%
|
pwra: 12%
|
||||||
powrh: 12%
|
pwrh: 12%
|
||||||
powro: 12%
|
pwro: 12%
|
||||||
UnitsToBuild:
|
UnitsToBuild:
|
||||||
rifle: 2%
|
rifle: 2%
|
||||||
bazooka: 2%
|
bazooka: 2%
|
||||||
@@ -202,6 +207,7 @@ Player:
|
|||||||
combath: 100%
|
combath: 100%
|
||||||
combato: 100%
|
combato: 100%
|
||||||
SquadSize: 6
|
SquadSize: 6
|
||||||
|
MaxBaseRadius: 40
|
||||||
HackyAI@Gladius:
|
HackyAI@Gladius:
|
||||||
Name: Gladius
|
Name: Gladius
|
||||||
UnitQueues: Infantry, Vehicle, Armor, Starport
|
UnitQueues: Infantry, Vehicle, Armor, Starport
|
||||||
@@ -209,7 +215,9 @@ Player:
|
|||||||
ConstructionYard: conyarda,conyardh,conyardo
|
ConstructionYard: conyarda,conyardh,conyardo
|
||||||
Refinery: refa,refh,refo
|
Refinery: refa,refh,refo
|
||||||
Power: pwra,pwrh,pwro
|
Power: pwra,pwrh,pwro
|
||||||
|
Barracks: barra,barrh,barro
|
||||||
VehiclesFactory: lighta,lighth,lighto,heavya,heavyh,heavyo
|
VehiclesFactory: lighta,lighth,lighto,heavya,heavyh,heavyo
|
||||||
|
Production: lighta,lighth,lighto,heavya,heavyh,heavyo,barra,barrh,barro
|
||||||
Silo: siloa, siloh, siloo
|
Silo: siloa, siloh, siloo
|
||||||
UnitsCommonNames:
|
UnitsCommonNames:
|
||||||
Mcv: mcva,mcvh,mcvo
|
Mcv: mcva,mcvh,mcvo
|
||||||
@@ -271,9 +279,9 @@ Player:
|
|||||||
palaceo: 0.1%
|
palaceo: 0.1%
|
||||||
guntower: 10%
|
guntower: 10%
|
||||||
rockettower: 5%
|
rockettower: 5%
|
||||||
powra: 10%
|
pwra: 10%
|
||||||
powrh: 10%
|
pwrh: 10%
|
||||||
powro: 10%
|
pwro: 10%
|
||||||
UnitsToBuild:
|
UnitsToBuild:
|
||||||
rifle: 15%
|
rifle: 15%
|
||||||
bazooka: 13%
|
bazooka: 13%
|
||||||
@@ -300,4 +308,4 @@ Player:
|
|||||||
combath: 100%
|
combath: 100%
|
||||||
combato: 100%
|
combato: 100%
|
||||||
SquadSize: 10
|
SquadSize: 10
|
||||||
|
MaxBaseRadius: 40
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ Player:
|
|||||||
Power: powr,apwr
|
Power: powr,apwr
|
||||||
Barracks: barr,tent
|
Barracks: barr,tent
|
||||||
VehiclesFactory: weap
|
VehiclesFactory: weap
|
||||||
|
Production: barr,tent,weap,afld,hpad,spen,syrd
|
||||||
Silo: silo
|
Silo: silo
|
||||||
UnitsCommonNames:
|
UnitsCommonNames:
|
||||||
Mcv: mcv
|
Mcv: mcv
|
||||||
@@ -29,7 +30,7 @@ Player:
|
|||||||
barr: 1%
|
barr: 1%
|
||||||
tent: 1%
|
tent: 1%
|
||||||
weap: 1%
|
weap: 1%
|
||||||
pbox.e1: 7%
|
pbox: 7%
|
||||||
gun: 7%
|
gun: 7%
|
||||||
tsla: 5%
|
tsla: 5%
|
||||||
ftur: 10%
|
ftur: 10%
|
||||||
@@ -59,6 +60,7 @@ Player:
|
|||||||
Power: powr,apwr
|
Power: powr,apwr
|
||||||
Barracks: barr,tent
|
Barracks: barr,tent
|
||||||
VehiclesFactory: weap
|
VehiclesFactory: weap
|
||||||
|
Production: barr,tent,weap,afld,hpad,spen,syrd
|
||||||
Silo: silo
|
Silo: silo
|
||||||
UnitsCommonNames:
|
UnitsCommonNames:
|
||||||
Mcv: mcv
|
Mcv: mcv
|
||||||
@@ -87,7 +89,7 @@ Player:
|
|||||||
spen: 1%
|
spen: 1%
|
||||||
syrd: 1%
|
syrd: 1%
|
||||||
afld: 4%
|
afld: 4%
|
||||||
pbox.e1: 7%
|
pbox: 7%
|
||||||
gun: 7%
|
gun: 7%
|
||||||
ftur: 10%
|
ftur: 10%
|
||||||
tsla: 5%
|
tsla: 5%
|
||||||
@@ -125,6 +127,7 @@ Player:
|
|||||||
Power: powr,apwr
|
Power: powr,apwr
|
||||||
Barracks: barr,tent
|
Barracks: barr,tent
|
||||||
VehiclesFactory: weap
|
VehiclesFactory: weap
|
||||||
|
Production: barr,tent,weap,afld,hpad,spen,syrd
|
||||||
Silo: silo
|
Silo: silo
|
||||||
UnitsCommonNames:
|
UnitsCommonNames:
|
||||||
Mcv: mcv
|
Mcv: mcv
|
||||||
@@ -151,7 +154,7 @@ Player:
|
|||||||
hpad: 2%
|
hpad: 2%
|
||||||
spen: 1%
|
spen: 1%
|
||||||
syrd: 1%
|
syrd: 1%
|
||||||
pbox.e1: 7%
|
pbox: 7%
|
||||||
gun: 7%
|
gun: 7%
|
||||||
ftur: 10%
|
ftur: 10%
|
||||||
tsla: 5%
|
tsla: 5%
|
||||||
@@ -188,6 +191,9 @@ Player:
|
|||||||
ConstructionYard: fact
|
ConstructionYard: fact
|
||||||
Refinery: proc
|
Refinery: proc
|
||||||
Power: powr,apwr
|
Power: powr,apwr
|
||||||
|
Barracks: barr,tent
|
||||||
|
VehiclesFactory: weap
|
||||||
|
Production: barr,tent,weap,afld,hpad,spen,syrd
|
||||||
Silo: silo
|
Silo: silo
|
||||||
UnitsCommonNames:
|
UnitsCommonNames:
|
||||||
Mcv: mcv
|
Mcv: mcv
|
||||||
@@ -213,7 +219,7 @@ Player:
|
|||||||
stek: 1%
|
stek: 1%
|
||||||
spen: 1%
|
spen: 1%
|
||||||
syrd: 1%
|
syrd: 1%
|
||||||
pbox.e1: 12%
|
pbox: 12%
|
||||||
gun: 12%
|
gun: 12%
|
||||||
ftur: 12%
|
ftur: 12%
|
||||||
tsla: 12%
|
tsla: 12%
|
||||||
|
|||||||
Reference in New Issue
Block a user