/// <summary> /// Constructs a mixed tile type for the given terrain types and combination. /// </summary> /// <param name="terrainTypeA">The name of the first terrain type.</param> /// <param name="terrainTypeB">The name of the second terrain type.</param> /// <param name="tileset">The tileset of the tile type.</param> /// <remarks>Terrain type A must be the parent of terrain type B.</remarks> public IsoTileType(string terrainTypeA, string terrainTypeB, TerrainCombination combination, TileSet tileset) { if (terrainTypeA == null) { throw new ArgumentNullException("terrainTypeA"); } if (terrainTypeB == null) { throw new ArgumentNullException("terrainTypeB"); } if (tileset == null) { throw new ArgumentNullException("tileset"); } if (combination == TerrainCombination.Simple) { throw new ArgumentException("combination", "Invalid combination for a mixed tile type!"); } this.tileset = tileset; TerrainType tA = this.tileset.GetTerrainTypeImpl(terrainTypeA); TerrainType tB = this.tileset.GetTerrainTypeImpl(terrainTypeB); if (tB.Parent != tA) { throw new ArgumentException(string.Format("TerrainType '{0}' must be the parent of TerrainType '{1}'!", terrainTypeA, terrainTypeB)); } this.terrainA = tA; this.terrainB = tB; this.combination = combination; this.tmpCurrentBranch = null; this.defaultBranch = null; this.variants = new List <Tuple <List <IsoTileVariant>, IIsoTileCondition> >(); }
/// <summary> /// Initializes the isometric tiles of the map structure. /// </summary> /// <param name="isotileListPackage">The package that contains the isometric tile informations.</param> private void LoadIsoTiles(RCPackage isotileListPackage) { string[] terrainIndexTable = isotileListPackage.ReadStringArray(0); byte[] isotileInfoBytes = isotileListPackage.ReadByteArray(1); int offset = 0; while (offset < isotileInfoBytes.Length) { int parsedBytes; RCPackage package = RCPackage.Parse(isotileInfoBytes, offset, isotileInfoBytes.Length - offset, out parsedBytes); if (package == null || !package.IsCommitted) { throw new MapException("Syntax error!"); } offset += parsedBytes; if (package.PackageFormat.ID == MapFileFormat.ISOTILE) { RCIntVector quadCoords = new RCIntVector(package.ReadShort(0), package.ReadShort(1)); TerrainCombination terrainCombo = (TerrainCombination)package.ReadByte(4); string terrainA = terrainIndexTable[package.ReadByte(2)]; string terrainB = terrainCombo != TerrainCombination.Simple ? terrainIndexTable[package.ReadByte(3)] : null; int variantIdx = package.ReadByte(5); this.mapStructure.InitIsoTile(quadCoords, terrainCombo == TerrainCombination.Simple ? this.mapStructure.Tileset.GetIsoTileType(terrainA) : this.mapStructure.Tileset.GetIsoTileType(terrainA, terrainB, terrainCombo), variantIdx); } } }
/// <summary> /// Creates a mixed tile type for the given terrain types. /// </summary> /// <param name="terrainTypeA">The name of the first terrain type.</param> /// <param name="terrainTypeB">The name of the second terrain type.</param> /// <param name="combination">The combination of the terrain types in the new mixed tile type.</param> /// <remarks>Terrain type A must be the parent of terrain type B.</remarks> public void CreateMixedTileType(string terrainTypeA, string terrainTypeB, TerrainCombination combination) { if (this.isFinalized) { throw new InvalidOperationException("It is not possible to create new tile type for a finalized TileSet!"); } if (terrainTypeA == null) { throw new ArgumentNullException("terrainTypeA"); } if (terrainTypeB == null) { throw new ArgumentNullException("terrainTypeB"); } if (combination == TerrainCombination.Simple) { throw new ArgumentException("combination", "Invalid combination for a mixed tile type!"); } Tuple <string, string, TerrainCombination> key = new Tuple <string, string, TerrainCombination>(terrainTypeA, terrainTypeB, combination); if (this.mixedTileTypes.ContainsKey(key)) { throw new TileSetException(string.Format("Mixed tile type for terrain types '{0}' and '{1}' with combination '{2}' already exists!", terrainTypeA, terrainTypeB, combination)); } IsoTileType newTile = new IsoTileType(terrainTypeA, terrainTypeB, combination, this); this.mixedTileTypes.Add(key, newTile); }
/// <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> /// Constructs a NeighbourCondition instance. /// </summary> /// <param name="combination">The terrain combination of the neighbour to check.</param> /// <param name="direction">The direction of the neighbour to check.</param> /// <param name="tileset">The tileset of this condition.</param> public NeighbourCondition(TerrainCombination combination, MapDirection direction, TileSet tileset) { if (tileset == null) { throw new ArgumentNullException("tileset"); } this.combination = combination; this.direction = direction; this.tileset = tileset; }
/// <see cref="ITileSet.GetIsoTileType"/> public IIsoTileType GetIsoTileType(string terrainTypeA, string terrainTypeB, TerrainCombination combination) { return(this.GetIsoTileTypeImpl(terrainTypeA, terrainTypeB, combination)); }
/// <see cref="ITileSet.GetIsoTileType"/> public IsoTileType GetIsoTileTypeImpl(string terrainTypeA, string terrainTypeB, TerrainCombination combination) { if (terrainTypeA == null) { throw new ArgumentNullException("terrainTypeA"); } if (terrainTypeB == null) { throw new ArgumentNullException("terrainTypeB"); } if (combination == TerrainCombination.Simple) { throw new ArgumentException("combination", "Invalid combination for a mixed tile type!"); } Tuple <string, string, TerrainCombination> key = new Tuple <string, string, TerrainCombination>(terrainTypeA, terrainTypeB, combination); if (!this.mixedTileTypes.ContainsKey(key)) { throw new TileSetException(string.Format("Mixed tile type for terrain types '{0}' and '{1}' with combination '{2}' doesn't exist!", terrainTypeA, terrainTypeB, combination)); } return(this.mixedTileTypes[key]); }
/// <summary> /// Fills up the layers from the base layer up to the target layer. /// </summary> /// <param name="center">The center of the draw operation.</param> /// <param name="targetTerrain">The target terrain of the draw operation.</param> /// <param name="baseTerrain">The base layer of the draw operation.</param> public void FillLayers(IMapAccess map, IIsoTile center, ITerrainType targetTerrain, ITerrainType baseTerrain) { /// Find the biggest flood area to be filled. FloodArea areaToFill = new FloodArea(); ITerrainType[] layersToFill = targetTerrain.FindRoute(baseTerrain); for (int routeIdx = 0; routeIdx < layersToFill.Length; routeIdx++) { ITerrainType prevTerrain = routeIdx - 1 >= 0 ? layersToFill[routeIdx - 1] : null; if (prevTerrain != null) { areaToFill.Enlarge(prevTerrain.TransitionLength + 1); } } /// Fill the appropriate layers for (int routeIdx = layersToFill.Length - 1; routeIdx >= 0; routeIdx--) { /// Fill the current layer at the appropriate area. ITerrainType currLayer = layersToFill[routeIdx]; foreach (FloodItem floodItem in areaToFill) { IIsoTile filledTile = map.GetIsoTile(center.MapCoords + floodItem.Coordinates); if (filledTile != null) { if (filledTile.Type.Combination != TerrainCombination.Simple) { /// Mixed tile. if (filledTile.Type.TerrainB == currLayer) { int newCombInt = (int)filledTile.Type.Combination | (floodItem.Combination != TerrainCombination.Simple ? (int)floodItem.Combination : 0xF); TerrainCombination newComb = newCombInt != 0xF ? (TerrainCombination)newCombInt : TerrainCombination.Simple; if (newComb != filledTile.Type.Combination) { filledTile.ExchangeType( newComb == TerrainCombination.Simple ? map.Tileset.GetIsoTileType(filledTile.Type.TerrainB.Name) : map.Tileset.GetIsoTileType(filledTile.Type.TerrainA.Name, filledTile.Type.TerrainB.Name, newComb)); } } else if (currLayer.IsDescendantOf(filledTile.Type.TerrainB)) { throw new MapException("Filling over the topmost layer is not possible!"); } } else { /// Simple tile. if (filledTile.Type.TerrainA == currLayer.Parent) { filledTile.ExchangeType( floodItem.Combination == TerrainCombination.Simple ? map.Tileset.GetIsoTileType(currLayer.Name) : map.Tileset.GetIsoTileType(filledTile.Type.TerrainA.Name, currLayer.Name, floodItem.Combination)); } else if (currLayer.IsDescendantOf(filledTile.Type.TerrainA)) { throw new MapException("Filling over the topmost layer is not possible!"); } } } } if (routeIdx > 0) { areaToFill.Reduce(); } } }
/// <summary> /// Clears the given layers for a draw operation. /// </summary> /// <param name="map">Reference to the map.</param> /// <param name="center">The center of the draw operation.</param> /// <param name="targetTerrain">The target terrain of the draw operation.</param> /// <param name="baseTerrain">The base layer of the draw operation.</param> /// <param name="layersToClear">The route from the target terrain up to a topmost layer in the terrain tree.</param> private void ClearLayers(IMapAccess map, IIsoTile center, ITerrainType targetTerrain, ITerrainType baseTerrain, ITerrainType[] layersToClear) { /// Find the biggest flood area to be cleared. FloodArea areaToClear = new FloodArea(); ITerrainType lastUninjuredLayer = null; for (int routeIdx = 0; routeIdx < layersToClear.Length; routeIdx++) { ITerrainType currTerrain = layersToClear[routeIdx]; if (lastUninjuredLayer == null) { /// We are going downstairs. ITerrainType nextTerrain = layersToClear[routeIdx + 1]; if (nextTerrain.Parent == currTerrain) { /// Last uninjured layer found, from now we go upstairs. lastUninjuredLayer = currTerrain; /// Enlarge the clear area by 1 if there was a previous layer along the way downstairs. ITerrainType prevTerrain = routeIdx - 1 >= 0 ? layersToClear[routeIdx - 1] : null; if (prevTerrain != null) { areaToClear.Enlarge(1); } } else { /// Enlarge the clear area by the transition length of the previous layer if there /// was a previous layer along the way downstairs. ITerrainType prevTerrain = routeIdx - 1 >= 0 ? layersToClear[routeIdx - 1] : null; if (prevTerrain != null) { areaToClear.Enlarge(prevTerrain.TransitionLength + 1); } } } else { /// We are going upstairs. ITerrainType prevTerrain = layersToClear[routeIdx - 1]; if (prevTerrain != lastUninjuredLayer) { areaToClear.Enlarge(currTerrain.TransitionLength + 1); } } } /// Clear the appropriate layers. if (lastUninjuredLayer == null) { throw new MapException("Last uninjured layer not found for draw terrain operation!"); } for (int routeIdx = layersToClear.Length - 1; routeIdx >= 0; routeIdx--) { ITerrainType currLayer = layersToClear[routeIdx]; if (currLayer == lastUninjuredLayer) { break; } /// Clear the current layer at the appropriate area. foreach (FloodItem floodItem in areaToClear) { IIsoTile clearedTile = map.GetIsoTile(center.MapCoords + floodItem.Coordinates); if (clearedTile != null) { if (clearedTile.Type.Combination != TerrainCombination.Simple) { /// Mixed tile. if (clearedTile.Type.TerrainB.IsDescendantOf(currLayer)) { /// Check whether TerrainB will be cleared by another branch or this is an error. if (!layersToClear.Contains(clearedTile.Type.TerrainB)) { continue; } else { throw new MapException("Clearing non-topmost layer is not possible!"); } } if (clearedTile.Type.TerrainB == currLayer) { TerrainCombination newComb = (TerrainCombination)((int)clearedTile.Type.Combination & ~(floodItem.Combination != TerrainCombination.Simple ? (int)floodItem.Combination : 0xF)); if (newComb != clearedTile.Type.Combination) { clearedTile.ExchangeType( newComb == TerrainCombination.Simple ? map.Tileset.GetIsoTileType(clearedTile.Type.TerrainA.Name) : map.Tileset.GetIsoTileType(clearedTile.Type.TerrainA.Name, clearedTile.Type.TerrainB.Name, newComb)); } } } else { /// Simple tile. if (clearedTile.Type.TerrainA.IsDescendantOf(currLayer)) { /// Check whether TerrainA will be cleared by another branch or this is an error. if (!layersToClear.Contains(clearedTile.Type.TerrainA)) { continue; } else { throw new MapException("Clearing non-topmost layer is not possible!"); } } if (clearedTile.Type.TerrainA == currLayer) { TerrainCombination newComb = (TerrainCombination)(0xF & ~(floodItem.Combination != TerrainCombination.Simple ? (int)floodItem.Combination : 0xF)); clearedTile.ExchangeType( newComb == TerrainCombination.Simple ? map.Tileset.GetIsoTileType(clearedTile.Type.TerrainA.Parent.Name) : map.Tileset.GetIsoTileType(clearedTile.Type.TerrainA.Parent.Name, clearedTile.Type.TerrainA.Name, newComb)); } } } } if (routeIdx > 1) { areaToClear.Reduce(); } } }
/// <summary>This method is used to rotate the terrain combination of an isometric tile.</summary> /// <param name="combination">The original terrain combination to be rotated.</param> /// <param name="origDir"> /// The original direction vector of the isometric tile. /// Can be one of the followings: MapDirection.NorthEast, MapDirection.SouthEast, MapDirection.SouthWest, /// MapDirection.NorthWest. /// </param> /// <param name="newDir"> /// The new direction vector of the isometric tile. /// Can be one of the followings: MapDirection.NorthEast, MapDirection.SouthEast, MapDirection.SouthWest, /// MapDirection.NorthWest. /// </param> /// <returns>The rotated terrain combination of the tile.</returns> public static TerrainCombination RotateTerrainCombination(TerrainCombination combination, MapDirection origDir, MapDirection newDir) { /// No rotation has to be performed in case of simple terrain combination. if (combination == TerrainCombination.Simple) { return(combination); } if (newDir != MapDirection.NorthEast) { int modifiedOrigDir = ((int)MapDirection.NorthEast - (int)newDir + (int)origDir) % 8; return(MapHelper.RotateTerrainCombination(combination, (MapDirection)(modifiedOrigDir >= 0 ? modifiedOrigDir : modifiedOrigDir + 8), MapDirection.NorthEast)); } if (origDir == MapDirection.NorthEast) { /// No rotation has to be performed as the direction vector already points to MapDirection.NorthEast. return(combination); } else if (origDir == MapDirection.SouthEast) { /// Rotate by +90 degrees. if (combination == TerrainCombination.AAAB) { return(TerrainCombination.AABA); } if (combination == TerrainCombination.AABA) { return(TerrainCombination.ABAA); } if (combination == TerrainCombination.AABB) { return(TerrainCombination.ABBA); } if (combination == TerrainCombination.ABAA) { return(TerrainCombination.BAAA); } if (combination == TerrainCombination.ABAB) { return(TerrainCombination.BABA); } if (combination == TerrainCombination.ABBA) { return(TerrainCombination.BBAA); } if (combination == TerrainCombination.ABBB) { return(TerrainCombination.BBBA); } if (combination == TerrainCombination.BAAA) { return(TerrainCombination.AAAB); } if (combination == TerrainCombination.BAAB) { return(TerrainCombination.AABB); } if (combination == TerrainCombination.BABA) { return(TerrainCombination.ABAB); } if (combination == TerrainCombination.BABB) { return(TerrainCombination.ABBB); } if (combination == TerrainCombination.BBAA) { return(TerrainCombination.BAAB); } if (combination == TerrainCombination.BBAB) { return(TerrainCombination.BABB); } if (combination == TerrainCombination.BBBA) { return(TerrainCombination.BBAB); } throw new ArgumentException("Invalid terrain combination!", "combination"); } else if (origDir == MapDirection.SouthWest) { /// Rotate by +180 degrees. if (combination == TerrainCombination.AAAB) { return(TerrainCombination.ABAA); } if (combination == TerrainCombination.AABA) { return(TerrainCombination.BAAA); } if (combination == TerrainCombination.AABB) { return(TerrainCombination.BBAA); } if (combination == TerrainCombination.ABAA) { return(TerrainCombination.AAAB); } if (combination == TerrainCombination.ABAB) { return(TerrainCombination.ABAB); } if (combination == TerrainCombination.ABBA) { return(TerrainCombination.BAAB); } if (combination == TerrainCombination.ABBB) { return(TerrainCombination.BBAB); } if (combination == TerrainCombination.BAAA) { return(TerrainCombination.AABA); } if (combination == TerrainCombination.BAAB) { return(TerrainCombination.ABBA); } if (combination == TerrainCombination.BABA) { return(TerrainCombination.BABA); } if (combination == TerrainCombination.BABB) { return(TerrainCombination.BBBA); } if (combination == TerrainCombination.BBAA) { return(TerrainCombination.AABB); } if (combination == TerrainCombination.BBAB) { return(TerrainCombination.ABBB); } if (combination == TerrainCombination.BBBA) { return(TerrainCombination.BABB); } throw new ArgumentException("Invalid terrain combination!", "combination"); } else if (origDir == MapDirection.NorthWest) { /// Rotate by +270 degrees. if (combination == TerrainCombination.AAAB) { return(TerrainCombination.BAAA); } if (combination == TerrainCombination.AABA) { return(TerrainCombination.AAAB); } if (combination == TerrainCombination.AABB) { return(TerrainCombination.BAAB); } if (combination == TerrainCombination.ABAA) { return(TerrainCombination.AABA); } if (combination == TerrainCombination.ABAB) { return(TerrainCombination.BABA); } if (combination == TerrainCombination.ABBA) { return(TerrainCombination.AABB); } if (combination == TerrainCombination.ABBB) { return(TerrainCombination.BABB); } if (combination == TerrainCombination.BAAA) { return(TerrainCombination.ABAA); } if (combination == TerrainCombination.BAAB) { return(TerrainCombination.BBAA); } if (combination == TerrainCombination.BABA) { return(TerrainCombination.ABAB); } if (combination == TerrainCombination.BABB) { return(TerrainCombination.BBAB); } if (combination == TerrainCombination.BBAA) { return(TerrainCombination.ABBA); } if (combination == TerrainCombination.BBAB) { return(TerrainCombination.BBBA); } if (combination == TerrainCombination.BBBA) { return(TerrainCombination.ABBB); } throw new ArgumentException("Invalid terrain combination!", "combination"); } else { throw new ArgumentException("Invalid direction vector!", "direction"); } }
/// <summary> /// Generates the terrain-NESW array for the given terrain types and their combination. /// </summary> /// <param name="a">The first terrain type.</param> /// <param name="b">The second terrain type or null in case of simple combination.</param> /// <param name="combination">The combination of the terrains.</param> /// <returns> /// A 4-long array that contains the terrain types in the north, east, south and west quarter (in this order). /// </returns> public static ITerrainType[] GetTerrainNESW(ITerrainType a, ITerrainType b, TerrainCombination combination) { if (a == null) { throw new ArgumentNullException("a"); } if (combination == TerrainCombination.Simple && b != null) { throw new ArgumentException("TerrainB must be null in case of simple tile type!", "b"); } if (combination != TerrainCombination.Simple && b == null) { throw new ArgumentException("TerrainB cannot be null in case of mixed tile type!", "b"); } if (combination == TerrainCombination.Simple) { return(new ITerrainType[4] { a, a, a, a }); } if (combination == TerrainCombination.AAAB) { return(new ITerrainType[4] { a, a, a, b }); } if (combination == TerrainCombination.AABA) { return(new ITerrainType[4] { a, a, b, a }); } if (combination == TerrainCombination.AABB) { return(new ITerrainType[4] { a, a, b, b }); } if (combination == TerrainCombination.ABAA) { return(new ITerrainType[4] { a, b, a, a }); } if (combination == TerrainCombination.ABAB) { return(new ITerrainType[4] { a, b, a, b }); } if (combination == TerrainCombination.ABBA) { return(new ITerrainType[4] { a, b, b, a }); } if (combination == TerrainCombination.ABBB) { return(new ITerrainType[4] { a, b, b, b }); } if (combination == TerrainCombination.BAAA) { return(new ITerrainType[4] { b, a, a, a }); } if (combination == TerrainCombination.BAAB) { return(new ITerrainType[4] { b, a, a, b }); } if (combination == TerrainCombination.BABA) { return(new ITerrainType[4] { b, a, b, a }); } if (combination == TerrainCombination.BABB) { return(new ITerrainType[4] { b, a, b, b }); } if (combination == TerrainCombination.BBAA) { return(new ITerrainType[4] { b, b, a, a }); } if (combination == TerrainCombination.BBAB) { return(new ITerrainType[4] { b, b, a, b }); } if (combination == TerrainCombination.BBBA) { return(new ITerrainType[4] { b, b, b, a }); } throw new ArgumentException("Invalid terrain combination!", "combination"); }