diff --git a/OpenRA.Mods.Common/Traits/Buildings/BuildingUtils.cs b/OpenRA.Mods.Common/Traits/Buildings/BuildingUtils.cs index 6e5e18e81d..e899e49fe3 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/BuildingUtils.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/BuildingUtils.cs @@ -22,22 +22,52 @@ namespace OpenRA.Mods.Common.Traits if (!world.Map.Contains(cell)) return false; - var building = world.WorldActor.Trait().GetBuildingAt(cell); - if (building != null) + if (!bi.AllowInvalidPlacement) { - if (ai == null) - return false; + // Replaceable actors are rare, so avoid initializing state unless we have to + var checkReplacements = ai != null && ai.HasTraitInfo(); + HashSet acceptedReplacements = null; - var replacementInfo = ai.TraitInfoOrDefault(); - if (replacementInfo == null) - return false; + var foundActors = false; + foreach (var a in world.ActorMap.GetActorsAt(cell)) + { + if (a == toIgnore) + continue; - if (!building.TraitsImplementing().Any(p => !p.IsTraitDisabled && - p.Info.Types.Overlaps(replacementInfo.ReplaceableTypes))) - return false; + // If this is potentially a replacement actor we must check *all* cell occupants + // before we know the placement is invalid + // Otherwise, we can bail immediately + if (!checkReplacements) + return false; + + foundActors = true; + foreach (var r in a.TraitsImplementing()) + { + if (r.IsTraitDisabled) + continue; + + if (acceptedReplacements == null) + acceptedReplacements = new HashSet(); + + acceptedReplacements.UnionWith(r.Info.Types); + } + } + + // Replacements are enabled and the cell contained at least one (not ignored) actor + if (foundActors) + { + // The cell contains at least one actor, and none were replaceable + if (acceptedReplacements == null) + return false; + + // The cell contains at least one replaceable actor, but not of the types we accept + var foundReplacement = ai.TraitInfos() + .Any(r => r.ReplaceableTypes.Overlaps(acceptedReplacements)); + + if (!foundReplacement) + return false; + } } - else if (!bi.AllowInvalidPlacement && world.ActorMap.GetActorsAt(cell).Any(a => a != toIgnore)) - return false; // Buildings can never be placed on ramps return world.Map.Ramp[cell] == 0 && bi.TerrainTypes.Contains(world.Map.GetTerrainInfo(cell).Type); diff --git a/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs b/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs index 6ebb986aec..0dce17aab3 100644 --- a/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs +++ b/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs @@ -163,16 +163,15 @@ namespace OpenRA.Mods.Common.Traits || !buildingInfo.IsCloseEnoughToBase(self.World, order.Player, actorInfo, targetLocation)) return; - var replacementInfo = actorInfo.TraitInfoOrDefault(); - if (replacementInfo != null) - { - var buildingInfluence = self.World.WorldActor.Trait(); + var replaceableTypes = actorInfo.TraitInfos() + .SelectMany(r => r.ReplaceableTypes) + .ToHashSet(); + + if (replaceableTypes.Any()) foreach (var t in buildingInfo.Tiles(targetLocation)) - { - var host = buildingInfluence.GetBuildingAt(t); - host?.World.Remove(host); - } - } + foreach (var a in self.World.ActorMap.GetActorsAt(t)) + if (a.TraitsImplementing().Any(r => !r.IsTraitDisabled && r.Info.Types.Overlaps(replaceableTypes))) + self.World.Remove(a); var building = w.CreateActor(actorInfo.Name, new TypeDictionary { diff --git a/OpenRA.Mods.Common/Traits/Replaceable.cs b/OpenRA.Mods.Common/Traits/Replaceable.cs index 6aeb8d0e05..af9029136b 100644 --- a/OpenRA.Mods.Common/Traits/Replaceable.cs +++ b/OpenRA.Mods.Common/Traits/Replaceable.cs @@ -16,15 +16,15 @@ namespace OpenRA.Mods.Common.Traits public class ReplaceableInfo : ConditionalTraitInfo { [FieldLoader.Require] - [Desc("Replacement types this Relpaceable actor accepts.")] + [Desc("Replacement types this Replaceable actor accepts.")] public readonly HashSet Types = new HashSet(); - public override object Create(ActorInitializer init) { return new Replaceable(init, this); } + public override object Create(ActorInitializer init) { return new Replaceable(this); } } public class Replaceable : ConditionalTrait { - public Replaceable(ActorInitializer init, ReplaceableInfo info) + public Replaceable(ReplaceableInfo info) : base(info) { } } } diff --git a/OpenRA.Mods.Common/Traits/Replacement.cs b/OpenRA.Mods.Common/Traits/Replacement.cs index dbaec5dc5b..3a9d3bfaba 100644 --- a/OpenRA.Mods.Common/Traits/Replacement.cs +++ b/OpenRA.Mods.Common/Traits/Replacement.cs @@ -17,7 +17,7 @@ namespace OpenRA.Mods.Common.Traits public class ReplacementInfo : TraitInfo { [FieldLoader.Require] - [Desc("Replacement type (matched against Conditions in Replaceable).")] + [Desc("Replacement type (matched against Types in Replaceable).")] public readonly HashSet ReplaceableTypes = new HashSet(); }