From 2c435c050628be41f91f44b2b05104d4059ec617 Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Sat, 3 Aug 2024 20:42:12 +0100 Subject: [PATCH] Ensure starting units or units granted by a crate are not isolated. When spawning starting units, or spawning units when collecting a crate, nearby locations will be used. If a nearby location cannot be reached, e.g. it is on top of a cliff or blocked in by trees, then any unit spawned there will be isolated which is not ideal. Use the PathMightExistForLocomotorBlockedByImmovable method to filter nearby locations to those where the spawned unit can path back to the original location. This ensures the spawned unit is not isolated. --- .../Traits/Crates/DuplicateUnitCrateAction.cs | 22 ++++++++++-- .../Traits/Crates/GiveUnitCrateAction.cs | 36 ++++++++++++++----- .../Traits/World/SpawnStartingUnits.cs | 22 ++++++++++-- 3 files changed, 65 insertions(+), 15 deletions(-) diff --git a/OpenRA.Mods.Common/Traits/Crates/DuplicateUnitCrateAction.cs b/OpenRA.Mods.Common/Traits/Crates/DuplicateUnitCrateAction.cs index ba20c0ad1b..39f3c2fb07 100644 --- a/OpenRA.Mods.Common/Traits/Crates/DuplicateUnitCrateAction.cs +++ b/OpenRA.Mods.Common/Traits/Crates/DuplicateUnitCrateAction.cs @@ -86,10 +86,26 @@ namespace OpenRA.Mods.Common.Traits collector.World.AddFrameEndTask(w => { var candidateCells = collector.World.Map.FindTilesInCircle(collector.Location, info.MaxRadius) - .Where(c => positionable.CanEnterCell(c)).Shuffle(collector.World.SharedRandom) + .Where(c => positionable.CanEnterCell(c)); + + var pathFinder = w.WorldActor.TraitOrDefault(); + if (pathFinder != null) + { + var actorRules = w.Map.Rules.Actors[collector.Info.Name]; + var locomotorName = actorRules.TraitInfoOrDefault()?.Locomotor; + if (locomotorName != null) + { + var locomotor = w.WorldActor.TraitsImplementing().Single(l => l.Info.Name == locomotorName); + candidateCells = candidateCells + .Where(c => pathFinder.PathMightExistForLocomotorBlockedByImmovable(locomotor, c, collector.Location)); + } + } + + var shuffledCandidateCells = candidateCells + .Shuffle(collector.World.SharedRandom) .ToArray(); - var duplicates = Math.Min(candidateCells.Length, info.MaxAmount); + var duplicates = Math.Min(shuffledCandidateCells.Length, info.MaxAmount); // Restrict duplicate count to a maximum value if (info.MaxDuplicateValue > 0) @@ -103,7 +119,7 @@ namespace OpenRA.Mods.Common.Traits { var actor = w.CreateActor(collector.Info.Name, new TypeDictionary { - new LocationInit(candidateCells[i]), + new LocationInit(shuffledCandidateCells[i]), new OwnerInit(info.Owner ?? collector.Owner.InternalName) }); diff --git a/OpenRA.Mods.Common/Traits/Crates/GiveUnitCrateAction.cs b/OpenRA.Mods.Common/Traits/Crates/GiveUnitCrateAction.cs index 71464a5858..2fa3dcb6fd 100644 --- a/OpenRA.Mods.Common/Traits/Crates/GiveUnitCrateAction.cs +++ b/OpenRA.Mods.Common/Traits/Crates/GiveUnitCrateAction.cs @@ -58,10 +58,12 @@ namespace OpenRA.Mods.Common.Traits if (info.ValidFactions.Count > 0 && !info.ValidFactions.Contains(collector.Owner.Faction.InternalName)) return false; + var pathFinder = collector.World.WorldActor.TraitOrDefault(); + var locomotorsByName = collector.World.WorldActor.TraitsImplementing().ToDictionary(l => l.Info.Name); foreach (var unit in info.Units) { // avoid dumping tanks in the sea, and ships on dry land. - if (!GetSuitableCells(collector.Location, unit).Any()) + if (!GetSuitableCells(collector.Location, unit, pathFinder, locomotorsByName).Any()) return false; } @@ -80,9 +82,11 @@ namespace OpenRA.Mods.Common.Traits { collector.World.AddFrameEndTask(w => { + var pathFinder = w.WorldActor.TraitOrDefault(); + var locomotorsByName = w.WorldActor.TraitsImplementing().ToDictionary(l => l.Info.Name); foreach (var unit in info.Units) { - var location = ChooseEmptyCellNear(collector, unit); + var location = ChooseEmptyCellNear(collector, unit, pathFinder, locomotorsByName); if (location != null) { var actor = w.CreateActor(unit, new TypeDictionary @@ -101,19 +105,33 @@ namespace OpenRA.Mods.Common.Traits base.Activate(collector); } - IEnumerable GetSuitableCells(CPos near, string unitName) + IEnumerable GetSuitableCells(CPos near, string unitName, IPathFinder pathFinder, Dictionary locomotorsByName) { - var ip = self.World.Map.Rules.Actors[unitName].TraitInfo(); + var actorRules = self.World.Map.Rules.Actors[unitName]; - for (var i = -1; i < 2; i++) - for (var j = -1; j < 2; j++) - if (ip.CanEnterCell(self.World, self, near + new CVec(i, j))) + Locomotor locomotor = null; + if (pathFinder != null) + { + var locomotorName = actorRules.TraitInfoOrDefault()?.Locomotor; + locomotor = locomotorName != null ? locomotorsByName[locomotorName] : null; + } + + var ip = actorRules.TraitInfo(); + for (var i = -1; i <= 1; i++) + { + for (var j = -1; j <= 1; j++) + { + var cell = near + new CVec(i, j); + if (ip.CanEnterCell(self.World, self, cell) && + (locomotor == null || pathFinder.PathMightExistForLocomotorBlockedByImmovable(locomotor, cell, near))) yield return near + new CVec(i, j); + } + } } - CPos? ChooseEmptyCellNear(Actor a, string unit) + CPos? ChooseEmptyCellNear(Actor a, string unit, IPathFinder pathFinder, Dictionary locomotorsByName) { - return GetSuitableCells(a.Location, unit) + return GetSuitableCells(a.Location, unit, pathFinder, locomotorsByName) .Cast() .RandomOrDefault(self.World.SharedRandom); } diff --git a/OpenRA.Mods.Common/Traits/World/SpawnStartingUnits.cs b/OpenRA.Mods.Common/Traits/World/SpawnStartingUnits.cs index 1c27807a4e..a016bea376 100644 --- a/OpenRA.Mods.Common/Traits/World/SpawnStartingUnits.cs +++ b/OpenRA.Mods.Common/Traits/World/SpawnStartingUnits.cs @@ -21,7 +21,7 @@ namespace OpenRA.Mods.Common.Traits [TraitLocation(SystemActors.World)] [Desc("Spawn base actor at the spawnpoint and support units in an annulus around the base actor. " + "Both are defined at MPStartUnits. Attach this to the world actor.")] - public class SpawnStartingUnitsInfo : TraitInfo, Requires, NotBefore, ILobbyOptions + public class SpawnStartingUnitsInfo : TraitInfo, Requires, NotBefore, ILobbyOptions { public readonly string StartingUnitsClass = "none"; @@ -102,13 +102,29 @@ namespace OpenRA.Mods.Common.Traits if (unitGroup.SupportActors.Length == 0) return; - var supportSpawnCells = w.Map.FindTilesInAnnulus(p.HomeLocation, unitGroup.InnerSupportRadius + 1, unitGroup.OuterSupportRadius); + var supportSpawnCells = w.Map + .FindTilesInAnnulus(p.HomeLocation, unitGroup.InnerSupportRadius + 1, unitGroup.OuterSupportRadius) + .ToList(); + var pathFinder = w.WorldActor.TraitOrDefault(); + var locomotorsByName = w.WorldActor.TraitsImplementing().ToDictionary(l => l.Info.Name); foreach (var s in unitGroup.SupportActors) { var actorRules = w.Map.Rules.Actors[s.ToLowerInvariant()]; var ip = actorRules.TraitInfo(); - var validCell = supportSpawnCells.Shuffle(w.SharedRandom).FirstOrDefault(c => ip.CanEnterCell(w, null, c)); + var validCells = supportSpawnCells.Where(c => ip.CanEnterCell(w, null, c)); + + if (pathFinder != null) + { + var locomotorName = actorRules.TraitInfoOrDefault()?.Locomotor; + var locomotor = locomotorName != null ? locomotorsByName[locomotorName] : null; + + if (locomotor != null) + validCells = validCells + .Where(c => pathFinder.PathMightExistForLocomotorBlockedByImmovable(locomotor, c, p.HomeLocation + unitGroup.BaseActorOffset)); + } + + var validCell = validCells.RandomOrDefault(w.SharedRandom); if (validCell == CPos.Zero) {