From 218f91d50e7fa415a8ead91803e17247f42397d9 Mon Sep 17 00:00:00 2001 From: dnqbob Date: Wed, 2 Mar 2022 17:39:34 +0800 Subject: [PATCH] Add place variant building for BaseBuilderBotModule. 1. If it follow the refinery placing logic, then we can use Facings in PlaceBuildingVariants to help BaseBuilderBotModule "rotates" it to minefield. 2. If it is a normal building, BaseBuilderBotModule will place a random variant actor. --- .../BotModuleLogic/BaseBuilderQueueManager.cs | 86 ++++++++++++++++--- .../Traits/Buildings/PlaceBuildingVariants.cs | 3 + 2 files changed, 78 insertions(+), 11 deletions(-) diff --git a/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/BaseBuilderQueueManager.cs b/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/BaseBuilderQueueManager.cs index 70822933d342..4ca7e60ffff8 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/BaseBuilderQueueManager.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/BaseBuilderQueueManager.cs @@ -135,11 +135,13 @@ bool TickQueue(IBot bot, ProductionQueue queue) // TODO: Derive this from BuildingCommonNames instead var type = BuildingType.Building; CPos? location = null; + var actorVariant = 0; string orderString = "PlaceBuilding"; // Check if Building is a plug for other Building var actorInfo = world.Map.Rules.Actors[currentBuilding.Item]; var plugInfo = actorInfo.TraitInfoOrDefault(); + if (plugInfo != null) { var possibleBuilding = world.ActorsWithTrait().FirstOrDefault(a => @@ -159,7 +161,9 @@ bool TickQueue(IBot bot, ProductionQueue queue) else if (baseBuilder.Info.RefineryTypes.Contains(actorInfo.Name)) type = BuildingType.Refinery; - location = ChooseBuildLocation(currentBuilding.Item, true, type); + var pack = ChooseBuildLocation(currentBuilding.Item, true, type); + location = pack.Location; + actorVariant = pack.Variant; } if (location == null) @@ -184,6 +188,9 @@ bool TickQueue(IBot bot, ProductionQueue queue) // Building to place TargetString = currentBuilding.Item, + // Actor variant will always be small enough to safely pack in a CPos + ExtraLocation = new CPos(actorVariant, 0), + // Actor ID to associate the placement with ExtraData = queue.Actor.ActorID, SuppressVisualFeedback = true @@ -325,8 +332,14 @@ ActorInfo ChooseBuildingToBuild(ProductionQueue queue) if (!buildableThings.Any(b => b.Name == name)) continue; + // Check the number of this structure and its variants + var actorInfo = world.Map.Rules.Actors[name]; + var buildingVariantInfo = actorInfo.TraitInfoOrDefault(); + var variants = buildingVariantInfo?.Actors ?? Array.Empty(); + + var count = playerBuildings.Count(a => a.Info.Name == name || variants.Contains(a.Info.Name)); + // Do we want to build this structure? - var count = playerBuildings.Count(a => a.Info.Name == name); if (count * 100 > frac.Value * playerBuildings.Length) continue; @@ -368,36 +381,86 @@ ActorInfo ChooseBuildingToBuild(ProductionQueue queue) return null; } - CPos? ChooseBuildLocation(string actorType, bool distanceToBaseIsImportant, BuildingType type) + (CPos? Location, int Variant) ChooseBuildLocation(string actorType, bool distanceToBaseIsImportant, BuildingType type) { var actorInfo = world.Map.Rules.Actors[actorType]; var bi = actorInfo.TraitInfoOrDefault(); + if (bi == null) - return null; + return (null, 0); // Find the buildable cell that is closest to pos and centered around center - Func findPos = (center, target, minRange, maxRange) => + Func findPos = (center, target, minRange, maxRange) => { + var actorVariant = 0; + var buildingVariantInfo = actorInfo.TraitInfoOrDefault(); + var variantActorInfo = actorInfo; + var vbi = bi; + var cells = world.Map.FindTilesInAnnulus(center, minRange, maxRange); // Sort by distance to target if we have one if (center != target) + { cells = cells.OrderBy(c => (c - target).LengthSquared); + + // Rotate building if we have a Facings in buildingVariantInfo. + // if we don't have Facings in buildingVariantInfo, use a random variant + if (buildingVariantInfo?.Actors != null) + { + if (buildingVariantInfo.Facings != null) + { + var vector = world.Map.CenterOfCell(target) - world.Map.CenterOfCell(center); + + // The rotation Y point to upside vertically, so -Y = Y(rotation) + var desireFacing = new WAngle(WAngle.ArcSin((int)((long)Math.Abs(vector.X) * 1024 / vector.Length)).Angle); + if (vector.X > 0 && vector.Y >= 0) + desireFacing = new WAngle(512) - desireFacing; + else if (vector.X < 0 && vector.Y >= 0) + desireFacing = new WAngle(512) + desireFacing; + else if (vector.X < 0 && vector.Y < 0) + desireFacing = -desireFacing; + + for (int i = 0, e = 1024; i < buildingVariantInfo.Facings.Length; i++) + { + var minDelta = Math.Min((desireFacing - buildingVariantInfo.Facings[i]).Angle, (buildingVariantInfo.Facings[i] - desireFacing).Angle); + if (e > minDelta) + { + e = minDelta; + actorVariant = i; + } + } + } + else + actorVariant = world.LocalRandom.Next(buildingVariantInfo.Actors.Length + 1); + } + } else + { cells = cells.Shuffle(world.LocalRandom); + if (buildingVariantInfo?.Actors != null) + actorVariant = world.LocalRandom.Next(buildingVariantInfo.Actors.Length + 1); + } + + if (actorVariant != 0) + { + variantActorInfo = world.Map.Rules.Actors[buildingVariantInfo.Actors[actorVariant - 1]]; + vbi = variantActorInfo.TraitInfoOrDefault(); + } + foreach (var cell in cells) { - if (!world.CanPlaceBuilding(cell, actorInfo, bi, null)) + if (!world.CanPlaceBuilding(cell, variantActorInfo, vbi, null)) continue; - if (distanceToBaseIsImportant && !bi.IsCloseEnoughToBase(world, player, actorInfo, cell)) + if (distanceToBaseIsImportant && !vbi.IsCloseEnoughToBase(world, player, variantActorInfo, cell)) continue; - return cell; + return (cell, actorVariant); } - return null; + return (null, 0); }; var baseCenter = baseBuilder.GetRandomBaseCenter(); @@ -411,6 +474,7 @@ ActorInfo ChooseBuildingToBuild(ProductionQueue queue) .ClosestTo(world.Map.CenterOfCell(baseBuilder.DefenseCenter)); var targetCell = closestEnemy != null ? closestEnemy.Location : baseCenter; + return findPos(baseBuilder.DefenseCenter, targetCell, baseBuilder.Info.MinimumDefenseRadius, baseBuilder.Info.MaximumDefenseRadius); case BuildingType.Refinery: @@ -425,7 +489,7 @@ ActorInfo ChooseBuildingToBuild(ProductionQueue queue) foreach (var r in nearbyResources) { var found = findPos(baseCenter, r, baseBuilder.Info.MinBaseRadius, baseBuilder.Info.MaxBaseRadius); - if (found != null) + if (found.Location != null) return found; } } @@ -439,7 +503,7 @@ ActorInfo ChooseBuildingToBuild(ProductionQueue queue) } // Can't find a build location - return null; + return (null, 0); } } } diff --git a/OpenRA.Mods.Common/Traits/Buildings/PlaceBuildingVariants.cs b/OpenRA.Mods.Common/Traits/Buildings/PlaceBuildingVariants.cs index 45de538540fb..b1a2a152c3f9 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/PlaceBuildingVariants.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/PlaceBuildingVariants.cs @@ -21,6 +21,9 @@ public class PlaceBuildingVariantsInfo : TraitInfo, Requi [Desc("Variant actors that can be cycled between when placing a structure.")] public readonly string[] Actors = null; + [Desc("Facing of the non-variant actor, followed by facings for each variant actor. The length equals the length of Actors + 1.")] + public readonly WAngle[] Facings = null; + public override object Create(ActorInitializer init) { return new PlaceBuildingVariants(); } }