diff --git a/OpenRA.Game/Manifest.cs b/OpenRA.Game/Manifest.cs index 1d2ad01433..a1ae7d4612 100644 --- a/OpenRA.Game/Manifest.cs +++ b/OpenRA.Game/Manifest.cs @@ -37,6 +37,20 @@ namespace OpenRA public readonly Size TileSize = new Size(24, 24); public readonly TileShape TileShape = TileShape.Rectangle; + [Desc("(x,y,z) offset of the full cell and each sub-cell", "x & y: -512 ... 512, Z >= 0")] + public readonly WVec[] SubCellOffsets = + { + new WVec(0, 0, 0), // full cell - index 0 + new WVec(-299, -256, 0), // top left - index 1 + new WVec(256, -256, 0), // top right - index 2 + new WVec(0, 0, 0), // center - index 3 + new WVec(-299, 256, 0), // bottom left - index 4 + new WVec(256, 256, 0), // bottom right - index 5 + }; + + [Desc("Default subcell index used if SubCellInit is absent", "0 - full cell, 1 - first sub-cell")] + public readonly int SubCellDefaultIndex = 3; + public Manifest(string mod) { var path = new[] { "mods", mod, "mod.yaml" }.Aggregate(Path.Combine); @@ -87,6 +101,35 @@ namespace OpenRA if (yaml.ContainsKey("TileShape")) TileShape = FieldLoader.GetValue("TileShape", yaml["TileShape"].Value); + // Read subcell information + // sub-cell index 0 is the full cell + if (yaml.ContainsKey("SubCells")) + { + var subcells = yaml["SubCells"].ToDictionary(); + + // Read (x,y,z) offset (relative to cell center) pairs for positioning subcells + if (subcells.ContainsKey("Offsets")) + { + SubCellOffsets = FieldLoader.GetValue("Offsets", subcells["Offsets"].Value); + + foreach (var i in SubCellOffsets) + if (i.X < -512 || i.X > 512 || i.Y < -512 || i.Y > 512 || i.Z < 0) + throw new InvalidDataException("Subcell offsets must be in bounds (X & Y: -512 ... 512, Z > 0)"); + } + + // Read default subcell index used when creating actors that share cells without SubCellInit + if (subcells.ContainsKey("DefaultIndex")) + SubCellDefaultIndex = FieldLoader.GetValue("DefaultIndex", subcells ["DefaultIndex"].Value); + + // Otherwise set the default subcell index to the middle subcell entry + else + SubCellDefaultIndex = SubCellOffsets.Length / 2; // default is the middle subcell entry + } + + // validate default index - 0 for no subcells, otherwise > 1 & <= subcell count (offset triples count - 1) + if (SubCellDefaultIndex < (SubCellOffsets.Length > 1 ? 1 : 0) || SubCellDefaultIndex >= SubCellOffsets.Length) + throw new InvalidDataException("Subcell default index must be a valid index into the offset triples and must be greater than 0 for mods with subcells"); + // Allow inherited mods to import parent maps. var compat = new List(); compat.Add(mod); diff --git a/OpenRA.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index 39cba9d30f..2731ba7b4c 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -78,16 +78,8 @@ namespace OpenRA public readonly TileShape TileShape; [FieldLoader.Ignore] - public readonly WVec[] SubCellOffsets = - { - new WVec(0, 0, 0), - new WVec(-299, -256, 0), - new WVec(256, -256, 0), - new WVec(0, 0, 0), - new WVec(-299, 256, 0), - new WVec(256, 256, 0), - }; - public readonly int SubCellsDefaultIndex = 3; + public readonly WVec[] SubCellOffsets; + public readonly int SubCellDefaultIndex; [FieldLoader.LoadUsing("LoadOptions")] public MapOptions Options; @@ -257,6 +249,8 @@ namespace OpenRA MapTiles = Exts.Lazy(() => LoadMapTiles()); MapResources = Exts.Lazy(() => LoadResourceTiles()); TileShape = Game.modData.Manifest.TileShape; + SubCellOffsets = Game.modData.Manifest.SubCellOffsets; + SubCellDefaultIndex = Game.modData.Manifest.SubCellDefaultIndex; // The Uid is calculated from the data on-disk, so // format changes must be flushed to disk. diff --git a/OpenRA.Game/Traits/World/ActorMap.cs b/OpenRA.Game/Traits/World/ActorMap.cs index e9f73b95ce..3c028af924 100644 --- a/OpenRA.Game/Traits/World/ActorMap.cs +++ b/OpenRA.Game/Traits/World/ActorMap.cs @@ -33,8 +33,6 @@ namespace OpenRA.Traits public Actor Actor; } - static readonly int[] SubCells = { 1, 2, 3, 4, 5 }; - readonly ActorMapInfo info; readonly Map map; readonly CellLayer influence; @@ -87,18 +85,18 @@ namespace OpenRA.Traits public bool HasFreeSubCell(CPos a) { - if (!AnyUnitsAt(a)) - return true; - - return SubCells.Any(b => !AnyUnitsAt(a, b)); + return FreeSubCell(a) >= 0; } - public int? FreeSubCell(CPos a) + public int FreeSubCell(CPos a) { - if (!HasFreeSubCell(a)) - return null; + if (!AnyUnitsAt(a)) + return map.SubCellDefaultIndex; - return SubCells.First(b => !AnyUnitsAt(a, b)); + for (var i = 1; i < map.SubCellOffsets.Length; ++i) + if (!AnyUnitsAt(a, i)) + return i; + return -1; } public bool AnyUnitsAt(CPos a) diff --git a/OpenRA.Mods.RA/Move/Mobile.cs b/OpenRA.Mods.RA/Move/Mobile.cs index 9c1992ca12..f4419079bc 100755 --- a/OpenRA.Mods.RA/Move/Mobile.cs +++ b/OpenRA.Mods.RA/Move/Mobile.cs @@ -238,7 +238,7 @@ namespace OpenRA.Mods.RA.Move this.self = init.self; this.Info = info; - toSubCell = fromSubCell = info.SharesCell ? init.world.Map.SubCellsDefaultIndex : 0; + toSubCell = fromSubCell = info.SharesCell ? init.world.Map.SubCellDefaultIndex : 0; if (init.Contains()) { this.fromSubCell = this.toSubCell = init.Get(); @@ -417,27 +417,36 @@ namespace OpenRA.Mods.RA.Move } } + bool IsDesiredSubcellNotBlocked(CPos a, int b, Actor ignoreActor) + { + 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; + } + public int GetDesiredSubcell(CPos a, Actor ignoreActor) { if (!Info.SharesCell) return 0; // Prioritise the current subcell - return new[]{ fromSubCell, 1, 2, 3, 4, 5}.First(b => - { - 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 (IsDesiredSubcellNotBlocked(a, fromSubCell, ignoreActor)) + return fromSubCell; - if (blockingActors.Any(c => !(c.HasTrait() && - c.TraitsImplementing().Any(d => d.CrushableBy(Info.Crushes, self.Owner))))) - return false; - } - return true; - }); + for (var i = 1; i < self.World.Map.SubCellOffsets.Length; ++i) + if (IsDesiredSubcellNotBlocked(a, i, ignoreActor)) + return i; + + return -1; } public bool CanEnterCell(CPos p) diff --git a/OpenRA.Mods.RA/SpawnMPUnits.cs b/OpenRA.Mods.RA/SpawnMPUnits.cs index a0df13ee48..58b5a2b46e 100644 --- a/OpenRA.Mods.RA/SpawnMPUnits.cs +++ b/OpenRA.Mods.RA/SpawnMPUnits.cs @@ -61,7 +61,7 @@ namespace OpenRA.Mods.RA throw new InvalidOperationException("No cells available to spawn starting unit {0}".F(s)); var cell = validCells.Random(w.SharedRandom); - var subCell = mi.SharesCell ? w.ActorMap.FreeSubCell(cell).Value : 0; + var subCell = mi.SharesCell ? w.ActorMap.FreeSubCell(cell) : 0; w.CreateActor(s.ToLowerInvariant(), new TypeDictionary { diff --git a/mods/cnc/mod.yaml b/mods/cnc/mod.yaml index 13479e27d0..6b6555c6c9 100644 --- a/mods/cnc/mod.yaml +++ b/mods/cnc/mod.yaml @@ -126,6 +126,10 @@ TileSets: mods/cnc/tilesets/temperat.yaml mods/cnc/tilesets/jungle.yaml +SubCells: + Offsets: 0,0,0, -299,-256,0, 256,-256,0, 0,0,0, -299,256,0, 256,256,0 + DefaultIndex: 3 + LoadScreen: CncLoadScreen Image: mods/cnc/uibits/chrome.png Text: Loading diff --git a/mods/d2k/mod.yaml b/mods/d2k/mod.yaml index e27ad056d8..b6c9c82605 100644 --- a/mods/d2k/mod.yaml +++ b/mods/d2k/mod.yaml @@ -100,6 +100,10 @@ Notifications: TileSets: mods/d2k/tilesets/arrakis.yaml +SubCells: + Offsets: 0,0,0, -299,-256,0, 256,-256,0, 0,0,0, -299,256,0, 256,256,0 + DefaultIndex: 3 + TileSize: 32,32 Music: diff --git a/mods/ra/mod.yaml b/mods/ra/mod.yaml index 9043bd1e6e..221a7c0e0c 100644 --- a/mods/ra/mod.yaml +++ b/mods/ra/mod.yaml @@ -117,6 +117,10 @@ TileSets: mods/ra/tilesets/temperat.yaml mods/ra/tilesets/desert.yaml +SubCells: + Offsets: 0,0,0, -299,-256,0, 256,-256,0, 0,0,0, -299,256,0, 256,256,0 + DefaultIndex: 3 + Music: mods/ra/music.yaml diff --git a/mods/ts/mod.yaml b/mods/ts/mod.yaml index 64548b7bac..bd7ce9714d 100644 --- a/mods/ts/mod.yaml +++ b/mods/ts/mod.yaml @@ -145,6 +145,9 @@ TileSets: TileSize: 48,24 TileShape: Diamond +SubCells: + Offsets: 0,0,0, -256,128,0, 0,-128,0, 256,128,0 + DefaultIndex: 2 Music: mods/ts/music.yaml