private static Dictionary <string, ColorDefinition> ParseColors(string text) { Dictionary <string, ColorDefinition> colors = new Dictionary <string, ColorDefinition>(); // Parse each line StringReader strReader = new StringReader(text); while (true) { string line = strReader.ReadLine(); // EOF if (line == null) { break; } // Skip comment lines if (line.StartsWith('#')) { continue; } // Skip empty lines else if (string.IsNullOrWhiteSpace(line)) { continue; } // Parse space delimited values else { // Split var values = line.Split(' '); // Skip ill-formatted lines if (values.Length != 4 && values.Length != 5) { Console.WriteLine($"Color definition line \"{line}\" is ill-formatted (`NodeName R G B (A)`); Skip."); continue; } // Extract string name = values[0]; int r = int.Parse(values[1]); int g = int.Parse(values[2]); int b = int.Parse(values[3]); int a = values.Length == 5 ? int.Parse(values[4]) : byte.MaxValue; // Save colors[values[0]] = new ColorDefinition(name, r, g, b, a); } } return(colors); }
static int Main(string[] args) { // Exception handling if (args.Length == 0) { Console.WriteLine("Not enough arguments."); return(-1); } // Extract parameters string sourcePath = args[0]; string targetPath = args[1]; Console.WriteLine($"Processing files: \n Input - {sourcePath} \n Output - {targetPath}"); // Start extraction Console.WriteLine("Reading source region..."); Region region = ParseRegion(File.ReadAllLines(sourcePath)); Console.WriteLine($"{region.Nodes.Length} nodes loaded."); // Read colors Console.WriteLine("Read color definitions..."); string workingDirectory = Directory.GetCurrentDirectory(); string colorsFilePath = Path.Combine(workingDirectory, "Colors.txt"); Dictionary <string, ColorDefinition> presetColors; if (File.Exists(colorsFilePath)) { Console.WriteLine($"Custom color configuration file found ({colorsFilePath}), use custom colors."); presetColors = ParseColors(File.ReadAllText(colorsFilePath)); } else { Console.WriteLine($"No custom color configuration file is found in current working directory ({workingDirectory}), use internal colors."); presetColors = ParseColors(ReadResource("Data.Colors.txt")); } // Colors preprocessing Console.WriteLine("Preprocessing referenced colors..."); HashSet <string> uniqueNodes = region.Nodes.Select(n => n.Name).Distinct().ToHashSet(); var referencedColors = presetColors.Where(c => uniqueNodes.Contains(c.Key)).ToDictionary(p => p.Key, p => p.Value); List <ColorDefinition> colorIndices = referencedColors.Values.ToList(); Dictionary <string, int> colorIndexDict = colorIndices.Select((c, i) => new KeyValuePair <string, int>(c.Name, i)) .ToDictionary(p => p.Key, p => p.Value); // Exception handling if (referencedColors.Count() > 255) { Console.WriteLine("[Error] More than 255 colors are used."); return(-2); } // Missing color handling var missingColors = uniqueNodes.Where(n => !presetColors.ContainsKey(n)).ToList(); if (referencedColors.Count == 0) { Console.WriteLine("[Warning] None of the node types are recognized in color file, please fix the following:"); foreach (string item in uniqueNodes) { if (item != "air" && item != "ignore") { Console.WriteLine($"\t{item}"); } } } else if (missingColors.Count != 0) { Console.WriteLine($"[Warning] {missingColors.Count} colors are not fonud in color presets, please fix the following:"); foreach (var name in missingColors) { if (name == "air") { Console.WriteLine($" Node type \"{name}\" is not defined in color file - but it's also not needed; Will treat as empty (color index 0)."); } else if (name == "ignore") { Console.WriteLine(" Exported region contains node type \"ignore\"; Will treat as empty (color index 0)."); } else if (colorIndices.Count > 1) { Console.WriteLine($" Node type \"{name}\" is not defined in color file; Will use `{colorIndices[1].Name}` instead (color index 1)."); } else { Console.WriteLine($" Node type \"{name}\" is not defined in color file; Will use color index value 1 instead."); } } } // Insert an empty color index if (!colorIndexDict.ContainsKey("air")) { colorIndices.Add(new ColorDefinition("air", 0, 0, 0, 0)); colorIndexDict["air"] = colorIndices.Count - 1; } // Switch color index 0 with empty int replaceLoc = colorIndexDict["air"]; ColorDefinition emptyColor = colorIndices[replaceLoc]; ColorDefinition replaceColor = colorIndices[0]; // Switch dict colorIndexDict["air"] = 0; colorIndexDict[replaceColor.Name] = replaceLoc; // Switch color colorIndices[0] = emptyColor; colorIndices[replaceLoc] = replaceColor; // Perform conversion Console.WriteLine("Convert source to XRAW format..."); using (BinaryWriter writer = new BinaryWriter(File.Open(targetPath, FileMode.Create))) { // Header // Magic Number writer.Write(Encoding.ASCII.GetBytes("XRAW")); // Color Meta: unsigned RGBA 8-bit 256-color palette writer.Write((byte)0); writer.Write((byte)4); writer.Write((byte)8); writer.Write((byte)8); // Size: 2x2x1 - 256 Colors writer.Write((int)region.SizeX); writer.Write((int)region.SizeY); writer.Write((int)region.SizeZ); writer.Write((int)256); // Voxel Buffer Random random = new Random(); foreach (Node node in region.Nodes) { // Special handle air if (node.Name == "air") { writer.Write((byte)0); } else if (colorIndexDict.ContainsKey(node.Name)) { writer.Write((byte)colorIndexDict[node.Name]); } else { writer.Write((byte)1); // Use index 1 for absent node types } } // Color Palette Buffer for (int i = 0; i < 256; i++) { // Real colors if (i < colorIndices.Count) { writer.Write((byte)colorIndices[i].R); // R writer.Write((byte)colorIndices[i].G); // G writer.Write((byte)colorIndices[i].B); // B writer.Write((byte)colorIndices[i].A); // A } // Padding else { writer.Write((byte)i); // R writer.Write((byte)i); // G writer.Write((byte)i); // B writer.Write((byte)255); // A } } // Dispose writer.Flush(); writer.Close(); } Console.WriteLine("Conversion finished."); // Return success return(0); }