diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index 74ba23fa9f..d8166e0d18 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -180,9 +180,9 @@ namespace OpenRA.Traits public interface IPositionable : IOccupySpace { - bool CanEnterCell(CPos location); - bool CanEnterCell(CPos location, Actor ignoreActor, bool checkTransientActors); - int GetDesiredSubcell(CPos a, Actor ignoreActor); + bool IsMovingFrom(CPos location, int subCell = -1); + bool CanEnterCell(CPos location, Actor ignoreActor = null, bool checkTransientActors = true); + int GetAvailableSubcell(CPos location, int preferredSubCell = -1, Actor ignoreActor = null, bool checkTransientActors = true); void SetPosition(Actor self, CPos cell, int subCell = -1); void SetPosition(Actor self, WPos pos); void SetVisualPosition(Actor self, WPos pos); diff --git a/OpenRA.Game/Traits/World/ActorMap.cs b/OpenRA.Game/Traits/World/ActorMap.cs index c4015d6e2b..8f85db0e15 100644 --- a/OpenRA.Game/Traits/World/ActorMap.cs +++ b/OpenRA.Game/Traits/World/ActorMap.cs @@ -83,35 +83,68 @@ namespace OpenRA.Traits yield return i.Actor; } - public bool HasFreeSubCell(CPos a) + public bool HasFreeSubCell(CPos a, bool checkTransient = true) { - return FreeSubCell(a) >= 0; + return FreeSubCell(a, -1, checkTransient) >= 0; } - public int FreeSubCell(CPos a, int preferredSubCell = -1) + public int FreeSubCell(CPos a, int preferredSubCell = -1, bool checkTransient = true) { - if (preferredSubCell >= 0 && !AnyUnitsAt(a, preferredSubCell)) + if (preferredSubCell >= 0 && !AnyUnitsAt(a, preferredSubCell, checkTransient)) return preferredSubCell; if (!AnyUnitsAt(a)) return map.SubCellDefaultIndex; for (var i = 1; i < map.SubCellOffsets.Length; i++) - if (!AnyUnitsAt(a, i)) + if (i != preferredSubCell && !AnyUnitsAt(a, i, checkTransient)) return i; return -1; } + public int FreeSubCell(CPos a, int preferredSubCell, Func checkIfBlocker) + { + if (preferredSubCell >= 0 && !AnyUnitsAt(a, preferredSubCell, checkIfBlocker)) + return preferredSubCell; + + if (!AnyUnitsAt(a)) + return map.SubCellDefaultIndex; + + for (var i = 1; i < map.SubCellOffsets.Length; i++) + if (i != preferredSubCell && !AnyUnitsAt(a, i, checkIfBlocker)) + return i; + return -1; + } + + // NOTE: does not check transients, but checks aircraft public bool AnyUnitsAt(CPos a) { return influence[a] != null; } - public bool AnyUnitsAt(CPos a, int sub) + // NOTE: can not check aircraft + public bool AnyUnitsAt(CPos a, int sub, bool checkTransient = true) { for (var i = influence[a]; i != null; i = i.Next) - if (i.SubCell == sub || i.SubCell == 0) - return true; + if (sub <= 0 || i.SubCell == sub || i.SubCell == 0) + { + if (checkTransient) + return true; + var pos = i.Actor.TraitOrDefault(); + if (pos == null || !pos.IsMovingFrom(a, i.SubCell)) + return true; + } + + return false; + } + + // NOTE: can not check aircraft + public bool AnyUnitsAt(CPos a, int sub, Func withCondition) + { + for (var i = influence[a]; i != null; i = i.Next) + if (sub <= 0 || i.SubCell == sub || i.SubCell == 0) + if (withCondition(i.Actor)) + return true; return false; } diff --git a/OpenRA.Mods.RA/Activities/UnloadCargo.cs b/OpenRA.Mods.RA/Activities/UnloadCargo.cs index fb4f68ec4f..7cd74300a0 100644 --- a/OpenRA.Mods.RA/Activities/UnloadCargo.cs +++ b/OpenRA.Mods.RA/Activities/UnloadCargo.cs @@ -38,7 +38,7 @@ namespace OpenRA.Mods.RA.Activities return cargo.CurrentAdjacentCells .Shuffle(self.World.SharedRandom) - .Select(c => Pair.New(c, pos.GetDesiredSubcell(c, null))) + .Select(c => Pair.New(c, pos.GetAvailableSubcell(c, -1, null))) .Cast?>() .FirstOrDefault(s => s.Value.Second >= 0); } diff --git a/OpenRA.Mods.RA/Air/Aircraft.cs b/OpenRA.Mods.RA/Air/Aircraft.cs index 687f62d166..83aa1db45d 100644 --- a/OpenRA.Mods.RA/Air/Aircraft.cs +++ b/OpenRA.Mods.RA/Air/Aircraft.cs @@ -198,10 +198,10 @@ namespace OpenRA.Mods.RA.Air return info.RearmBuildings.Contains(a.Info.Name) || info.RepairBuildings.Contains(a.Info.Name); } - - public int GetDesiredSubcell(CPos a, Actor ignoreActor) { return -1; } // does not use any subcell - public bool CanEnterCell(CPos location) { return true; } - public bool CanEnterCell(CPos cell, Actor ignoreActor, bool checkTransientActors) { return true; } + + public bool IsMovingFrom(CPos location, int subCell = -1) { return false; } // TODO: handle landing + public int GetAvailableSubcell(CPos a, int preferredSubCell = -1, Actor ignoreActor = null, bool checkTransientActors = true) { return -1; } // does not use any subcell + public bool CanEnterCell(CPos cell, Actor ignoreActor = null, bool checkTransientActors = true) { return true; } public int MovementSpeed { diff --git a/OpenRA.Mods.RA/Crate.cs b/OpenRA.Mods.RA/Crate.cs index 599b27c90a..4f41200196 100644 --- a/OpenRA.Mods.RA/Crate.cs +++ b/OpenRA.Mods.RA/Crate.cs @@ -95,27 +95,30 @@ namespace OpenRA.Mods.RA public void SetPosition(Actor self, WPos pos) { SetPosition(self, self.World.Map.CellContaining(pos)); } public void SetVisualPosition(Actor self, WPos pos) { SetPosition(self, self.World.Map.CellContaining(pos)); } - public bool CanEnterCell(CPos cell, Actor ignoreActor, bool checkTransientActors) + public bool IsMovingFrom(CPos location, int subCell = -1) { return false; } + public int GetAvailableSubcell(CPos cell, int preferredSubCell = -1, Actor ignoreActor = null, bool checkTransientActors = true) { - if (!self.World.Map.Contains(cell)) return false; + if (!self.World.Map.Contains(cell)) return -1; var type = self.World.Map.GetTerrainInfo(cell).Type; if (!info.TerrainTypes.Contains(type)) - return false; + return -1; if (self.World.WorldActor.Trait().GetBuildingAt(cell) != null) - return false; + return -1; if (!checkTransientActors) - return true; + return 0; return !self.World.ActorMap.GetUnitsAt(cell) .Where(x => x != ignoreActor) - .Any(); + .Any() ? 0 : -1; } - public int GetDesiredSubcell(CPos a, Actor ignoreActor) { return CanEnterCell(a, ignoreActor, true) ? 0 : -1; } - public bool CanEnterCell(CPos cell) { return CanEnterCell(cell, null, true); } + public bool CanEnterCell(CPos a, Actor ignoreActor = null, bool checkTransientActors = true) + { + return GetAvailableSubcell(a, -1, ignoreActor, checkTransientActors) >= 0; + } public void SetPosition(Actor self, CPos cell, int subCell = -1) { diff --git a/OpenRA.Mods.RA/Husk.cs b/OpenRA.Mods.RA/Husk.cs index d4d1c366a6..19d3fe37a1 100644 --- a/OpenRA.Mods.RA/Husk.cs +++ b/OpenRA.Mods.RA/Husk.cs @@ -53,24 +53,28 @@ namespace OpenRA.Mods.RA } public IEnumerable> OccupiedCells() { yield return Pair.New(TopLeft, 0); } - public bool CanEnterCell(CPos cell, Actor ignoreActor, bool checkTransientActors) + public bool IsMovingFrom(CPos location, int subCell = -1) { return false; } + public int GetAvailableSubcell(CPos cell, int preferredSubCell = -1, Actor ignoreActor = null, bool checkTransientActors = true) { if (!self.World.Map.Contains(cell)) - return false; + return -1; if (!info.AllowedTerrain.Contains(self.World.Map.GetTerrainInfo(cell).Type)) - return false; + return -1; if (!checkTransientActors) - return true; + return 0; return !self.World.ActorMap.GetUnitsAt(cell) .Where(x => x != ignoreActor) - .Any(); + .Any() ? 0 : -1; + } + + public bool CanEnterCell(CPos a, Actor ignoreActor = null, bool checkTransientActors = true) + { + return GetAvailableSubcell(a, -1, ignoreActor, checkTransientActors) >= 0; } - public int GetDesiredSubcell(CPos a, Actor ignoreActor) { return CanEnterCell(a, ignoreActor, true) ? 0 : -1; } - public bool CanEnterCell(CPos cell) { return CanEnterCell(cell, null, true); } public void SetPosition(Actor self, CPos cell, int subCell = -1) { SetPosition(self, self.World.Map.CenterOfCell(cell)); } public void SetVisualPosition(Actor self, WPos pos) diff --git a/OpenRA.Mods.RA/Move/Mobile.cs b/OpenRA.Mods.RA/Move/Mobile.cs index e0283487d0..6785875d8d 100755 --- a/OpenRA.Mods.RA/Move/Mobile.cs +++ b/OpenRA.Mods.RA/Move/Mobile.cs @@ -206,6 +206,45 @@ namespace OpenRA.Mods.RA.Move return true; } + public int GetAvailableSubCell(World world, Actor self, CPos cell, int preferredSubCell = -1, Actor ignoreActor = null, CellConditions check = CellConditions.All) + { + if (MovementCostForCell(world, cell) == int.MaxValue) + return -1; + + if (check.HasFlag(CellConditions.TransientActors)) + { + var canIgnoreMovingAllies = self != null && !check.HasFlag(CellConditions.BlockedByMovers); + var needsCellExclusively = self == null || Crushes == null; + + Func checkTransient = a => + { + if (a == ignoreActor) return false; + + // Neutral/enemy units are blockers. Allied units that are moving are not blockers. + if (canIgnoreMovingAllies && self.Owner.Stances[a.Owner] == Stance.Ally && IsMovingInMyDirection(self, a)) return false; + + // Non-sharable unit can enter a cell with shareable units only if it can crush all of them. + if (needsCellExclusively) return true; + if (!a.HasTrait()) return true; + foreach (var crushable in a.TraitsImplementing()) + if (!crushable.CrushableBy(Crushes, self.Owner)) + return true; + + return false; + }; + + if (!SharesCell) + return world.ActorMap.AnyUnitsAt(cell, 0, checkTransient)? -1 : 0; + + return world.ActorMap.FreeSubCell(cell, preferredSubCell, checkTransient); + } + + if (!SharesCell) + return world.ActorMap.AnyUnitsAt(cell, 0)? -1 : 0; + + return world.ActorMap.FreeSubCell(cell, preferredSubCell); + } + public int GetInitialFacing() { return InitialFacing; } } @@ -437,44 +476,18 @@ namespace OpenRA.Mods.RA.Move } } - bool IsDesiredSubcellNotBlocked(CPos a, int b, Actor ignoreActor) + public bool IsMovingFrom(CPos location, int subCell = -1) { - var blockingActors = self.World.ActorMap.GetUnitsAt(a, b).Where(c => c != ignoreActor); - if (blockingActors.Any()) - { - // Non-sharable unit can enter a cell with shareable units only if it can crush all of them - if (Info.Crushes == null) - return false; - - if (blockingActors.Any(c => !(c.HasTrait() && - c.TraitsImplementing().Any(d => d.CrushableBy(Info.Crushes, self.Owner))))) - return false; - } - return true; + return toCell != location && __fromCell == location + && (subCell == -1 || fromSubCell == subCell || subCell == 0 || fromSubCell == 0); } - public int GetDesiredSubcell(CPos a, Actor ignoreActor) + public int GetAvailableSubcell(CPos a, int preferredSubCell, Actor ignoreActor = null, bool checkTransientActors = true) { - if (!Info.SharesCell) - return 0; - - // Prioritise the current subcell - if (IsDesiredSubcellNotBlocked(a, fromSubCell, ignoreActor)) - return fromSubCell; - - for (var i = 1; i < self.World.Map.SubCellOffsets.Length; i++) - if (IsDesiredSubcellNotBlocked(a, i, ignoreActor)) - return i; - - return -1; + return Info.GetAvailableSubCell(self.World, self, a, preferredSubCell, ignoreActor, checkTransientActors? CellConditions.All : CellConditions.None); } - public bool CanEnterCell(CPos p) - { - return CanEnterCell(p, null, true); - } - - public bool CanEnterCell(CPos cell, Actor ignoreActor, bool checkTransientActors) + public bool CanEnterCell(CPos cell, Actor ignoreActor = null, bool checkTransientActors = true) { return Info.CanEnterCell(self.World, self, cell, ignoreActor, checkTransientActors ? CellConditions.All : CellConditions.BlockedByMovers); } diff --git a/OpenRA.Mods.RA/Move/Move.cs b/OpenRA.Mods.RA/Move/Move.cs index 1cd3c83c7c..245f620f24 100755 --- a/OpenRA.Mods.RA/Move/Move.cs +++ b/OpenRA.Mods.RA/Move/Move.cs @@ -245,7 +245,7 @@ namespace OpenRA.Mods.RA.Move hasWaited = false; path.RemoveAt(path.Count - 1); - var subCell = mobile.GetDesiredSubcell(nextCell, ignoreBuilding); + var subCell = mobile.GetAvailableSubcell(nextCell, -1, ignoreBuilding); return Pair.New(nextCell, subCell); }