/// <summary> /// Sets the given isometric tile as the parent of this cell. /// </summary> /// <param name="tile">The isometric tile to set as the parent of this cell.</param> /// <param name="isoIndices">The indices of this cell inside the parent isometric tile.</param> public void SetIsoTile(IsoTile tile, RCIntVector isoIndices) { if (tile == null) { throw new ArgumentNullException("tile"); } if (isoIndices == RCIntVector.Undefined) { throw new ArgumentNullException("isoIndices"); } if (tile.ParentMap != this.parentMap) { throw new InvalidOperationException("cells and their parent isometric tile must have the same parent map!"); } if (this.parentMap.Status != MapStructure.MapStatus.Initializing) { throw new InvalidOperationException(string.Format("Invalid operation! Map status: {0}", this.parentMap.Status)); } if (this.parentIsoTile != null) { throw new InvalidOperationException("Parent isometric tile already set!"); } this.parentIsoTile = tile; this.isoIndices = isoIndices; }
/// <summary> /// Validates whether the neighbour of this isometric tile in the given direction satisfies the /// constraints of the tileset. /// </summary> /// <param name="dir">The direction of the neighbour to validate.</param> private void ValidateNeighbour(MapDirection dir) { IsoTile neighbour = this.neighbours[(int)dir]; if (neighbour != null) { /// To simplify the algorithm, we use terrain combinations rotated to MapDirection.NorthEast. TerrainCombination thisCombRot = MapHelper.RotateTerrainCombination(this.type.Combination, dir, MapDirection.NorthEast); TerrainCombination neighCombRot = MapHelper.RotateTerrainCombination(neighbour.type.Combination, dir, MapDirection.NorthEast); /// Generate the terrain-NESW array for this tile and the neighbour. ITerrainType[] thisNESWRot = MapHelper.GetTerrainNESW(this.type.TerrainA, this.type.TerrainB, thisCombRot); ITerrainType[] neighNESWRot = MapHelper.GetTerrainNESW(neighbour.type.TerrainA, neighbour.type.TerrainB, neighCombRot); /// Check the generated terrain-NESW arrays. if (thisNESWRot[0] != neighNESWRot[3] || thisNESWRot[1] != neighNESWRot[2]) { throw new MapException(string.Format("Invalid neighbours at {0} and {1}!", this.mapCoords, neighbour.mapCoords)); } /// Check whether the given direction satisfies the transition-length constraint. if (this.type.TerrainB != null && this.type.TerrainB.TransitionLength > 0 && thisNESWRot[0] == this.type.TerrainA && thisNESWRot[1] == this.type.TerrainA) { int remaining = this.type.TerrainB.TransitionLength; IsoTile currTile = neighbour; while (currTile != null && remaining > 0) { /// Generate the terrain-NESW array of the currently checked tile. TerrainCombination currCombRot = MapHelper.RotateTerrainCombination(currTile.type.Combination, dir, MapDirection.NorthEast); ITerrainType[] currNESWRot = MapHelper.GetTerrainNESW(currTile.type.TerrainA, currTile.type.TerrainB, currCombRot); /// Check if the currently checked tile is part of the transition. if (currNESWRot[0] != this.type.TerrainA || currNESWRot[1] != this.type.TerrainA || currNESWRot[2] != this.type.TerrainA || currNESWRot[3] != this.type.TerrainA) { /// No, it's not part of the transition. We have to check whether the upcoming terrain type /// is another child of TerrainA or not. if (currNESWRot[2] == this.type.TerrainA && currNESWRot[3] == this.type.TerrainA && (currNESWRot[0] == this.type.TerrainA || currNESWRot[0].Parent == this.type.TerrainA) && (currNESWRot[1] == this.type.TerrainA || currNESWRot[1].Parent == this.type.TerrainA)) { /// It's another child of TerrainA, no contradiction with the tileset -> OK. break; } else { /// It's not a child of TerrainA -> Error. throw new MapException(string.Format("Invalid transition from {0} in direction {1}! Length must be at least {2}!", this.mapCoords, dir, this.type.TerrainB.TransitionLength)); } } /// Yes, it's part of the transition. We can switch to the next tile in the same direction. currTile = currTile.neighbours[(int)dir]; remaining--; } } } }
/// <summary> /// Using this method the isometric tiles notify the MapStructure object if they type has been changed during a /// tile exchanging operation. /// </summary> /// <param name="tile">The isometric tile whose type has been changed.</param> public void OnIsoTileExchanged(IsoTile tile) { if (this.status != MapStatus.ExchangingTiles) { throw new InvalidOperationException(string.Format("Invalid operation! Map status: {0}", this.status)); } if (tile == null) { throw new ArgumentNullException("tile"); } this.tmpReplacedTiles.Add(tile); }
/// <summary> /// Detaches the appropriate neighbours and cells of this isometric tile depending on the size of the map. /// </summary> public void DetachAtSize() { if (this.parentMap.Status != MapStructure.MapStatus.Opening) { throw new InvalidOperationException(string.Format("Invalid operation! Map status: {0}", this.parentMap.Status)); } RCNumVector edges = new RCNumVector((RCNumber)(this.parentMap.Size.X * 2 - 1) / 2, (RCNumber)(this.parentMap.Size.Y * 2 - 1) / 2); /// Detach the appropriate neighbours. for (int dir = 0; dir < this.neighbours.Length; dir++) { if (this.neighbours[dir] != null) { IsoTile neighbour = this.neighbours[dir]; RCNumVector quadCoords = MapStructure.QuadIsoTransform.TransformBA(neighbour.mapCoords); if (quadCoords.X > edges.X || quadCoords.Y > edges.Y) { this.detachedNeighbours.Add(new Tuple <IsoTile, MapDirection>(this.neighbours[dir], (MapDirection)dir)); this.neighbours[dir] = null; } } } /// Detach the appropriate cells. for (int col = 0; col < MapStructure.QUAD_PER_ISO_VERT * MapStructure.NAVCELL_PER_QUAD; col++) { for (int row = 0; row < MapStructure.QUAD_PER_ISO_HORZ * MapStructure.NAVCELL_PER_QUAD; row++) { if (this.cells[col, row] != null && (this.cells[col, row].MapCoords.X >= this.parentMap.Size.X * MapStructure.NAVCELL_PER_QUAD || this.cells[col, row].MapCoords.Y >= this.parentMap.Size.Y * MapStructure.NAVCELL_PER_QUAD)) { this.detachedCells.Add(this.cells[col, row]); this.cells[col, row] = null; } } } /// Detach the appropriate cutting quadratic tiles. foreach (QuadTile cuttingQuadTile in this.cuttingQuadTiles) { if (cuttingQuadTile.MapCoords.X >= this.parentMap.Size.X || cuttingQuadTile.MapCoords.Y >= this.parentMap.Size.Y) { this.detachedCuttingQuadTiles.Add(cuttingQuadTile); } } }
/// <summary> /// Builds up the relationships between the cells and isometric tiles. /// </summary> private void SetNavCellIsoIndices() { foreach (Cell cell in this.cells) { RCNumVector isoCoords = MapStructure.NavCellIsoTransform.TransformAB(cell.MapCoords); RCIntVector isoCoordsInt = isoCoords.Round(); IsoTile isoTile = this.isometricTiles[isoCoordsInt]; RCNumVector isoCoordsRel = isoCoords - isoTile.MapCoords; RCNumVector isoIndices = MapStructure.NavCellIsoTransform.TransformBA(isoCoordsRel); RCIntVector isoIndicesInt = isoIndices.Round(); cell.SetIsoTile(isoTile, isoIndicesInt); isoTile.SetCell(cell, isoIndicesInt); } }
/// <summary> /// Constructs a quadratic tile. /// </summary> /// <param name="map">The map that this quadratic tile belongs to.</param> /// <param name="primaryIsoTile">Primary isometric tile.</param> /// <param name="secondaryIsoTile">Secondary isometric tile or null if doesn't exist.</param> /// <param name="mapCoords">The map coordinates of this quadratic tile.</param> public QuadTile(MapStructure map, IsoTile primaryIsoTile, IsoTile secondaryIsoTile, RCIntVector mapCoords) { if (map == null) { throw new ArgumentNullException("map"); } if (primaryIsoTile == null) { throw new ArgumentNullException("isoTile"); } if (mapCoords == RCIntVector.Undefined) { throw new ArgumentNullException("mapCoords"); } if (mapCoords.X < 0 || mapCoords.X >= MapStructure.MAX_MAPSIZE || mapCoords.Y < 0 || mapCoords.Y >= MapStructure.MAX_MAPSIZE) { throw new ArgumentOutOfRangeException("mapCoords"); } this.parentMap = map; this.primaryIsoTile = primaryIsoTile; this.secondaryIsoTile = secondaryIsoTile; this.terrainObject = null; this.mapCoords = mapCoords; this.neighbours = new QuadTile[8]; this.detachedNeighbours = new List <Tuple <QuadTile, MapDirection> >(); this.isBuildableCache = new CachedValue <bool>(this.CalculateBuildabilityFlag); this.groundLevelCache = new CachedValue <int>(this.CalculateGroundLevel); this.cells = new Cell[MapStructure.NAVCELL_PER_QUAD, MapStructure.NAVCELL_PER_QUAD]; for (int col = 0; col < MapStructure.NAVCELL_PER_QUAD; col++) { for (int row = 0; row < MapStructure.NAVCELL_PER_QUAD; row++) { this.cells[col, row] = new Cell(this.parentMap, this, new RCIntVector(col, row)); } } this.primaryIsoTile.SetCuttingQuadTile(this); if (this.secondaryIsoTile != null) { this.secondaryIsoTile.SetCuttingQuadTile(this); } }
/// <summary> /// Sets the given isometric tile as a neighbour of this isometric tile in the given direction. /// </summary> /// <param name="neighbour">The isometric tile to be set as a neighbour of this.</param> /// <param name="direction">The direction of the new neighbour.</param> public void SetNeighbour(IsoTile neighbour, MapDirection direction) { if (neighbour == null) { throw new ArgumentNullException("neighbour"); } if (neighbour.parentMap != this.parentMap) { throw new InvalidOperationException("Neighbour isometric tiles must have the same parent map!"); } if (this.parentMap.Status != MapStructure.MapStatus.Initializing) { throw new InvalidOperationException(string.Format("Invalid operation! Map status: {0}", this.parentMap.Status)); } if (this.neighbours[(int)direction] != null) { throw new InvalidOperationException(string.Format("Neighbour in direction {0} already set!", direction)); } this.neighbours[(int)direction] = neighbour; }
/// <summary> /// Attaches the part of the map structure that is out of its size. /// </summary> private void AttachAtSize() { /// Attach the cells along the edge of the map. for (int row = 0; row < this.size.Y * NAVCELL_PER_QUAD; row++) { this.cells[this.size.X * NAVCELL_PER_QUAD - 1, row].AttachNeighboursAtSize(); } for (int col = 0; col < this.size.X * NAVCELL_PER_QUAD; col++) { this.cells[col, this.size.Y * NAVCELL_PER_QUAD - 1].AttachNeighboursAtSize(); } /// Attach the quadratic tiles along the edge of the map. for (int row = 0; row < this.size.Y; row++) { this.quadTiles[this.size.X - 1, row].AttachNeighboursAtSize(); } for (int col = 0; col < this.size.X; col++) { this.quadTiles[col, this.size.Y - 1].AttachNeighboursAtSize(); } RCNumVector outerEdges = new RCNumVector((RCNumber)(this.size.X * 2 - 1) / 2, (RCNumber)(this.size.Y * 2 - 1) / 2); RCNumVector innerEdges = new RCNumVector(((RCNumber)(this.size.X * 2 - 1) / 2) - MapStructure.QUAD_PER_ISO_VERT_HALF, ((RCNumber)(this.size.Y * 2 - 1) / 2) - MapStructure.QUAD_PER_ISO_HORZ_HALF); /// Attach isometric tiles along the inner vertical edge. for (RCNumber innerVertical = MapStructure.QUAD_PER_ISO_HORZ_HALF - (RCNumber)1 / (RCNumber)2; innerVertical <= innerEdges.Y; innerVertical += MapStructure.QUAD_PER_ISO_HORZ) { RCNumVector quadCoords = new RCNumVector(innerEdges.X, innerVertical); RCIntVector isoCoords = MapStructure.QuadIsoTransform.TransformAB(quadCoords).Round(); IsoTile tile = this.isometricTiles[isoCoords]; tile.AttachAtSize(); } /// Attach isometric tiles along the outer vertical edge. for (RCNumber outerVertical = -((RCNumber)1 / (RCNumber)2); outerVertical <= outerEdges.Y; outerVertical += MapStructure.QUAD_PER_ISO_HORZ) { RCNumVector quadCoords = new RCNumVector(outerEdges.X, outerVertical); RCIntVector isoCoords = MapStructure.QuadIsoTransform.TransformAB(quadCoords).Round(); IsoTile tile = this.isometricTiles[isoCoords]; tile.AttachAtSize(); } /// Attach isometric tiles along the inner horizontal edge. for (RCNumber innerHorizontal = MapStructure.QUAD_PER_ISO_VERT_HALF - ((RCNumber)1 / (RCNumber)2); innerHorizontal <= innerEdges.X; innerHorizontal += MapStructure.QUAD_PER_ISO_VERT) { RCNumVector quadCoords = new RCNumVector(innerHorizontal, innerEdges.Y); RCIntVector isoCoords = MapStructure.QuadIsoTransform.TransformAB(quadCoords).Round(); IsoTile tile = this.isometricTiles[isoCoords]; tile.AttachAtSize(); } /// Attach isometric tiles along the outer horizontal edge. for (RCNumber outerHorizontal = -((RCNumber)1 / (RCNumber)2); outerHorizontal <= outerEdges.X; outerHorizontal += MapStructure.QUAD_PER_ISO_VERT) { RCNumVector quadCoords = new RCNumVector(outerHorizontal, outerEdges.Y); RCIntVector isoCoords = MapStructure.QuadIsoTransform.TransformAB(quadCoords).Round(); IsoTile tile = this.isometricTiles[isoCoords]; tile.AttachAtSize(); } }
/// <summary> /// Initializes the structure of this map. /// </summary> public void Initialize() { if (this.status != MapStatus.Initializing) { throw new InvalidOperationException(string.Format("Invalid operation! Map status: {0}", this.status)); } /// Create the quadratic and isometric tiles. RCIntVector navCellPerQuadVect = new RCIntVector(NAVCELL_PER_QUAD, NAVCELL_PER_QUAD); for (int col = 0; col < MAX_MAPSIZE; col++) { for (int row = 0; row < MAX_MAPSIZE; row++) { /// Calculate the coordinates of the isometric tiles that contain the corner cells of the current quadratic tile. /// Order of the cornerIsoCoords array: NorthWest, NorthEast, SouthEast, SouthWest. RCIntVector quadTileCoords = new RCIntVector(col, row); RCIntVector[] cornerIsoCoords = new RCIntVector[4] { MapStructure.NavCellIsoTransform.TransformAB(quadTileCoords * navCellPerQuadVect + new RCIntVector(0, 0)).Round(), MapStructure.NavCellIsoTransform.TransformAB(quadTileCoords * navCellPerQuadVect + new RCIntVector(NAVCELL_PER_QUAD - 1, 0)).Round(), MapStructure.NavCellIsoTransform.TransformAB(quadTileCoords * navCellPerQuadVect + new RCIntVector(NAVCELL_PER_QUAD - 1, NAVCELL_PER_QUAD - 1)).Round(), MapStructure.NavCellIsoTransform.TransformAB(quadTileCoords * navCellPerQuadVect + new RCIntVector(0, NAVCELL_PER_QUAD - 1)).Round() }; /// Create the isometric tiles. int isoTileACount = 0, isoTileBCount = 0; IsoTile isoTileA = null, isoTileB = null; for (int cornerIdx = 0; cornerIdx < 4; cornerIdx++) { if (isoTileA == null) { isoTileA = this.isometricTiles.ContainsKey(cornerIsoCoords[cornerIdx]) ? this.isometricTiles[cornerIsoCoords[cornerIdx]] : new IsoTile(this, cornerIsoCoords[cornerIdx]); this.isometricTiles[cornerIsoCoords[cornerIdx]] = isoTileA; isoTileACount++; } else if (cornerIsoCoords[cornerIdx] == isoTileA.MapCoords) { isoTileACount++; } else if (isoTileB == null) { isoTileB = this.isometricTiles.ContainsKey(cornerIsoCoords[cornerIdx]) ? this.isometricTiles[cornerIsoCoords[cornerIdx]] : new IsoTile(this, cornerIsoCoords[cornerIdx]); this.isometricTiles[cornerIsoCoords[cornerIdx]] = isoTileB; isoTileBCount++; } else if (cornerIsoCoords[cornerIdx] == isoTileB.MapCoords) { isoTileBCount++; } else { throw new InvalidOperationException("Unexpected case!"); } } QuadTile quadTile = new QuadTile(this, isoTileACount >= isoTileBCount ? isoTileA : isoTileB, isoTileACount < isoTileBCount ? isoTileA : isoTileB, quadTileCoords); this.quadTiles[quadTileCoords.X, quadTileCoords.Y] = quadTile; } } /// Buildup the structure of the map. this.SetIsoNeighbours(); this.SetQuadNeighbours(); this.SetNavCellNeighbours(); this.SetNavCellIsoIndices(); this.status = MapStatus.Closed; }