public void CalculateTerrainAutotiles(Loc rectStart, Loc rectSize) { //does not calculate floor tiles. //in all known use cases, there is no need to autotile floor tiles. //if a use case is brought up that does, this can be changed. HashSet <int> blocktilesets = new HashSet <int>(); for (int ii = rectStart.X; ii < rectStart.X + rectSize.X; ii++) { for (int jj = rectStart.Y; jj < rectStart.Y + rectSize.Y; jj++) { if (Collision.InBounds(Width, Height, new Loc(ii, jj))) { AutoTile outTile; if (TextureMap.TryGetValue(Tiles[ii][jj].Data.ID, out outTile)) { Tiles[ii][jj].Data.TileTex = outTile.Copy(); } if (Tiles[ii][jj].Data.TileTex.AutoTileset > -1) { blocktilesets.Add(Tiles[ii][jj].Data.TileTex.AutoTileset); } } } } foreach (int tileset in blocktilesets) { AutoTileData entry = DataManager.Instance.GetAutoTile(tileset); entry.Tiles.AutoTileArea(Rand.FirstSeed, rectStart, rectSize, new Loc(Width, Height), (int x, int y, int neighborCode) => { Tiles[x][y].Data.TileTex.NeighborCode = neighborCode; }, (int x, int y) => { if (!Collision.InBounds(Width, Height, new Loc(x, y))) { return(true); } return(Tiles[x][y].Data.TileTex.AutoTileset == tileset); }, (int x, int y) => { if (!Collision.InBounds(Width, Height, new Loc(x, y))) { return(true); } return(Tiles[x][y].Data.TileTex.AutoTileset == tileset || Tiles[x][y].Data.TileTex.Associates.Contains(tileset)); }); } }
public override void Apply(T map) { AutoTileData entry = DataManager.Instance.GetAutoTile(BlockTileset); map.Map.BlankBG = new AutoTile(entry.Tiles.Generic); map.Map.FloorBG = new AutoTile(GroundTileset, IndependentGround ? -1 : BlockTileset); map.Map.TextureMap[1] = new AutoTile(BlockTileset, -1); map.Map.TextureMap[2] = new AutoTile(BlockTileset, -1); map.Map.TextureMap[3] = new AutoTile(WaterTileset, -1); map.Map.TextureMap[4] = new AutoTile(WaterTileset, -1); map.Map.TextureMap[5] = new AutoTile(WaterTileset, -1); map.Map.Element = GroundElement; }
/// <summary> /// Transforms the intermediate AutoTile data into an output format that is optimized for /// efficient reading and updating operations. /// /// Requires the total number of output tiles, including generated ones, to be known. /// </summary> private void TransformAutoTileData(List <TilesetAutoTileInfo> outputData) { for (int autoTileIndex = 0; autoTileIndex < this.autoTiles.Count; autoTileIndex++) { AutoTileData data = this.autoTiles[autoTileIndex]; TilesetAutoTileItem[] tileInfo = new TilesetAutoTileItem[this.outputTileCount]; data.TileInfo.CopyTo(tileInfo, 0); outputData.Add(new TilesetAutoTileInfo( data.BaseTile, data.StateToTile, tileInfo)); } }
public void Draw(SpriteBatch spriteBatch, Loc pos) { List <TileLayer> layers; if (AutoTileset > -1) { AutoTileData entry = DataManager.Instance.GetAutoTile(AutoTileset); layers = entry.Tiles.GetLayers(NeighborCode); } else { layers = Layers; } foreach (TileLayer anim in layers) { anim.Draw(spriteBatch, pos, GraphicsManager.TotalFrameTick); } }
public static void ImportAllDtefTiles(string sourceDir, string cachePattern) { var dirs = Directory.GetDirectories(sourceDir); foreach (var dir in dirs) { var fileName = Path.GetFileName(dir); var outputFnTiles = string.Format(cachePattern, fileName); DiagManager.Instance.LoadMsg = "Importing " + fileName; // Read XML for layer mapping var document = new XmlDocument(); document.Load(Path.Join(dir, XML_FN)); var tileSize = int.Parse(document.DocumentElement.GetAttribute("dimensions")); // The tile index inside the tile sheet where the first frame of animation for this variation is. var variationStarts = new[] { 0, 0, 0 }; // Outer dict: Layer num; inner dict: frame num; tuple: (file name, frame length) var frameSpecs = new[] { new SortedDictionary <int, SortedDictionary <int, Tuple <string, int> > >(), new SortedDictionary <int, SortedDictionary <int, Tuple <string, int> > >(), new SortedDictionary <int, SortedDictionary <int, Tuple <string, int> > >() }; try { var tileList = new List <BaseSheet>(); foreach (var tileTitle in TileTitles) { for (var vi = 0; vi < VariantTitles.Length; vi++) { variationStarts[vi] = tileList.Count; var variantFn = VariantTitles[vi]; var reg = VariantTitlesFrames[vi]; var path = Path.Join(dir, variantFn); if (!File.Exists(path)) { if (variantFn != VAR0_FN) { throw new KeyNotFoundException($"Base variant missing for {fileName}."); } continue; } // Import main frame var tileset = BaseSheet.Import(path); tileList.Add(tileset); // List additional layers and their frames - We do it this way in two steps to make sure it's sorted foreach (var frameFn in Directory.GetFiles(dir, "*.png")) { if (!reg.IsMatch(frameFn)) { continue; } var match = reg.Match(frameFn); var layerIdx = int.Parse(match.Groups[1].ToString()); var frameIdx = int.Parse(match.Groups[2].ToString()); var durationIdx = int.Parse(match.Groups[3].ToString()); if (!frameSpecs[vi].ContainsKey(layerIdx)) { frameSpecs[vi].Add(layerIdx, new SortedDictionary <int, Tuple <string, int> >()); } // GetFiles lists some files twice?? if (!frameSpecs[vi][layerIdx].ContainsKey(frameIdx)) { frameSpecs[vi][layerIdx].Add(frameIdx, new Tuple <string, int>(frameFn, durationIdx)); } } // Import additional frames foreach (var layerFn in frameSpecs[vi].Values) { foreach (var frameFn in layerFn.Values) { // Import frame tileset = BaseSheet.Import(frameFn.Item1); tileList.Add(tileset); } } } var node = document.SelectSingleNode("//DungeonTileset/RogueEssence/" + tileTitle); var index = -1; if (node != null) { index = int.Parse(node.InnerText); } var autoTile = new AutoTileData(); var entry = new AutoTileAdjacent(); var totalArray = new List <TileLayer> [48][]; for (var jj = 0; jj < FieldDtefMapping.Length; jj++) { totalArray[jj] = new List <TileLayer> [3]; for (var kk = 0; kk < MAX_VARIANTS; kk++) { totalArray[jj][kk] = new List <TileLayer>(); } } for (var jj = 0; jj < FieldDtefMapping.Length; jj++) { for (var kk = 0; kk < MAX_VARIANTS; kk++) { if (FieldDtefMapping[jj] == -1) { continue; // Skip empty tile } var offIndex = tileTitle switch { "Secondary" => 1, "Floor" => 2, _ => 0 }; var tileX = 6 * offIndex + jj % 6; var tileY = (int)Math.Floor(jj / 6.0); // Base Layer var baseLayer = new TileLayer { FrameLength = 999 }; var idx = variationStarts[kk]; var tileset = tileList[idx]; //keep adding more tiles to the anim until end of blank spot is found if (!tileset.IsBlank(tileX * tileSize, tileY * tileSize, tileSize, tileSize)) { baseLayer.Frames.Add(new TileFrame(new Loc(tileX, tileY + idx * 8), fileName)); } if (baseLayer.Frames.Count < 1) { continue; } totalArray[jj][kk].Add(baseLayer); // Additional layers var processedLayerFrames = 1; foreach (var layer in frameSpecs[kk].Values) { if (layer.Count < 1) { continue; } var anim = new TileLayer { FrameLength = layer[0].Item2 }; for (var mm = 0; mm < layer.Count; mm++) { idx = variationStarts[kk] + processedLayerFrames; processedLayerFrames += 1; if (tileList.Count <= idx) { continue; } tileset = tileList[idx]; //keep adding more tiles to the anim until end of blank spot is found if (!tileset.IsBlank(tileX * tileSize, tileY * tileSize, tileSize, tileSize)) { anim.Frames.Add(new TileFrame(new Loc(tileX, tileY + idx * 8), fileName)); } } if (anim.Frames.Count > 0) { totalArray[jj][kk].Add(anim); } } } } if (index == -1) { if (tileTitle == "Secondary") // Secondary terrain is okay to be missing. { continue; } throw new KeyNotFoundException($"Layer index mapping for layer {tileTitle} for {fileName} missing."); } // Import auto tiles for (var i = 0; i < FieldDtefMapping.Length; i++) { if (FieldDtefMapping[i] == -1) { continue; } List <List <TileLayer> > tileArray = typeof(AutoTileAdjacent) .GetField($"Tilex{FieldDtefMapping[i]:X2}") .GetValue(entry) as List <List <TileLayer> >; ImportTileVariant(tileArray, totalArray[i]); } autoTile.Tiles = entry; autoTile.Name = new LocalText(fileName + tileTitle); DataManager.SaveData(index, DataManager.DataType.AutoTile.ToString(), autoTile); Debug.WriteLine($"{index:D3}: {autoTile.Name}"); } ImportHelper.SaveTileSheet(tileList, outputFnTiles, tileSize); foreach (var tex in tileList) { tex.Dispose(); } } catch (Exception ex) { DiagManager.Instance.LogError(new Exception("Error importing " + fileName + "\n", ex)); } } }
/// <summary> /// Gathers and processes AutoTile input data into an easily modifyable intermediate format, /// while also collecting information on generated tiles and connectivity state mappings. /// </summary> /// <param name="autoTileConfig"></param> private void GatherAutoTileData(IReadOnlyList <TilesetAutoTileInput> autoTileConfig) { for (int autoTileIndex = 0; autoTileIndex < autoTileConfig.Count; autoTileIndex++) { TilesetAutoTileInput autoTileInput = autoTileConfig[autoTileIndex]; AutoTileData autoTile = new AutoTileData { BaseTile = MathF.Clamp(autoTileInput.BaseTileIndex, 0, this.inputTileCount - 1), TileInfo = this.autoTileItemPool.Rent(this.inputTileCount), StateToTile = new int[(int)TileConnection.All + 1], IsStateAvailable = new bool[(int)TileConnection.All + 1] }; autoTile.TileInfo.Count = this.inputTileCount; // Initialize the tile mapping for all potential connection states with the base tile for (int conIndex = 0; conIndex < autoTile.StateToTile.Length; conIndex++) { autoTile.StateToTile[conIndex] = autoTile.BaseTile; } // Use the directly applicable tile mapping as-is int autoTileSourceTileCount = MathF.Min(autoTileInput.TileInput.Count, this.inputTileCount); for (int tileIndex = autoTileSourceTileCount - 1; tileIndex >= 0; tileIndex--) { TilesetAutoTileItem tileInput = autoTileInput.TileInput[tileIndex]; autoTile.TileInfo[tileIndex] = tileInput; if (tileInput.IsAutoTile) { autoTile.IsStateAvailable[(int)tileInput.Neighbours] = true; autoTile.StateToTile[(int)tileInput.Neighbours] = tileIndex; autoTile.TileInfo.Data[tileIndex].ConnectsToAutoTile = true; // Apply base tile information to the main tile dataset this.tiles.Data[tileIndex].AutoTileLayer = autoTileIndex + 1; } } // Attempt to construct missing tiles of the minimum required base set from existing tiles // by using their sub-tile quadrants individually. Use a buffer for availability checks, so // we don't base generated tiles on previously generated tiles. autoTile.IsStateAvailable.CopyTo(this.autoTileStateBuffer, 0); for (int i = 0; i < AutoTileFallbackMap.BaseConnectivityTiles.Count; i++) { TileConnection connectivity = AutoTileFallbackMap.BaseConnectivityTiles[i]; if (this.autoTileStateBuffer[(int)connectivity]) { continue; } TileConnection topLeft = FindGeneratedAutoTileBase(TileQuadrant.TopLeft, connectivity, this.autoTileStateBuffer); TileConnection topRight = FindGeneratedAutoTileBase(TileQuadrant.TopRight, connectivity, this.autoTileStateBuffer); TileConnection bottomRight = FindGeneratedAutoTileBase(TileQuadrant.BottomRight, connectivity, this.autoTileStateBuffer); TileConnection bottomLeft = FindGeneratedAutoTileBase(TileQuadrant.BottomLeft, connectivity, this.autoTileStateBuffer); // Skip cases where we can't construct a full tile if (topLeft == TileConnection.None) { continue; } if (topRight == TileConnection.None) { continue; } if (bottomRight == TileConnection.None) { continue; } if (bottomLeft == TileConnection.None) { continue; } int generatedIndex = this.ScheduleGenerateTile( autoTile.BaseTile, autoTile.StateToTile[(int)topLeft], autoTile.StateToTile[(int)topRight], autoTile.StateToTile[(int)bottomRight], autoTile.StateToTile[(int)bottomLeft]); autoTile.IsStateAvailable[(int)connectivity] = true; autoTile.StateToTile[(int)connectivity] = generatedIndex; autoTile.TileInfo.Count = MathF.Max(autoTile.TileInfo.Count, generatedIndex + 1); autoTile.TileInfo.Data[generatedIndex] = autoTileInput.TileInput[autoTile.BaseTile]; autoTile.TileInfo[generatedIndex] = new TilesetAutoTileItem { IsAutoTile = true, ConnectsToAutoTile = true, Neighbours = connectivity }; } // Fill up unavailable state mappings with the closest available match for (int stateIndex = 0; stateIndex < autoTile.IsStateAvailable.Length; stateIndex++) { if (autoTile.IsStateAvailable[stateIndex]) { continue; } IReadOnlyList <TileConnection> fallbacks = AutoTileFallbackMap.GetFallback((TileConnection)stateIndex); for (int i = 0; i < fallbacks.Count; i++) { int fallbackStateIndex = (int)fallbacks[i]; if (autoTile.IsStateAvailable[fallbackStateIndex]) { autoTile.StateToTile[stateIndex] = autoTile.StateToTile[fallbackStateIndex]; break; } } } // Add the gathered info to our local working data this.autoTiles.Add(autoTile); } }
/// <summary> /// Reads all tileset folders from the input directory, and creates autotiles from them. /// </summary> /// <param name="sourceDir"></param> /// <param name="cacheDir"></param> public static void ImportAllAutoTiles(string sourceDir, string cacheDir) { //TODO: create a version for one tile import int index = 0; string[] sizeDirs = Directory.GetDirectories(sourceDir); foreach (string sizeDir in sizeDirs) { int tileSize = GetDirSize(sizeDir); if (tileSize == 0) { continue; } string[] dirs = Directory.GetDirectories(sizeDir); for (int ii = 0; ii < dirs.Length; ii++) { string fileName = Path.GetFileName(dirs[ii]); //string[] info = fileName.Split('.'); string outputName = fileName; DiagManager.Instance.LoadMsg = "Importing " + outputName; int TOTAL_TILES = 47; try { int currentTier = 0; foreach (string tileTitle in TILE_TITLES) { AutoTileData autoTile = new AutoTileData(); AutoTileAdjacent entry = new AutoTileAdjacent(); List <TileLayer>[][] totalArray = new List <TileLayer> [48][]; for (int jj = 0; jj < TOTAL_TILES; jj++) { totalArray[jj] = new List <TileLayer> [3]; for (int kk = 0; kk < 3; kk++) { totalArray[jj][kk] = new List <TileLayer>(); } } int layerIndex = 0; while (true) { string[] layers = Directory.GetFiles(dirs[ii] + "/", tileTitle + "." + String.Format("{0:D2}", layerIndex) + ".*"); if (layers.Length == 1) { string layerName = Path.GetFileNameWithoutExtension(layers[0]); string[] layerInfo = layerName.Split('.'); using (BaseSheet tileset = BaseSheet.Import(layers[0])) { int frameLength = Convert.ToInt32(layerInfo[2]); if (frameLength == 0) { frameLength = 60; } int maxVariants = Convert.ToInt32(layerInfo[3]); int maxFrames = tileset.Width / tileSize / maxVariants; for (int jj = 0; jj < TOTAL_TILES; jj++) { for (int kk = 0; kk < maxVariants; kk++) { //go through each layer TileLayer anim = new TileLayer(); anim.FrameLength = frameLength; for (int mm = 0; mm < maxFrames; mm++) { //keep adding more tiles to the anim until end of blank spot is found if (!tileset.IsBlank((kk * maxFrames + mm) * tileSize, jj * tileSize, tileSize, tileSize)) { anim.Frames.Add(new TileFrame(new Loc(kk * maxFrames + mm, jj + currentTier * 47), outputName)); } } if (anim.Frames.Count > 0) { totalArray[jj][kk].Add(anim); } } } } } else if (layers.Length > 1) { throw new Exception("More files than expected"); } else { break; } layerIndex++; currentTier++; } if (layerIndex > 0) { ImportTileVariant(entry.Tilex00, totalArray[0]); ImportTileVariant(entry.Tilex01, totalArray[1]); ImportTileVariant(entry.Tilex02, totalArray[2]); ImportTileVariant(entry.Tilex03, totalArray[3]); ImportTileVariant(entry.Tilex13, totalArray[4]); ImportTileVariant(entry.Tilex04, totalArray[5]); ImportTileVariant(entry.Tilex05, totalArray[6]); ImportTileVariant(entry.Tilex06, totalArray[7]); ImportTileVariant(entry.Tilex26, totalArray[8]); ImportTileVariant(entry.Tilex07, totalArray[9]); ImportTileVariant(entry.Tilex17, totalArray[10]); ImportTileVariant(entry.Tilex27, totalArray[11]); ImportTileVariant(entry.Tilex37, totalArray[12]); ImportTileVariant(entry.Tilex08, totalArray[13]); ImportTileVariant(entry.Tilex09, totalArray[14]); ImportTileVariant(entry.Tilex89, totalArray[15]); ImportTileVariant(entry.Tilex0A, totalArray[16]); ImportTileVariant(entry.Tilex0B, totalArray[17]); ImportTileVariant(entry.Tilex1B, totalArray[18]); ImportTileVariant(entry.Tilex8B, totalArray[19]); ImportTileVariant(entry.Tilex9B, totalArray[20]); ImportTileVariant(entry.Tilex0C, totalArray[21]); ImportTileVariant(entry.Tilex4C, totalArray[22]); ImportTileVariant(entry.Tilex0D, totalArray[23]); ImportTileVariant(entry.Tilex4D, totalArray[24]); ImportTileVariant(entry.Tilex8D, totalArray[25]); ImportTileVariant(entry.TilexCD, totalArray[26]); ImportTileVariant(entry.Tilex0E, totalArray[27]); ImportTileVariant(entry.Tilex2E, totalArray[28]); ImportTileVariant(entry.Tilex4E, totalArray[29]); ImportTileVariant(entry.Tilex6E, totalArray[30]); ImportTileVariant(entry.Tilex0F, totalArray[31]); ImportTileVariant(entry.Tilex1F, totalArray[32]); ImportTileVariant(entry.Tilex2F, totalArray[33]); ImportTileVariant(entry.Tilex3F, totalArray[34]); ImportTileVariant(entry.Tilex4F, totalArray[35]); ImportTileVariant(entry.Tilex5F, totalArray[36]); ImportTileVariant(entry.Tilex6F, totalArray[37]); ImportTileVariant(entry.Tilex7F, totalArray[38]); ImportTileVariant(entry.Tilex8F, totalArray[39]); ImportTileVariant(entry.Tilex9F, totalArray[40]); ImportTileVariant(entry.TilexAF, totalArray[41]); ImportTileVariant(entry.TilexBF, totalArray[42]); ImportTileVariant(entry.TilexCF, totalArray[43]); ImportTileVariant(entry.TilexDF, totalArray[44]); ImportTileVariant(entry.TilexEF, totalArray[45]); ImportTileVariant(entry.TilexFF, totalArray[46]); autoTile.Tiles = entry; autoTile.Name = new LocalText(outputName + tileTitle); DataManager.SaveData(index, DataManager.DataType.AutoTile.ToString(), autoTile); Debug.WriteLine(String.Format("{0:D3}: {1}", index, autoTile.Name)); index++; } } } catch (Exception ex) { DiagManager.Instance.LogError(new Exception("Error importing " + outputName + "\n", ex)); } } } }
private Image AutoTileTexture(Sides sides, Corners corners, MTexture[,] edges, MTexture[,] innerCorners, out AutoTileData data) { if (sides == Sides.All) { data = corners switch { Corners.All ^ Corners.UpLeft => new AutoTileData(0, 0, TileType.InnerCorner), Corners.All ^ Corners.UpRight => new AutoTileData(1, 0, TileType.InnerCorner), Corners.All ^ Corners.DownLeft => new AutoTileData(0, 1, TileType.InnerCorner), Corners.All ^ Corners.DownRight => new AutoTileData(1, 1, TileType.InnerCorner), _ => new AutoTileData(1, 1, TileType.Filler) }; } else { data = sides switch { Sides.All ^ Sides.Up => new AutoTileData(1, 0, TileType.Edge), Sides.All ^ Sides.Down => new AutoTileData(1, 2, TileType.Edge), Sides.All ^ Sides.Left => new AutoTileData(0, 1, TileType.Edge), Sides.All ^ Sides.Right => new AutoTileData(2, 1, TileType.Edge), Sides.Down | Sides.Right => new AutoTileData(0, 0, TileType.Corner), Sides.Down | Sides.Left => new AutoTileData(2, 0, TileType.Corner), Sides.Up | Sides.Right => new AutoTileData(0, 2, TileType.Corner), Sides.Up | Sides.Left => new AutoTileData(2, 2, TileType.Corner), _ => new AutoTileData(1, 1, TileType.Filler) }; } return(new Image( data.Type == TileType.InnerCorner ? innerCorners[data.X, data.Y] : edges[data.X, data.Y])); }