Bot mcv expansion: 1. Add ResourceMapBotModule. 2. Add McvExpansionManagerBotModule. 3. Give BaseBuilderBotModule better control over refinery. -- allow bot sell refinery too close. -- allow bot sell refinery far away from resource. (requires ResourceMapBotModule) -- allow bot recieve refinery construct requirement. 4. Give BaseBuilderBotModule control of expansion timing. (requires McvExpansionManagerBotModule) 5. Remove unused VehiclesFactoryTypes and useless BarracksTypes (now all use ProductionTypes). 6. Give HarvesterBotModule ability of order harvesters in low resource area to high resource area.
This commit is contained in:
committed by
Gustas Kažukauskas
parent
7a2e25af86
commit
961702d818
@@ -18,26 +18,23 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[TraitLocation(SystemActors.Player)]
|
||||
[Desc("Manages AI base construction.")]
|
||||
public class BaseBuilderBotModuleInfo : ConditionalTraitInfo
|
||||
public class BaseBuilderBotModuleInfo : ConditionalTraitInfo, NotBefore<ResourceMapBotModuleInfo>, NotBefore<IResourceLayerInfo>
|
||||
{
|
||||
[Desc("Tells the AI what building types are considered construction yards.")]
|
||||
public readonly HashSet<string> ConstructionYardTypes = [];
|
||||
|
||||
[Desc("Tells the AI what building types are considered vehicle production facilities.")]
|
||||
public readonly HashSet<string> VehiclesFactoryTypes = [];
|
||||
|
||||
[Desc("Tells the AI what building types are considered refineries.")]
|
||||
public readonly HashSet<string> RefineryTypes = [];
|
||||
|
||||
[Desc("Tells the AI what building types are considered power plants.")]
|
||||
public readonly HashSet<string> PowerTypes = [];
|
||||
|
||||
[Desc("Tells the AI what building types are considered infantry production facilities.")]
|
||||
public readonly HashSet<string> BarracksTypes = [];
|
||||
|
||||
[Desc("Tells the AI what building types are considered production facilities.")]
|
||||
public readonly HashSet<string> ProductionTypes = [];
|
||||
|
||||
[Desc("Tells the AI what building types are considered tech buildings.")]
|
||||
public readonly HashSet<string> TechTypes = [];
|
||||
|
||||
[Desc("Tells the AI what building types are considered naval production facilities.")]
|
||||
public readonly HashSet<string> NavalProductionTypes = [];
|
||||
|
||||
@@ -71,10 +68,10 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("Increase maintained excess power by ExcessPowerIncrement for every N base buildings.")]
|
||||
public readonly int ExcessPowerIncreaseThreshold = 1;
|
||||
|
||||
[Desc("Number of refineries to build before building a barracks.")]
|
||||
[Desc("Number of refineries to build before building any production building.")]
|
||||
public readonly int InititalMinimumRefineryCount = 1;
|
||||
|
||||
[Desc("Number of refineries to build additionally after building a barracks.")]
|
||||
[Desc("Number of refineries to build additionally after building any production building.")]
|
||||
public readonly int AdditionalMinimumRefineryCount = 1;
|
||||
|
||||
[Desc("Additional delay (in ticks) between structure production checks when there is no active production.",
|
||||
@@ -139,15 +136,36 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("Delay (in ticks) between reassigning rally points.")]
|
||||
public readonly int AssignRallyPointsInterval = 100;
|
||||
|
||||
[Desc("Delay (in ticks) for finding a good resource to place a refinery next to.")]
|
||||
public readonly int CheckBestResourceLocationInterval = 151;
|
||||
|
||||
[Desc("Interval (in ticks) between checking whether to sell a redundant refinery. Set to -1 to disable.")]
|
||||
public readonly int SellRefineryInterval = 5000;
|
||||
|
||||
[Desc("Distance (in cells) for refineries finding redundant refineries.")]
|
||||
public readonly int SellRefineryTooCloseCellDistance = 6;
|
||||
|
||||
[Desc("Maximum distance (in cells) from resources before refineries are eligible to be sold.")]
|
||||
public readonly int SellRefineryNoResourceDistance = 12;
|
||||
|
||||
[Desc("Maximum refinery count per area. Area size is defined in " + nameof(ResourceMapBotModule) + ".")]
|
||||
public readonly int MaxRefineryPerIndice = 2;
|
||||
|
||||
[Desc($"AI will move mcv when those numbers of refinery <= productions + tech - {nameof(ExpansionTolerate)}.")]
|
||||
public readonly int[] ExpansionTolerate = [0, 1];
|
||||
|
||||
[Desc($"AI will move the only mcv when those numbers of refinery <= productions + tech - {nameof(ForceExpansionTolerate)}.")]
|
||||
public readonly int[] ForceExpansionTolerate = [2, 3];
|
||||
|
||||
public override object Create(ActorInitializer init) { return new BaseBuilderBotModule(init.Self, this); }
|
||||
}
|
||||
|
||||
public class BaseBuilderBotModule : ConditionalTrait<BaseBuilderBotModuleInfo>, IGameSaveTraitData,
|
||||
IBotTick, IBotPositionsUpdated, IBotRespondToAttack, IBotRequestPauseUnitProduction, INotifyActorDisposing
|
||||
IBotTick, IBotPositionsUpdated, IBotRespondToAttack, IBotRequestPauseUnitProduction, IBotSuggestRefineryProduction, INotifyActorDisposing
|
||||
{
|
||||
public CPos GetRandomBaseCenter()
|
||||
{
|
||||
var randomConstructionYard = constructionYardBuildings.Actors
|
||||
var randomConstructionYard = ConstructionYardBuildings.Actors
|
||||
.RandomOrDefault(world.LocalRandom);
|
||||
|
||||
return randomConstructionYard?.Location ?? initialBaseCenter;
|
||||
@@ -157,6 +175,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
// Actor, ActorCount.
|
||||
public Dictionary<string, int> BuildingsBeingProduced = [];
|
||||
public IBotBaseExpansion[] BaseExpansionModules;
|
||||
public ResourceMapBotModule ResourceMapModule;
|
||||
|
||||
readonly World world;
|
||||
readonly Player player;
|
||||
@@ -166,17 +186,22 @@ namespace OpenRA.Mods.Common.Traits
|
||||
IPathFinder pathFinder;
|
||||
IBotPositionsUpdated[] positionsUpdatedModules;
|
||||
CPos initialBaseCenter;
|
||||
public CPos? ResourceConyardCenter;
|
||||
public Dictionary<Actor, (CPos ConyardLoc, CPos ResourceLoc)> RequestedRefineries = [];
|
||||
|
||||
readonly Stack<TraitPair<RallyPoint>> rallyPoints = [];
|
||||
int assignRallyPointsTicks;
|
||||
int checkBestResourceLocationTicks;
|
||||
int sellRefineryTick;
|
||||
bool firstTick = true;
|
||||
|
||||
readonly BaseBuilderQueueManager[] builders;
|
||||
int currentBuilderIndex = 0;
|
||||
|
||||
readonly ActorIndex.OwnerAndNamesAndTrait<BuildingInfo> refineryBuildings;
|
||||
public readonly ActorIndex.OwnerAndNamesAndTrait<BuildingInfo> RefineryBuildings;
|
||||
readonly ActorIndex.OwnerAndNamesAndTrait<BuildingInfo> powerBuildings;
|
||||
readonly ActorIndex.OwnerAndNamesAndTrait<BuildingInfo> constructionYardBuildings;
|
||||
readonly ActorIndex.OwnerAndNamesAndTrait<BuildingInfo> barracksBuildings;
|
||||
public readonly ActorIndex.OwnerAndNamesAndTrait<BuildingInfo> ConstructionYardBuildings;
|
||||
public readonly ActorIndex.OwnerAndNamesAndTrait<BuildingInfo> ProductionBuildings;
|
||||
|
||||
public BaseBuilderBotModule(Actor self, BaseBuilderBotModuleInfo info)
|
||||
: base(info)
|
||||
@@ -184,10 +209,10 @@ namespace OpenRA.Mods.Common.Traits
|
||||
world = self.World;
|
||||
player = self.Owner;
|
||||
builders = new BaseBuilderQueueManager[info.BuildingQueues.Count + info.DefenseQueues.Count];
|
||||
refineryBuildings = new ActorIndex.OwnerAndNamesAndTrait<BuildingInfo>(world, info.RefineryTypes, player);
|
||||
RefineryBuildings = new ActorIndex.OwnerAndNamesAndTrait<BuildingInfo>(world, info.RefineryTypes, player);
|
||||
powerBuildings = new ActorIndex.OwnerAndNamesAndTrait<BuildingInfo>(world, info.PowerTypes, player);
|
||||
constructionYardBuildings = new ActorIndex.OwnerAndNamesAndTrait<BuildingInfo>(world, info.ConstructionYardTypes, player);
|
||||
barracksBuildings = new ActorIndex.OwnerAndNamesAndTrait<BuildingInfo>(world, info.BarracksTypes, player);
|
||||
ConstructionYardBuildings = new ActorIndex.OwnerAndNamesAndTrait<BuildingInfo>(world, info.ConstructionYardTypes, player);
|
||||
ProductionBuildings = new ActorIndex.OwnerAndNamesAndTrait<BuildingInfo>(world, info.ProductionTypes, player);
|
||||
}
|
||||
|
||||
protected override void Created(Actor self)
|
||||
@@ -197,6 +222,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
resourceLayer = self.World.WorldActor.TraitOrDefault<IResourceLayer>();
|
||||
pathFinder = self.World.WorldActor.TraitOrDefault<IPathFinder>();
|
||||
positionsUpdatedModules = self.Owner.PlayerActor.TraitsImplementing<IBotPositionsUpdated>().ToArray();
|
||||
BaseExpansionModules = self.Owner.PlayerActor.TraitsImplementing<IBotBaseExpansion>().ToArray();
|
||||
|
||||
var i = 0;
|
||||
|
||||
@@ -211,6 +237,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
// Avoid all AIs reevaluating assignments on the same tick, randomize their initial evaluation delay.
|
||||
assignRallyPointsTicks = world.LocalRandom.Next(0, Info.AssignRallyPointsInterval);
|
||||
checkBestResourceLocationTicks = world.LocalRandom.Next(0, Info.CheckBestResourceLocationInterval);
|
||||
sellRefineryTick = Info.SellRefineryInterval < 0 ? 0 : world.LocalRandom.Next(0, Info.SellRefineryInterval);
|
||||
}
|
||||
|
||||
void IBotPositionsUpdated.UpdatedBaseCenter(CPos newLocation)
|
||||
@@ -227,6 +255,12 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
void IBotTick.BotTick(IBot bot)
|
||||
{
|
||||
if (firstTick)
|
||||
{
|
||||
ResourceMapModule = bot.Player.PlayerActor.TraitsImplementing<ResourceMapBotModule>().First(t => t.IsTraitEnabled());
|
||||
firstTick = false;
|
||||
}
|
||||
|
||||
if (--assignRallyPointsTicks <= 0)
|
||||
{
|
||||
assignRallyPointsTicks = Math.Max(2, Info.AssignRallyPointsInterval);
|
||||
@@ -245,6 +279,51 @@ namespace OpenRA.Mods.Common.Traits
|
||||
}
|
||||
}
|
||||
|
||||
if (--checkBestResourceLocationTicks <= 0 && resourceLayer != null)
|
||||
{
|
||||
checkBestResourceLocationTicks = Info.CheckBestResourceLocationInterval;
|
||||
|
||||
// Clear outdated refinery requests that add too many refinery to a map indice
|
||||
if (ResourceMapModule != null)
|
||||
{
|
||||
foreach (var mcv in RequestedRefineries.Keys.ToList())
|
||||
{
|
||||
if (ResourceMapModule.FindClosestIndiceFromCPos(
|
||||
RequestedRefineries[mcv].ResourceLoc).PlayerRefineryCount >= Info.MaxRefineryPerIndice)
|
||||
RequestedRefineries.Remove(mcv);
|
||||
}
|
||||
}
|
||||
|
||||
Actor bestconyard = null;
|
||||
var best = int.MinValue;
|
||||
|
||||
foreach (var conyard in ConstructionYardBuildings.Actors)
|
||||
{
|
||||
if (conyard.IsDead)
|
||||
continue;
|
||||
|
||||
if (!world.Map.FindTilesInAnnulus(conyard.Location, Info.MinBaseRadius, Info.MaxBaseRadius)
|
||||
.Any(c => ResourceMapModule != null
|
||||
? ResourceMapModule.Info.ValuableResourceTypes.Contains(resourceLayer.GetResource(c).Type)
|
||||
: resourceLayer.GetResource(c).Type != null))
|
||||
continue;
|
||||
|
||||
var refs = world.FindActorsInCircle(conyard.CenterPosition, WDist.FromCells(Info.MaxBaseRadius))
|
||||
.Count(a => a.Owner == player && Info.RefineryTypes.Contains(a.Info.Name));
|
||||
|
||||
var suitable = -world.FindActorsInCircle(conyard.CenterPosition, WDist.FromCells(Info.MaxBaseRadius))
|
||||
.Count(a => a.Owner.RelationshipWith(player) == PlayerRelationship.Enemy) - refs;
|
||||
|
||||
if (suitable > best)
|
||||
{
|
||||
best = suitable;
|
||||
bestconyard = conyard;
|
||||
}
|
||||
}
|
||||
|
||||
ResourceConyardCenter = bestconyard?.Location;
|
||||
}
|
||||
|
||||
BuildingsBeingProduced.Clear();
|
||||
|
||||
// PERF: We tick only one type of valid queue at a time
|
||||
@@ -286,6 +365,12 @@ namespace OpenRA.Mods.Common.Traits
|
||||
}
|
||||
|
||||
builders[currentBuilderIndex].Tick(bot, queuesByCategory);
|
||||
|
||||
if (Info.SellRefineryInterval >= 0 && --sellRefineryTick <= 0)
|
||||
{
|
||||
SellUselessRefinery(bot);
|
||||
sellRefineryTick = Info.SellRefineryInterval;
|
||||
}
|
||||
}
|
||||
|
||||
void IBotRespondToAttack.RespondToAttack(IBot bot, Actor self, AttackInfo e)
|
||||
@@ -392,12 +477,12 @@ namespace OpenRA.Mods.Common.Traits
|
||||
// Require at least one refinery, unless we can't build it.
|
||||
public bool HasAdequateRefineryCount() =>
|
||||
Info.RefineryTypes.Count == 0 ||
|
||||
AIUtils.CountActorByCommonName(refineryBuildings) >= MinimumRefineryCount() ||
|
||||
AIUtils.CountActorByCommonName(RefineryBuildings) >= MinimumRefineryCount() ||
|
||||
AIUtils.CountActorByCommonName(powerBuildings) == 0 ||
|
||||
AIUtils.CountActorByCommonName(constructionYardBuildings) == 0;
|
||||
AIUtils.CountActorByCommonName(ConstructionYardBuildings) == 0;
|
||||
|
||||
int MinimumRefineryCount() =>
|
||||
AIUtils.CountActorByCommonName(barracksBuildings) > 0
|
||||
AIUtils.CountActorByCommonName(ProductionBuildings) > 0
|
||||
? Info.InititalMinimumRefineryCount + Info.AdditionalMinimumRefineryCount
|
||||
: Info.InititalMinimumRefineryCount;
|
||||
|
||||
@@ -413,6 +498,37 @@ namespace OpenRA.Mods.Common.Traits
|
||||
];
|
||||
}
|
||||
|
||||
void SellUselessRefinery(IBot bot)
|
||||
{
|
||||
// Sell one refinery each time. Perserve at least one refinery
|
||||
var refineries = world.ActorsHavingTrait<Refinery>().Where(a => a.Owner == player).ToArray();
|
||||
|
||||
if (refineries.Length <= Info.InititalMinimumRefineryCount + Info.AdditionalMinimumRefineryCount)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < refineries.Length; i++)
|
||||
{
|
||||
for (var j = i + 1; j < refineries.Length; j++)
|
||||
{
|
||||
if ((refineries[i].Location - refineries[j].Location).LengthSquared <= Info.SellRefineryTooCloseCellDistance * Info.SellRefineryTooCloseCellDistance)
|
||||
{
|
||||
bot.QueueOrder(new Order("Sell", refineries[i], Target.FromActor(refineries[i]), false));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ResourceMapModule != null &&
|
||||
!world.Map.FindTilesInAnnulus(refineries[i].Location, 0, Info.SellRefineryNoResourceDistance)
|
||||
.Any(c => ResourceMapModule.Info.ValuableResourceTypes.Contains(resourceLayer.GetResource(c).Type))
|
||||
&& !world.FindActorsInCircle(refineries[i].CenterPosition, WDist.FromCells(Info.SellRefineryNoResourceDistance))
|
||||
.Any(a => ResourceMapModule.Info.ResourceCreatorTypes.Contains(a.Info.Name)))
|
||||
{
|
||||
bot.QueueOrder(new Order("Sell", refineries[i], Target.FromActor(refineries[i]), false));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IGameSaveTraitData.ResolveTraitData(Actor self, MiniYaml data)
|
||||
{
|
||||
if (self.World.IsReplay)
|
||||
@@ -429,10 +545,16 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
void INotifyActorDisposing.Disposing(Actor self)
|
||||
{
|
||||
refineryBuildings.Dispose();
|
||||
RefineryBuildings.Dispose();
|
||||
powerBuildings.Dispose();
|
||||
constructionYardBuildings.Dispose();
|
||||
barracksBuildings.Dispose();
|
||||
ConstructionYardBuildings.Dispose();
|
||||
ProductionBuildings.Dispose();
|
||||
}
|
||||
|
||||
void IBotSuggestRefineryProduction.RequestLocation(CPos refineryLocation, CPos conyardLocation, Actor expandActor)
|
||||
{
|
||||
if (ResourceMapModule == null || ResourceMapModule.FindClosestIndiceFromCPos(refineryLocation).PlayerRefineryCount < Info.MaxRefineryPerIndice)
|
||||
RequestedRefineries[expandActor] = (conyardLocation, refineryLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user