Refactor footprint cell lookups and move them to Building

Removing FootprintUtils happens in the next commit for better
reviewability.
This commit is contained in:
reaperrr
2017-06-28 14:00:50 +02:00
committed by abcdefg30
parent 801796b184
commit 46dc827d46
7 changed files with 112 additions and 85 deletions

View File

@@ -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<string> emitError, Action<string> emitWarning, Ruleset rules)
{
foreach (var actorInfo in rules.Actors)
{
var building = actorInfo.Value.TraitInfoOrDefault<BuildingInfo>();
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);
}
}
}
}

View File

@@ -169,7 +169,6 @@
<Compile Include="HitShapes\Rectangle.cs" />
<Compile Include="HitShapes\Capsule.cs" />
<Compile Include="HitShapes\Circle.cs" />
<Compile Include="Lint\CheckBuildingFootprint.cs" />
<Compile Include="Lint\CheckAngle.cs" />
<Compile Include="Lint\CheckSequences.cs" />
<Compile Include="Lint\CheckPalettes.cs" />

View File

@@ -227,7 +227,7 @@ namespace OpenRA.Mods.Common.Orders
var res = world.WorldActor.Trait<ResourceLayer>();
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<Order> 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();

View File

@@ -23,6 +23,13 @@ namespace OpenRA.Mods.Common.Traits
public class GivesBuildableAreaInfo : TraitInfo<GivesBuildableArea> { }
public class GivesBuildableArea { }
public enum FootprintCellType
{
Empty = '_',
OccupiedPassable = '=',
Blocking = 'x'
}
public class BuildingInfo : ITraitInfo, IOccupySpaceInfo, IPlaceBuildingDecorationInfo, UsesInit<LocationInit>
{
[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<CVec, FootprintCellType> 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<CVec>("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<CVec, FootprintCellType>();
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<CPos> FootprintTiles(CPos location, FootprintCellType type)
{
return Footprint.Where(kv => kv.Value == type).Select(kv => location + kv.Key);
}
public IEnumerable<CPos> 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<CPos> 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<CPos> UnpathableTiles(CPos location)
{
foreach (var t in FootprintTiles(location, FootprintCellType.Blocking))
yield return t;
}
public IEnumerable<CPos> 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<MapBuildRadius>().AllyBuildRadiusEnabled;
foreach (var bp in world.ActorsWithTrait<BaseProvider>())
@@ -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<CPos, SubCell> 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<CPos, SubCell>(occupied);
@@ -178,13 +268,13 @@ namespace OpenRA.Mods.Common.Traits
topLeft = init.Get<LocationInit, CPos>();
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<SkipMakeAnimsInit>();
}
@@ -258,7 +348,7 @@ namespace OpenRA.Mods.Common.Traits
var smudgeLayers = self.World.WorldActor.TraitsImplementing<SmudgeLayer>();
foreach (var smudgeLayer in smudgeLayers)
foreach (var footprintTile in FootprintUtils.Tiles(self))
foreach (var footprintTile in Info.Tiles(self.Location))
smudgeLayer.RemoveSmudge(footprintTile);
}
}

View File

@@ -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;
};

View File

@@ -44,7 +44,7 @@ namespace OpenRA.Mods.Common.Traits
return true;
var res = world.WorldActor.Trait<ResourceLayer>();
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));
}

View File

@@ -17,62 +17,36 @@ namespace OpenRA.Mods.Common.Traits
{
public static class FootprintUtils
{
public static IEnumerable<CPos> 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<CPos> Tiles(Actor a)
{
return Tiles(a.World.Map.Rules, a.Info.Name, a.Info.TraitInfo<BuildingInfo>(), a.Location);
var info = a.Info.TraitInfo<BuildingInfo>();
return info.Tiles(a.Location);
}
public static IEnumerable<CPos> FrozenUnderFogTiles(Actor a)
{
return Tiles(a.World.Map.Rules, a.Info.Name, a.Info.TraitInfo<BuildingInfo>(), a.Location, true);
var info = a.Info.TraitInfo<BuildingInfo>();
return info.FrozenUnderFogTiles(a);
}
public static IEnumerable<CPos> 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<CPos> 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<CVec> TilesWhere(string name, CVec dim, char[] footprint, Func<char, bool> 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);
}
}
}