diff --git a/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/BaseBuilderQueueManager.cs b/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/BaseBuilderQueueManager.cs index 81c55606b5..4e4cc7b03f 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/BaseBuilderQueueManager.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/BaseBuilderQueueManager.cs @@ -135,11 +135,13 @@ namespace OpenRA.Mods.Common.Traits // 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 @@ namespace OpenRA.Mods.Common.Traits 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 @@ namespace OpenRA.Mods.Common.Traits // 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 @@ namespace OpenRA.Mods.Common.Traits 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 @@ namespace OpenRA.Mods.Common.Traits 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 @@ namespace OpenRA.Mods.Common.Traits .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 @@ namespace OpenRA.Mods.Common.Traits 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 @@ namespace OpenRA.Mods.Common.Traits } // 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 45de538540..b1a2a152c3 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/PlaceBuildingVariants.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/PlaceBuildingVariants.cs @@ -21,6 +21,9 @@ namespace OpenRA.Mods.Common.Traits [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(); } }