diff --git a/OpenRA.Mods.Common/Lint/CheckBuildingFootprint.cs b/OpenRA.Mods.Common/Lint/CheckBuildingFootprint.cs deleted file mode 100644 index 14938f458c..0000000000 --- a/OpenRA.Mods.Common/Lint/CheckBuildingFootprint.cs +++ /dev/null @@ -1,36 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2017 The OpenRA Developers (see AUTHORS) - * This file is part of OpenRA, which is free software. It is made - * available to you under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. For more - * information, see COPYING. - */ -#endregion - -using System; -using System.Linq; -using OpenRA.Mods.Common.Traits; -using OpenRA.Traits; - -namespace OpenRA.Mods.Common.Lint -{ - class CheckBuildingFootprint : ILintRulesPass - { - public void Run(Action emitError, Action emitWarning, Ruleset rules) - { - foreach (var actorInfo in rules.Actors) - { - var building = actorInfo.Value.TraitInfoOrDefault(); - if (building == null) - continue; - - var footprint = building.Footprint.Where(x => !char.IsWhiteSpace(x)).ToArray(); - var dimension = building.Dimensions; - if (footprint.Length != dimension.X * dimension.Y) - emitError("Invalid building footprint/dimension for " + actorInfo.Key); - } - } - } -} diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index c1a3c5fc87..19dc3391c4 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -169,7 +169,6 @@ - diff --git a/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs b/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs index 03ac1082ff..23a273457a 100644 --- a/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs +++ b/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs @@ -227,7 +227,7 @@ namespace OpenRA.Mods.Common.Orders var res = world.WorldActor.Trait(); var isCloseEnough = buildingInfo.IsCloseEnoughToBase(world, world.LocalPlayer, building, topLeft); - foreach (var t in FootprintUtils.Tiles(rules, building, buildingInfo, topLeft)) + foreach (var t in buildingInfo.Tiles(topLeft)) cells.Add(t, MakeCellType(isCloseEnough && world.IsCellBuildable(t, buildingInfo) && res.GetResource(t) == null)); } @@ -248,7 +248,7 @@ namespace OpenRA.Mods.Common.Orders IEnumerable ClearBlockersOrders(World world, CPos topLeft) { - var allTiles = FootprintUtils.Tiles(world.Map.Rules, building, buildingInfo, topLeft).ToArray(); + var allTiles = buildingInfo.Tiles(topLeft).ToArray(); var neightborTiles = Util.ExpandFootprint(allTiles, true).Except(allTiles) .Where(world.Map.Contains).ToList(); diff --git a/OpenRA.Mods.Common/Traits/Buildings/Building.cs b/OpenRA.Mods.Common/Traits/Buildings/Building.cs index 89b2bbc28a..939852514b 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/Building.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/Building.cs @@ -23,6 +23,13 @@ namespace OpenRA.Mods.Common.Traits public class GivesBuildableAreaInfo : TraitInfo { } public class GivesBuildableArea { } + public enum FootprintCellType + { + Empty = '_', + OccupiedPassable = '=', + Blocking = 'x' + } + public class BuildingInfo : ITraitInfo, IOccupySpaceInfo, IPlaceBuildingDecorationInfo, UsesInit { [Desc("Where you are allowed to place the building (Water, Clear, ...)")] @@ -31,8 +38,10 @@ namespace OpenRA.Mods.Common.Traits [Desc("The range to the next building it can be constructed. Set it higher for walls.")] public readonly int Adjacent = 2; - [Desc("x means space it blocks, _ is a part that is passable by actors.")] - public readonly string Footprint = "x"; + [Desc("x means cell is blocked, = means part of the footprint but passable, ", + "_ means completely empty.")] + [FieldLoader.LoadUsing("LoadFootprint")] + public readonly Dictionary Footprint; public readonly CVec Dimensions = new CVec(1, 1); @@ -58,9 +67,90 @@ namespace OpenRA.Mods.Common.Traits public virtual object Create(ActorInitializer init) { return new Building(init, this); } + protected static object LoadFootprint(MiniYaml yaml) + { + var footprintYaml = yaml.Nodes.FirstOrDefault(n => n.Key == "Footprint"); + var footprintChars = footprintYaml != null ? footprintYaml.Value.Value.Where(x => !char.IsWhiteSpace(x)).ToArray() : new[] { 'x' }; + + var dimensionsYaml = yaml.Nodes.FirstOrDefault(n => n.Key == "Dimensions"); + var dim = dimensionsYaml != null ? FieldLoader.GetValue("Dimensions", dimensionsYaml.Value.Value) : new CVec(1, 1); + + if (footprintChars.Length != dim.X * dim.Y) + { + var fp = footprintYaml.Value.Value.ToString(); + var dims = dim.X + "x" + dim.Y; + throw new YamlException("Invalid footprint: {0} does not match dimensions {1}".F(fp, dims)); + } + + var index = 0; + var ret = new Dictionary(); + for (var y = 0; y < dim.Y; y++) + { + for (var x = 0; x < dim.X; x++) + { + var c = footprintChars[index++]; + if (!Enum.IsDefined(typeof(FootprintCellType), (FootprintCellType)c)) + throw new YamlException("Invalid footprint cell type '{0}'".F(c)); + + ret[new CVec(x, y)] = (FootprintCellType)c; + } + } + + return ret; + } + + public IEnumerable FootprintTiles(CPos location, FootprintCellType type) + { + return Footprint.Where(kv => kv.Value == type).Select(kv => location + kv.Key); + } + + public IEnumerable Tiles(CPos location) + { + foreach (var t in FootprintTiles(location, FootprintCellType.OccupiedPassable)) + yield return t; + + foreach (var t in FootprintTiles(location, FootprintCellType.Blocking)) + yield return t; + } + + public IEnumerable FrozenUnderFogTiles(CPos location) + { + foreach (var t in FootprintTiles(location, FootprintCellType.Empty)) + yield return t; + + foreach (var t in Tiles(location)) + yield return t; + } + + public IEnumerable UnpathableTiles(CPos location) + { + foreach (var t in FootprintTiles(location, FootprintCellType.Blocking)) + yield return t; + } + + public IEnumerable PathableTiles(CPos location) + { + foreach (var t in FootprintTiles(location, FootprintCellType.Empty)) + yield return t; + + foreach (var t in FootprintTiles(location, FootprintCellType.OccupiedPassable)) + yield return t; + } + + public CVec LocationOffset() + { + return new CVec(Dimensions.X / 2, Dimensions.Y > 1 ? (Dimensions.Y + 1) / 2 : 0); + } + + public WVec CenterOffset(World w) + { + var off = (w.Map.CenterOfCell(new CPos(Dimensions.X, Dimensions.Y)) - w.Map.CenterOfCell(new CPos(1, 1))) / 2; + return (off - new WVec(0, 0, off.Z)) + LocalCenterOffset; + } + public Actor FindBaseProvider(World world, Player p, CPos topLeft) { - var center = world.Map.CenterOfCell(topLeft) + FootprintUtils.CenterOffset(world, this); + var center = world.Map.CenterOfCell(topLeft) + CenterOffset(world); var allyBuildEnabled = world.WorldActor.Trait().AllyBuildRadiusEnabled; foreach (var bp in world.ActorsWithTrait()) @@ -118,7 +208,7 @@ namespace OpenRA.Mods.Common.Traits } } - var buildingTiles = FootprintUtils.Tiles(world.Map.Rules, buildingName, this, topLeft).ToList(); + var buildingTiles = Tiles(topLeft).ToList(); return nearnessCandidates .Any(a => buildingTiles .Any(b => Math.Abs(a.X - b.X) <= Adjacent @@ -127,7 +217,7 @@ namespace OpenRA.Mods.Common.Traits public IReadOnlyDictionary OccupiedCells(ActorInfo info, CPos topLeft, SubCell subCell = SubCell.Any) { - var occupied = FootprintUtils.UnpathableTiles(info.Name, this, topLeft) + var occupied = UnpathableTiles(topLeft) .ToDictionary(c => c, c => SubCell.FullCell); return new ReadOnlyDictionary(occupied); @@ -178,13 +268,13 @@ namespace OpenRA.Mods.Common.Traits topLeft = init.Get(); Info = info; - occupiedCells = FootprintUtils.UnpathableTiles(self.Info.Name, Info, TopLeft) + occupiedCells = Info.UnpathableTiles(TopLeft) .Select(c => Pair.New(c, SubCell.FullCell)).ToArray(); - targetableCells = FootprintUtils.UnpathableTiles(self.Info.Name, Info, TopLeft) + targetableCells = Info.FootprintTiles(TopLeft, FootprintCellType.Blocking) .Select(c => Pair.New(c, SubCell.FullCell)).ToArray(); - CenterPosition = init.World.Map.CenterOfCell(topLeft) + FootprintUtils.CenterOffset(init.World, Info); + CenterPosition = init.World.Map.CenterOfCell(topLeft) + Info.CenterOffset(init.World); SkipMakeAnimation = init.Contains(); } @@ -258,7 +348,7 @@ namespace OpenRA.Mods.Common.Traits var smudgeLayers = self.World.WorldActor.TraitsImplementing(); foreach (var smudgeLayer in smudgeLayers) - foreach (var footprintTile in FootprintUtils.Tiles(self)) + foreach (var footprintTile in Info.Tiles(self.Location)) smudgeLayer.RemoveSmudge(footprintTile); } } diff --git a/OpenRA.Mods.Common/Traits/Buildings/BuildingInfluence.cs b/OpenRA.Mods.Common/Traits/Buildings/BuildingInfluence.cs index d69a8c7aad..f9e90a2991 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/BuildingInfluence.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/BuildingInfluence.cs @@ -36,7 +36,7 @@ namespace OpenRA.Mods.Common.Traits if (b == null) return; - foreach (var u in FootprintUtils.Tiles(map.Rules, a.Info.Name, b, a.Location)) + foreach (var u in b.Tiles(a.Location)) if (influence.Contains(u) && influence[u] == null) influence[u] = a; }; @@ -47,7 +47,7 @@ namespace OpenRA.Mods.Common.Traits if (b == null) return; - foreach (var u in FootprintUtils.Tiles(map.Rules, a.Info.Name, b, a.Location)) + foreach (var u in b.Tiles(a.Location)) if (influence.Contains(u) && influence[u] == a) influence[u] = null; }; diff --git a/OpenRA.Mods.Common/Traits/Buildings/BuildingUtils.cs b/OpenRA.Mods.Common/Traits/Buildings/BuildingUtils.cs index 8bae043e68..35c604f6dc 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/BuildingUtils.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/BuildingUtils.cs @@ -44,7 +44,7 @@ namespace OpenRA.Mods.Common.Traits return true; var res = world.WorldActor.Trait(); - return FootprintUtils.Tiles(world.Map.Rules, name, building, topLeft).All( + return building.Tiles(topLeft).All( t => world.Map.Contains(t) && res.GetResource(t) == null && world.IsCellBuildable(t, building, toIgnore)); } diff --git a/OpenRA.Mods.Common/Traits/Buildings/FootprintUtils.cs b/OpenRA.Mods.Common/Traits/Buildings/FootprintUtils.cs index a8c2237b1f..fdd3eb8f60 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/FootprintUtils.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/FootprintUtils.cs @@ -17,62 +17,36 @@ namespace OpenRA.Mods.Common.Traits { public static class FootprintUtils { - public static IEnumerable Tiles(Ruleset rules, string name, BuildingInfo buildingInfo, CPos topLeft, bool includePassable = false) - { - var dim = buildingInfo.Dimensions; - var footprint = buildingInfo.Footprint.Where(x => !char.IsWhiteSpace(x)); - - return TilesWhere(name, dim, footprint.ToArray(), a => includePassable || a != '_').Select(t => t + topLeft); - } - public static IEnumerable Tiles(Actor a) { - return Tiles(a.World.Map.Rules, a.Info.Name, a.Info.TraitInfo(), a.Location); + var info = a.Info.TraitInfo(); + return info.Tiles(a.Location); } public static IEnumerable FrozenUnderFogTiles(Actor a) { - return Tiles(a.World.Map.Rules, a.Info.Name, a.Info.TraitInfo(), a.Location, true); + var info = a.Info.TraitInfo(); + return info.FrozenUnderFogTiles(a); } public static IEnumerable UnpathableTiles(string name, BuildingInfo buildingInfo, CPos position) { - var footprint = buildingInfo.Footprint.Where(x => !char.IsWhiteSpace(x)).ToArray(); - foreach (var tile in TilesWhere(name, buildingInfo.Dimensions, footprint, a => a == 'x')) - yield return tile + position; + return buildingInfo.UnpathableTiles(position); } public static IEnumerable PathableTiles(string name, BuildingInfo buildingInfo, CPos position) { - var footprint = buildingInfo.Footprint.Where(x => !char.IsWhiteSpace(x)).ToArray(); - foreach (var tile in TilesWhere(name, buildingInfo.Dimensions, footprint, a => a == '_')) - yield return tile + position; - } - - static IEnumerable TilesWhere(string name, CVec dim, char[] footprint, Func cond) - { - if (footprint.Length != dim.X * dim.Y) - throw new InvalidOperationException("Invalid footprint for " + name); - var index = 0; - - for (var y = 0; y < dim.Y; y++) - for (var x = 0; x < dim.X; x++) - if (cond(footprint[index++])) - yield return new CVec(x, y); + return buildingInfo.PathableTiles(position); } public static CVec AdjustForBuildingSize(BuildingInfo buildingInfo) { - var dim = buildingInfo.Dimensions; - return new CVec(dim.X / 2, dim.Y > 1 ? (dim.Y + 1) / 2 : 0); + return buildingInfo.AdjustForBuildingSize(); } public static WVec CenterOffset(World w, BuildingInfo buildingInfo) { - var dim = buildingInfo.Dimensions; - var localOffset = buildingInfo.LocalCenterOffset; - var off = (w.Map.CenterOfCell(new CPos(dim.X, dim.Y)) - w.Map.CenterOfCell(new CPos(1, 1))) / 2; - return (off - new WVec(0, 0, off.Z)) + localOffset; + return buildingInfo.CenterOffset(w); } } }