Skip to content

Commit

Permalink
Added support for bots protecting structures with walls
Browse files Browse the repository at this point in the history
From here OpenRA#19853
  • Loading branch information
ottelo9 committed Oct 29, 2024
1 parent fffc252 commit 8d3c0ee
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 23 deletions.
8 changes: 7 additions & 1 deletion OpenRA.Mods.Common/Traits/BotModules/BaseBuilderBotModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ public class BaseBuilderBotModuleInfo : ConditionalTraitInfo
[Desc("Production queues AI uses for defenses.")]
public readonly HashSet<string> DefenseQueues = new() { "Defense" };

[Desc("Tells the AI what building types are considered barriers.")]
public readonly HashSet<string> WallTypes = new();

[Desc("Buildings that should be walled off, if any.")]
public readonly HashSet<string> WalledStructures = new();

[Desc("Minimum distance in cells from center of the base when checking for building placement.")]
public readonly int MinBaseRadius = 2;

Expand Down Expand Up @@ -334,7 +340,7 @@ CPos ChooseRallyLocationNear(Actor producer)

if (possibleRallyPoints.Count == 0)
{
AIUtils.BotDebug("{0} has no possible rallypoint near {1}", producer.Owner, producer.Location);
AIUtils.BotDebug($"{producer.Owner} has no possible rallypoint near {producer.Location}");
return producer.Location;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,11 @@ bool TickQueue(IBot bot, ProductionQueue queue)
location = possibleBuilding.Actor.Location + possibleBuilding.Trait.Info.Offset;
}
}
else if (actorInfo.HasTraitInfo<LineBuildInfo>())
{
orderString = "LineBuild";
location = ChooseWallLocation(actorInfo).Location;
}
else
{
// Check if Building is a defense and if we should place it towards the enemy or not.
Expand Down Expand Up @@ -254,7 +259,7 @@ ActorInfo ChooseBuildingToBuild(ProductionQueue queue)
if (playerPower != null && playerPower.ExcessPower < minimumExcessPower &&
power != null && power.TraitInfos<PowerInfo>().Where(i => i.EnabledByDefault).Sum(p => p.Amount) > 0)
{
AIUtils.BotDebug("{0} decided to build {1}: Priority override (low power)", queue.Actor.Owner, power.Name);
AIUtils.BotDebug($"{queue.Actor.Owner} decided to build {power.Name}: Priority override (low power)");
return power;
}

Expand All @@ -264,30 +269,44 @@ ActorInfo ChooseBuildingToBuild(ProductionQueue queue)
var refinery = GetProducibleBuilding(baseBuilder.Info.RefineryTypes, buildableThings);
if (refinery != null && HasSufficientPowerForActor(refinery))
{
AIUtils.BotDebug("{0} decided to build {1}: Priority override (refinery)", queue.Actor.Owner, refinery.Name);
AIUtils.BotDebug($"{queue.Actor.Owner} decided to build {refinery.Name}: Priority override (refinery)");
return refinery;
}

if (power != null && refinery != null && !HasSufficientPowerForActor(refinery))
{
AIUtils.BotDebug("{0} decided to build {1}: Priority override (would be low power)", queue.Actor.Owner, power.Name);
AIUtils.BotDebug($"{queue.Actor.Owner} decided to build {power.Name}: Priority override (would be low power)");
return power;
}
}

if (baseBuilder.Info.WalledStructures.Count > 0)
{
var wall = GetProducibleBuilding(baseBuilder.Info.WallTypes, buildableThings);
if (wall != null && HasSufficientPowerForActor(wall))
{
var possibleLocation = ChooseWallLocation(wall);
if (possibleLocation.Location != null)
{
AIUtils.BotDebug($"{queue.Actor.Owner} decided to build {wall.Name}: Priority override (wall)");
return wall;
}
}
}

// Make sure that we can spend as fast as we are earning
if (baseBuilder.Info.NewProductionCashThreshold > 0 && playerResources.GetCashAndResources() > baseBuilder.Info.NewProductionCashThreshold)
{
var production = GetProducibleBuilding(baseBuilder.Info.ProductionTypes, buildableThings);
if (production != null && HasSufficientPowerForActor(production))
{
AIUtils.BotDebug("{0} decided to build {1}: Priority override (production)", queue.Actor.Owner, production.Name);
AIUtils.BotDebug($"{queue.Actor.Owner} decided to build {production.Name}: Priority override (production)");
return production;
}

if (power != null && production != null && !HasSufficientPowerForActor(production))
{
AIUtils.BotDebug("{0} decided to build {1}: Priority override (would be low power)", queue.Actor.Owner, power.Name);
AIUtils.BotDebug($"{queue.Actor.Owner} decided to build {power.Name}: Priority override (would be low power)");
return power;
}
}
Expand All @@ -300,13 +319,13 @@ ActorInfo ChooseBuildingToBuild(ProductionQueue queue)
var navalproduction = GetProducibleBuilding(baseBuilder.Info.NavalProductionTypes, buildableThings);
if (navalproduction != null && HasSufficientPowerForActor(navalproduction))
{
AIUtils.BotDebug("{0} decided to build {1}: Priority override (navalproduction)", queue.Actor.Owner, navalproduction.Name);
AIUtils.BotDebug($"{queue.Actor.Owner} decided to build {navalproduction.Name}: Priority override (navalproduction)");
return navalproduction;
}

if (power != null && navalproduction != null && !HasSufficientPowerForActor(navalproduction))
{
AIUtils.BotDebug("{0} decided to build {1}: Priority override (would be low power)", queue.Actor.Owner, power.Name);
AIUtils.BotDebug($"{queue.Actor.Owner} decided to build {power.Name}: Priority override (would be low power)");
return power;
}
}
Expand All @@ -317,13 +336,13 @@ ActorInfo ChooseBuildingToBuild(ProductionQueue queue)
var silo = GetProducibleBuilding(baseBuilder.Info.SiloTypes, buildableThings);
if (silo != null && HasSufficientPowerForActor(silo))
{
AIUtils.BotDebug("{0} decided to build {1}: Priority override (silo)", queue.Actor.Owner, silo.Name);
AIUtils.BotDebug($"{queue.Actor.Owner} decided to build {silo.Name}: Priority override (silo)");
return silo;
}

if (power != null && silo != null && !HasSufficientPowerForActor(silo))
{
AIUtils.BotDebug("{0} decided to build {1}: Priority override (would be low power)", queue.Actor.Owner, power.Name);
AIUtils.BotDebug($"{queue.Actor.Owner} decided to build {power.Name}: Priority override (would be low power)");
return power;
}
}
Expand Down Expand Up @@ -375,9 +394,9 @@ ActorInfo ChooseBuildingToBuild(ProductionQueue queue)
if (power != null && power.TraitInfos<PowerInfo>().Where(i => i.EnabledByDefault).Sum(pi => pi.Amount) > 0)
{
if (playerPower.PowerOutageRemainingTicks > 0)
AIUtils.BotDebug("{0} decided to build {1}: Priority override (is low power)", queue.Actor.Owner, power.Name);
AIUtils.BotDebug($"{queue.Actor.Owner} decided to build {power.Name}: Priority override (is low power)");
else
AIUtils.BotDebug("{0} decided to build {1}: Priority override (would be low power)", queue.Actor.Owner, power.Name);
AIUtils.BotDebug($"{queue.Actor.Owner} decided to build {power.Name}: Priority override (would be low power)");

return power;
}
Expand All @@ -390,25 +409,54 @@ ActorInfo ChooseBuildingToBuild(ProductionQueue queue)
}

// Too spammy to keep enabled all the time, but very useful when debugging specific issues.
// AIUtils.BotDebug("{0} couldn't decide what to build for queue {1}.", queue.Actor.Owner, queue.Info.Group);
// AIUtils.BotDebug($"{queue.Actor.Owner} couldn't decide what to build for queue {queue.Info.Group}.");
return null;
}

(CPos? Location, int Variant) ChooseWallLocation(ActorInfo actorInfo)
{
// Build around important structure
var buildingToWall = world.ActorsWithTrait<Building>().FirstOrDefault(a => !a.Actor.Disposed && a.Actor.Owner == player &&
baseBuilder.Info.WalledStructures.Contains(a.Actor.Info.Name));

if (buildingToWall.Actor == null)
return (null, 0);

var topLeft = buildingToWall.Actor.Location;
var edges = new List<CPos>()
{
topLeft + new CVec(-1, -1),
topLeft + new CVec(buildingToWall.Trait.Info.Dimensions.X, -1),
topLeft + new CVec(-1, buildingToWall.Trait.Info.Dimensions.Y),
topLeft + buildingToWall.Trait.Info.Dimensions,
};

var buildingInfo = actorInfo.TraitInfoOrDefault<BuildingInfo>();
if (buildingInfo == null)
return (null, 0);

var possibleEdges = edges.Where(e => world.CanPlaceBuilding(e, actorInfo, buildingInfo, null));
if (!possibleEdges.Any())
return (null, 0);

return (possibleEdges.Random(world.LocalRandom), 0);
}

(CPos? Location, int Variant) ChooseBuildLocation(string actorType, bool distanceToBaseIsImportant, BuildingType type)
{
var actorInfo = world.Map.Rules.Actors[actorType];
var bi = actorInfo.TraitInfoOrDefault<BuildingInfo>();
var buildingInfo = actorInfo.TraitInfoOrDefault<BuildingInfo>();

if (bi == null)
if (buildingInfo == null)
return (null, 0);

// Find the buildable cell that is closest to pos and centered around center
(CPos? Location, int Variant) FindPos(CPos center, CPos target, int minRange, int maxRange)
{
var actorVariant = 0;
var buildingVariantInfo = actorInfo.TraitInfoOrDefault<PlaceBuildingVariantsInfo>();
var variantActorInfo = actorInfo;
var vbi = bi;
var actorInfoVariant = actorInfo;
var buildingInfoVariant = buildingInfo;

var cells = world.Map.FindTilesInAnnulus(center, minRange, maxRange);

Expand Down Expand Up @@ -458,16 +506,16 @@ ActorInfo ChooseBuildingToBuild(ProductionQueue queue)

if (actorVariant != 0)
{
variantActorInfo = world.Map.Rules.Actors[buildingVariantInfo.Actors[actorVariant - 1]];
vbi = variantActorInfo.TraitInfoOrDefault<BuildingInfo>();
actorInfoVariant = world.Map.Rules.Actors[buildingVariantInfo.Actors[actorVariant - 1]];
buildingInfoVariant = actorInfoVariant.TraitInfoOrDefault<BuildingInfo>();
}

foreach (var cell in cells)
{
if (!world.CanPlaceBuilding(cell, variantActorInfo, vbi, null))
if (!world.CanPlaceBuilding(cell, actorInfoVariant, buildingInfoVariant, null))
continue;

if (distanceToBaseIsImportant && !vbi.IsCloseEnoughToBase(world, player, variantActorInfo, cell))
if (distanceToBaseIsImportant && !buildingInfoVariant.IsCloseEnoughToBase(world, player, actorInfoVariant, cell))
continue;

return (cell, actorVariant);
Expand Down
12 changes: 10 additions & 2 deletions mods/ra/rules/ai.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ Player:
ProductionTypes: barr,tent,weap
SiloTypes: silo
DefenseTypes: hbox,pbox,gun,ftur,tsla,agun,sam
WallTypes: brik
WalledStructures: fact
BuildingLimits:
proc: 4
barr: 1
Expand Down Expand Up @@ -143,14 +145,16 @@ Player:
NavalProductionTypes: spen, syrd
SiloTypes: silo
DefenseTypes: hbox,pbox,gun,ftur,tsla,agun,sam
WallTypes: brik
WalledStructures: fact
BuildingLimits:
proc: 6
barr: 3
tent: 3
dome: 1
weap: 3
spen: 2
syrd: 2
spen: 1
syrd: 1
hpad: 4
afld: 4
afld.ukraine: 4
Expand Down Expand Up @@ -197,6 +201,8 @@ Player:
NavalProductionTypes: spen, syrd
SiloTypes: silo
DefenseTypes: hbox,pbox,gun,ftur,tsla,agun,sam
WallTypes: brik
WalledStructures: fact
BuildingLimits:
proc: 4
barr: 1
Expand Down Expand Up @@ -252,6 +258,8 @@ Player:
NavalProductionTypes: spen, syrd
SiloTypes: silo
DefenseTypes: hbox,pbox,gun,ftur,tsla,agun,sam
WallTypes: brik
WalledStructures: fact
BuildingLimits:
proc: 3
dome: 1
Expand Down

0 comments on commit 8d3c0ee

Please sign in to comment.