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) {