/// <summary> /// Determines which areas are neighbors of other areas. /// </summary> /// <param name="areas">A map representation where each contiguous area has the same number (i.e. the output of <see cref="GetAreaGrid"/>).</param> /// <returns>A distinct set of neighbor relationships as byte-tuples (with the lower-value id coming first in each tuple).</returns> private static HashSet <(byte, byte)> GetNeighbors(MapArray <byte> areas) { /* Neighbors are areas with bordering spaces. I'm pretty sure we don't * have to worry about diagonals because I don't think they come up specifically, * and I don't think you can move between two diagonal neighbor spaces if there * are no other open spaces around. * * As such, this just goes from left-to-right in each row and bottom-to-top * in each column and whenever the adjacent numbers are different and non-zero, * that's a neighboring relationship. */ var neighbors = new HashSet <(byte, byte)>(); // TODO: Get rid of code duplication for (var x = 0; x < areas.Size.X; x++) { for (var y = 1; y < areas.Size.Y; y++) { if (areas[x, y - 1] != 0 && areas[x, y] != 0 && areas[x, y - 1] != areas[x, y]) { var first = areas[x, y - 1]; var second = areas[x, y]; // Consistently put the lesser number first to avoid duplication if (first < second) { neighbors.Add((first, second)); } else { neighbors.Add((second, first)); } } } } for (var y = 0; y < areas.Size.Y; y++) { for (var x = 1; x < areas.Size.X; x++) { if (areas[x - 1, y] != 0 && areas[x, y] != 0 && areas[x - 1, y] != areas[x, y]) { var first = areas[x - 1, y]; var second = areas[x, y]; // Consistently put the lesser number first to avoid duplication if (first < second) { neighbors.Add((first, second)); } else { neighbors.Add((second, first)); } } } } return(neighbors); }
public Map(StartRaw startingData) { this.Raw = startingData; this.Size = startingData.MapSize; placementGridOriginal = new MapArray <byte>(startingData.PlacementGrid.Data.ToByteArray(), this.Size); placementGrid = new MapArray <byte>(placementGridOriginal); pathingGrid = new MapArray <byte>(startingData.PathingGrid.Data.ToByteArray(), this.Size); heightGrid = new MapArray <byte>(startingData.TerrainHeight.Data.ToByteArray(), this.Size); creepGrid = new MapArray <byte>(startingData.TerrainHeight.Data.ToByteArray(), this.Size); this.structuresAndDeposits = new MapArray <Unit>(this.Size); this.structuresAndDepositsList = new List <Unit>(); GeneratePadding(startingData); this.structurePadding = new MapArray <bool>(this.Size); this.resourcePadding = new MapArray <bool>(this.Size); // Not gonna work for anything but 1-on-1 games, but it seems like // it doesn't give this player's location, just the enemy's. var enemyStart = startingData.StartLocations.Single(); this.enemyStartLocation = new Location { X = (int)enemyStart.X, Y = (int)enemyStart.Y }; }
private void GeneratePadding(StartRaw startingData) { this.padding = new MapArray <bool>(this.Size); for (var x = 0; x < Size.X; x++) { for (var y = 0; y < Size.Y; y++) { if (placementGrid[x, y] == 0) { SetAdjacentSpaces(this.padding, x, y); } } } }
public MapData(StartRaw startingData) { this.Raw = startingData; this.Size = startingData.MapSize; pathingGrid = new MapArray <byte>(startingData.PathingGrid.Data.ToByteArray(), this.Size); placementGrid = new MapArray <byte>(startingData.PlacementGrid.Data.ToByteArray(), this.Size); heightGrid = new MapArray <byte>(startingData.TerrainHeight.Data.ToByteArray(), this.Size); this.structuresAndDeposits = new MapArray <Unit>(this.Size); GeneratePadding(startingData); this.areas = GetAreas(); this.deposits = new List <Deposit>(); this.structurePadding = new MapArray <bool>(this.Size); }
private void SetAdjacentSpaces(MapArray <bool> targetArray, int x, int y, int size = 1) { var xVals = new List <int> { x - size, x, x + size }; xVals.RemoveAll(n => n < 0 || n >= Size.X); var yVals = new List <int> { y - size, y, y + size }; yVals.RemoveAll(n => n < 0 || n >= Size.Y); foreach (var xVal in xVals) { foreach (var yVal in yVals) { targetArray[xVal, yVal] = true; } } }
public MapData(MapData prior, List <Unit> units, Translator translator, Dictionary <uint, UnitTypeData> unitTypes) { this.Raw = prior.Raw; this.Size = prior.Size; this.placementGrid = prior.placementGrid; this.pathingGrid = prior.pathingGrid; this.heightGrid = prior.heightGrid; this.padding = prior.padding; this.areaGrid = prior.areaGrid; this.areas = prior.areas; this.deposits = GetDeposits(units); this.structuresAndDeposits = new MapArray <Unit>(this.Size); this.structurePadding = new MapArray <bool>(this.Size); foreach (var unit in units) { var unitType = unitTypes[unit.Raw.UnitType]; if (unitType.Attributes.Contains(Proto.Attribute.Structure)) { var structureSize = translator.GetStructureSize(unit); var originX = (int)Math.Round(unit.X - structureSize.X * 0.5f); var originY = (int)Math.Round(unit.Y - structureSize.Y * 0.5f); for (var x = originX; x < originX + structureSize.X; x++) { for (var y = originY; y < originY + structureSize.Y; y++) { structuresAndDeposits[x, y] = unit; SetAdjacentSpaces(structurePadding, x, y); } } } } }
private void SetAdjacentSpaces(MapArray <bool> targetArray, int x, int y) { var xVals = new List <int> { x - 1, x, x + 1 }; xVals.Remove(-1); xVals.Remove(Size.X); var yVals = new List <int> { y - 1, y, y + 1 }; yVals.Remove(-1); yVals.Remove(Size.Y); foreach (var xVal in xVals) { foreach (var yVal in yVals) { targetArray[xVal, yVal] = true; } } }
public MapArray(MapArray <T> other) { this.size = new Size2DI(other.size); this.data = (T[])other.data.Clone(); }
private void AddAdjacentLocations(Location location, HashSet <Location> locations, MapArray <byte> areas) { foreach (var adjacentLocation in AdjacentLocations(location)) { if (areas[adjacentLocation] == 0 && (this.CanBuild(adjacentLocation) || this.CanTraverse(adjacentLocation))) { locations.Add(adjacentLocation); } } }
/// <summary> /// Builds a representation of the map where each distinct area has a different value. /// </summary> /// <param name="mapData">The representation of the map. (Only the size, pathing grid, and placement grid are used.)</param> /// <returns>A represntation of the map where all spaces that are the same 'area' have the same numeric value. /// Locations that are not accessible to ground units have a value of 0.</returns> private MapArray <byte> GetAreaGrid() { /* Pick a starting location and fan out to adjacent locations. * * If it's a buildable zone, find all spots that are also buildable, * adjacent, and at the same height. This will be a 'mesa'. * * If it's not buildable but it can be traversed then it's a ramp, * find all adjacent spots that can also be traversed and are not buildable. * * We can probably use the starting locations on the map as safe places to begin, * since they'll be base locations. */ // TODO: Add support for "islands" - this mechanism will only capture areas connected to the starting location // Resulting array of areas MapArray <byte> areas = new MapArray <byte>(this.Size); // Area id - will be incremented as we move to other areas // (and before assigning the first area as well) byte currentId = 0; var locations = new HashSet <Location>(); var otherAreaLocations = new HashSet <Location>(); var startPosition = this.Raw.StartLocations[0]; otherAreaLocations.Add(new Location { X = (int)startPosition.X, Y = (int)startPosition.Y }); while (otherAreaLocations.Count > 0) { var lastLocation = otherAreaLocations.First(); otherAreaLocations.Remove(lastLocation); currentId++; areas[lastLocation] = currentId; AddAdjacentLocations(lastLocation, locations, areas); while (locations.Count > 0) { var location = locations.First(); locations.Remove(location); otherAreaLocations.Remove(location); if (CanBuild(lastLocation) == CanBuild(location)) { areas[location] = currentId; AddAdjacentLocations(location, locations, areas); } else { otherAreaLocations.Add(location); } } } return(areas); }
/// <summary> /// Builds a list of <see cref="Area"/>s that have references to their neighbors. /// </summary> public List <Area> GetAreas() // TODO: Break up this function { this.areaGrid = GetAreaGrid(); var neighbors = GetNeighbors(areaGrid); var mesas = new Dictionary <byte, Mesa>(); var ramps = new Dictionary <byte, Ramp>(); var edges = new Dictionary <byte, Edge>(); var maxAreaId = areaGrid.Data.Max(); // Need to get the center of each area. To avoid translation confusion, // I'm just going to create arrays with an extra space even though there's // no relevant area 0 (which instead represents impassible terrain). var centers = new Location[maxAreaId + 1]; var locationLists = new List <Location> [maxAreaId + 1]; var xTotals = new int[maxAreaId + 1]; var yTotals = new int[maxAreaId + 1]; var counts = new int[maxAreaId + 1]; for (var x = 0; x < this.Size.X; x++) { for (var y = 0; y < this.Size.Y; y++) { var areaId = areaGrid[x, y]; if (areaId != 0) { xTotals[areaId] += x; yTotals[areaId] += y; counts[areaId] += 1; locationLists[areaId] = locationLists[areaId] ?? new List <Location>(); locationLists[areaId].Add(new Location { X = x, Y = y }); } } } // Build mesas first - ramps need mesa info in their constructor, // and for convenience mesas can add neighbors post-construction for (byte areaId = 1; areaId <= maxAreaId; areaId++) { // This part can really be done once for both ramps and mesas var center = new Location { X = xTotals[areaId] / counts[areaId], Y = yTotals[areaId] / counts[areaId] }; if (locationLists[areaId].Contains(center)) { centers[areaId] = center; } else { centers[areaId] = center.GetClosest(locationLists[areaId]); } if (CanBuild(locationLists[areaId][0])) { var height = this.HeightGrid[locationLists[areaId][0]]; mesas[areaId] = new Mesa(areaId, locationLists[areaId], centers[areaId], height); } } for (byte areaId = 1; areaId <= maxAreaId; areaId++) { if (!CanBuild(locationLists[areaId][0])) { var topAndBottomNeighborIds = neighbors .Where(pair => pair.Item1 == areaId || pair.Item2 == areaId) .Select(pair => pair.Item1 == areaId ? pair.Item2 : pair.Item1).ToList(); if (topAndBottomNeighborIds.Count != 2) { // This isn't really a ramp. Using the 'Edge' class for miscellaneous non-buildable // areas for now. This does assume that an Edge won't connect to a Ramp. var neighborMesas = topAndBottomNeighborIds.Select(id => mesas[id]).ToArray(); edges[areaId] = new Edge(areaId, locationLists[areaId], centers[areaId], neighborMesas); foreach (var neighborMesa in neighborMesas) { neighborMesa.AddNeighbor(edges[areaId]); } } else { var topAndBottomNeighbors = topAndBottomNeighborIds.Select(id => mesas[id]).OrderBy(mesa => mesa.Height).ToArray(); ramps[areaId] = new Ramp(areaId, locationLists[areaId], centers[areaId], topAndBottomNeighbors[1], topAndBottomNeighbors[0]); mesas[topAndBottomNeighborIds[0]].AddNeighbor(ramps[areaId]); mesas[topAndBottomNeighborIds[1]].AddNeighbor(ramps[areaId]); } } } // At some point there might be 'mesas' that are adjacent, because we want to // subdivide large areas with multiple mining locations even if there's no ramp. foreach (var neighbor in neighbors) { if (mesas.ContainsKey(neighbor.Item1) && mesas.ContainsKey(neighbor.Item2)) { mesas[neighbor.Item1].AddNeighbor(mesas[neighbor.Item2]); mesas[neighbor.Item2].AddNeighbor(mesas[neighbor.Item1]); } } return(mesas.Values.Concat <Area>(ramps.Values).ToList()); }
public Map(Map prior, List <Unit> units, Translator translator, Dictionary <uint, UnitTypeData> unitTypes, ImageData creep) { this.playerStartLocation = prior.playerStartLocation; this.enemyStartLocation = prior.enemyStartLocation; this.Raw = prior.Raw; this.Size = prior.Size; this.placementGridOriginal = prior.placementGridOriginal; this.placementGrid = new MapArray <byte>(this.placementGridOriginal); this.pathingGrid = prior.pathingGrid; this.heightGrid = prior.heightGrid; this.padding = prior.padding; this.creepGrid = new MapArray <byte>(creep.Data.ToByteArray(), this.Size); this.structuresAndDeposits = new MapArray <Unit>(this.Size); this.structuresAndDepositsList = new List <Unit>(); this.structurePadding = new MapArray <bool>(this.Size); this.resourcePadding = new MapArray <bool>(this.Size); foreach (var unit in units) { var unitType = unitTypes[unit.Raw.UnitType]; if (unitType.Attributes.Contains(Proto.Attribute.Structure)) { var structureSize = translator.GetStructureSize(unit); var originX = (int)Math.Round(unit.X - structureSize.X * 0.5f); var originY = (int)Math.Round(unit.Y - structureSize.Y * 0.5f); var origin = new Location { X = originX, Y = originY }; BuildingType buildingType = (unit.Type?.IsBuildingType ?? false) ? (BuildingType)unit.Type : null; this.structuresAndDepositsList.Add(unit); StoreBuildingLocation(origin, structureSize, unit, buildingType); } // Check unit orders to find planned buildings if (unit.IsWorker) { var currentlyBuilding = translator.CurrentlyBuilding(unit); if (currentlyBuilding != null && currentlyBuilding.IsBuildingType && currentlyBuilding != TerranBuildingType.Refinery && currentlyBuilding != ProtossBuildingType.Assimilator && currentlyBuilding != ZergBuildingType.Extractor) { var building = (BuildingType)currentlyBuilding; var buildingSize = translator.GetBuildingSize(building); var targetX = (int)Math.Round(unit.Raw.Orders[0].TargetWorldSpacePos.X - (buildingSize.X * 0.5f)); var targetY = (int)Math.Round(unit.Raw.Orders[0].TargetWorldSpacePos.Y - (buildingSize.Y * 0.5f)); var targetLocation = new Location { X = targetX, Y = targetY }; StoreBuildingLocation(targetLocation, buildingSize, null, building); } } } if (!this.playerStartLocation.HasValue) { var mainBase = units.Single(u => u.Raw.Alliance == Alliance.Self && u.IsMainBase); this.playerStartLocation = new Location { X = (int)mainBase.X, Y = (int)mainBase.Y }; } }