diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..8ded3ff
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,15 @@
+# Set the default behavior, in case people don't have core.autocrlf set.
+* text=auto
+* eol=crlf
+
+# Declare files that will always have CRLF line endings on checkout.
+*.sln text eol=crlf
+*.cs text eol=crlf
+*.csproj text eol=crlf
+*.manifest text eol=crlf
+*.resx text eol=crlf
+*.settings text eol=crlf
+
+# Denote all files that are truly binary and should not be modified.
+*.dll binary
+*.pdb binary
diff --git a/MapTool.sln b/MapTool.sln
index 5fef9c4..4ca1f24 100644
--- a/MapTool.sln
+++ b/MapTool.sln
@@ -1,46 +1,46 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.29911.84
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapTool", "MapTool\MapTool.csproj", "{263073ED-EAD2-493C-BC60-705DB71E2624}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{57071E6F-AEE2-4E1E-8606-831EED5483CD}"
- ProjectSection(SolutionItems) = preProject
- SharedAssemblyInfo.cs = SharedAssemblyInfo.cs
- EndProjectSection
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapTool.UI", "MapTool.UI\MapTool.UI.csproj", "{7F418E35-AB7B-4D20-88FA-08C214D3D9B1}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Debug|x86 = Debug|x86
- Release|Any CPU = Release|Any CPU
- Release|x86 = Release|x86
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {263073ED-EAD2-493C-BC60-705DB71E2624}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {263073ED-EAD2-493C-BC60-705DB71E2624}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {263073ED-EAD2-493C-BC60-705DB71E2624}.Debug|x86.ActiveCfg = Debug|x86
- {263073ED-EAD2-493C-BC60-705DB71E2624}.Debug|x86.Build.0 = Debug|x86
- {263073ED-EAD2-493C-BC60-705DB71E2624}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {263073ED-EAD2-493C-BC60-705DB71E2624}.Release|Any CPU.Build.0 = Release|Any CPU
- {263073ED-EAD2-493C-BC60-705DB71E2624}.Release|x86.ActiveCfg = Release|x86
- {263073ED-EAD2-493C-BC60-705DB71E2624}.Release|x86.Build.0 = Release|x86
- {7F418E35-AB7B-4D20-88FA-08C214D3D9B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {7F418E35-AB7B-4D20-88FA-08C214D3D9B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {7F418E35-AB7B-4D20-88FA-08C214D3D9B1}.Debug|x86.ActiveCfg = Debug|x86
- {7F418E35-AB7B-4D20-88FA-08C214D3D9B1}.Debug|x86.Build.0 = Debug|x86
- {7F418E35-AB7B-4D20-88FA-08C214D3D9B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {7F418E35-AB7B-4D20-88FA-08C214D3D9B1}.Release|Any CPU.Build.0 = Release|Any CPU
- {7F418E35-AB7B-4D20-88FA-08C214D3D9B1}.Release|x86.ActiveCfg = Release|x86
- {7F418E35-AB7B-4D20-88FA-08C214D3D9B1}.Release|x86.Build.0 = Release|x86
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {DF69AC1D-AE05-41D0-B31F-635260FE53EB}
- EndGlobalSection
-EndGlobal
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29911.84
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapTool", "MapTool\MapTool.csproj", "{263073ED-EAD2-493C-BC60-705DB71E2624}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{57071E6F-AEE2-4E1E-8606-831EED5483CD}"
+ ProjectSection(SolutionItems) = preProject
+ SharedAssemblyInfo.cs = SharedAssemblyInfo.cs
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapTool.UI", "MapTool.UI\MapTool.UI.csproj", "{7F418E35-AB7B-4D20-88FA-08C214D3D9B1}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {263073ED-EAD2-493C-BC60-705DB71E2624}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {263073ED-EAD2-493C-BC60-705DB71E2624}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {263073ED-EAD2-493C-BC60-705DB71E2624}.Debug|x86.ActiveCfg = Debug|x86
+ {263073ED-EAD2-493C-BC60-705DB71E2624}.Debug|x86.Build.0 = Debug|x86
+ {263073ED-EAD2-493C-BC60-705DB71E2624}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {263073ED-EAD2-493C-BC60-705DB71E2624}.Release|Any CPU.Build.0 = Release|Any CPU
+ {263073ED-EAD2-493C-BC60-705DB71E2624}.Release|x86.ActiveCfg = Release|x86
+ {263073ED-EAD2-493C-BC60-705DB71E2624}.Release|x86.Build.0 = Release|x86
+ {7F418E35-AB7B-4D20-88FA-08C214D3D9B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7F418E35-AB7B-4D20-88FA-08C214D3D9B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7F418E35-AB7B-4D20-88FA-08C214D3D9B1}.Debug|x86.ActiveCfg = Debug|x86
+ {7F418E35-AB7B-4D20-88FA-08C214D3D9B1}.Debug|x86.Build.0 = Debug|x86
+ {7F418E35-AB7B-4D20-88FA-08C214D3D9B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7F418E35-AB7B-4D20-88FA-08C214D3D9B1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7F418E35-AB7B-4D20-88FA-08C214D3D9B1}.Release|x86.ActiveCfg = Release|x86
+ {7F418E35-AB7B-4D20-88FA-08C214D3D9B1}.Release|x86.Build.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {DF69AC1D-AE05-41D0-B31F-635260FE53EB}
+ EndGlobalSection
+EndGlobal
diff --git a/MapTool/MapTool.cs b/MapTool/MapTool.cs
index a148302..6560bb2 100644
--- a/MapTool/MapTool.cs
+++ b/MapTool/MapTool.cs
@@ -1,1715 +1,1715 @@
-/*
- * Copyright 2017-2020 by Starkku
- * This file is part of MapTool, which is free software. It is made
- * available to you under the terms of the GNU General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version. For more
- * information, see COPYING.
- */
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.IO;
-using System.Text.RegularExpressions;
-using System.Globalization;
-using MapTool.DataStructures;
-using StarkkuUtils.FileTypes;
-using StarkkuUtils.Utilities;
-using StarkkuUtils.ExtensionMethods;
-
-namespace MapTool
-{
- ///
- /// Map tile data sort mode.
- ///
- public enum IsoMapPack5SortMode { NotDefined, XLevelTileIndex, XTileIndexLevel, TileIndexXLevel, LevelXTileIndex, X, Level, TileIndex, SubTileIndex, IceGrowth, Y }
-
- ///
- /// Map file modifier tool class.
- ///
- public class MapTool
- {
- #region public_properties
-
- ///
- /// Has tool been initialized or not.
- ///
- public bool Initialized { get; private set; }
-
- ///
- /// Has map file been altered or not.
- ///
- public bool MapAltered { get; private set; }
-
- ///
- /// Map input filename.
- ///
- public string FilenameInput { get; private set; }
-
- ///
- /// Map output filename.
- ///
- public string FilenameOutput { get; private set; }
-
- #endregion
-
- #region private_fields
-
- ///
- /// Map file.
- ///
- private readonly INIFile mapINI;
-
- ///
- /// Map theater.
- ///
- private readonly string mapTheater = null;
-
- ///
- /// Map full width.
- ///
- private readonly int mapWidth;
-
- ///
- /// Map full height.
- ///
- private readonly int mapHeight;
-
- ///
- /// Map tile data.
- ///
- private List isoMapPack5 = new List();
-
- ///
- /// Map overlay ID data.
- ///
- private byte[] overlayPack = null;
-
- ///
- /// Map overlay frame data.
- ///
- private byte[] overlayDataPack = null;
-
- ///
- /// Look-up table for map coordinate (X,Y) validity.
- ///
- private bool[,] coordinateValidityLUT;
-
- ///
- /// Conversion profile INI file.
- ///
- private readonly INIFile conversionProfileINI;
-
- ///
- /// Conversion profile applicable theaters.
- ///
- private readonly List applicableTheaters = new List();
-
- ///
- /// Conversion profile theater-specific global tile offsets.
- ///
- private readonly Dictionary> theaterTileOffsets = new Dictionary>();
-
- ///
- /// Conversion profile new theater.
- ///
- private readonly string newTheater = null;
-
- ///
- /// Conversion profile tile rules.
- ///
- private readonly List tileRules = new List();
-
- ///
- /// Conversion profile overlay rules.
- ///
- private readonly List overlayRules = new List();
-
- ///
- /// Conversion profile object rules.
- ///
- private readonly List objectRules = new List();
-
- ///
- /// // Conversion profile section rules.
- ///
- private readonly List sectionRules = new List();
-
- ///
- /// Optimize output map file or not.
- ///
- private readonly bool useMapOptimize = false;
-
- ///
- /// Compress output map file or not.
- ///
- private readonly bool useMapCompress = false;
-
- ///
- /// Delete objects outside visible map bounds or not.
- ///
- private readonly bool deleteObjectsOutsideMapBounds = false;
-
- ///
- /// Remove clear tiles at level 0 from map tile data (they will be filled in by the game) or not.
- ///
- private readonly bool removeLevel0ClearTiles = false;
-
- ///
- /// Fix tunnel data or not.
- ///
- private readonly bool fixTunnels = false;
-
- ///
- /// Map tile data sort mode.
- ///
- private readonly IsoMapPack5SortMode isoMapPack5SortBy = IsoMapPack5SortMode.NotDefined;
-
- ///
- /// Theater configuration file.
- ///
- private readonly INIFile theaterConfigINI;
-
- ///
- /// OverlayPack/DataPack length.
- ///
- private const int OVERLAY_DATA_LENGTH = 262144;
-
- ///
- /// Random number generator.
- ///
- private readonly Random random = new Random();
-
- #endregion
-
- ///
- /// Initializes a new instance of MapTool.
- ///
- /// Input file name.
- /// Output file name.
- /// Conversion profile file name.
- /// If set, it is assumed that this instance of MapTool is initialized for listing theater data rather than processing maps.
- public MapTool(string inputFile, string outputFile, string fileConfig, bool listTheaterData)
- {
- Initialized = false;
- MapAltered = false;
- FilenameInput = inputFile;
- FilenameOutput = outputFile;
-
- if (listTheaterData && !string.IsNullOrEmpty(FilenameInput))
- {
- theaterConfigINI = new INIFile(FilenameInput);
- }
-
- else if (!string.IsNullOrEmpty(FilenameInput) && !string.IsNullOrEmpty(FilenameOutput))
- {
-
- Logger.Info("Reading map file '" + inputFile + "'.");
- mapINI = new INIFile(inputFile);
-
- string[] size = mapINI.GetKey("Map", "Size", "").Split(',');
- mapWidth = Conversion.GetIntFromString(size[2], -1);
- mapHeight = Conversion.GetIntFromString(size[3], -1);
-
- mapTheater = mapINI.GetKey("Map", "Theater", null);
- if (mapTheater != null)
- mapTheater = mapTheater.ToUpper();
-
- Logger.Info("Parsing conversion profile file.");
- conversionProfileINI = new INIFile(fileConfig);
- string[] sections = conversionProfileINI.GetSections();
- if (sections == null || sections.Length < 1)
- {
- Logger.Error("Conversion profile file is empty.");
- Initialized = false;
- return;
- }
-
- string include = conversionProfileINI.GetKey("ProfileData", "IncludeFiles", null);
- if (!string.IsNullOrEmpty(include))
- {
- string[] includeFiles = include.Split(',');
- string basedir = Path.GetDirectoryName(fileConfig);
- foreach (string filename in includeFiles)
- {
- if (File.Exists(basedir + "\\" + filename))
- {
- INIFile includeIni = new INIFile(basedir + "\\" + filename);
- Logger.Info("Merging included file '" + filename + "' to conversion profile.");
- conversionProfileINI.Merge(includeIni);
- }
- }
- }
-
- // Parse general options.
- useMapOptimize = Conversion.GetBoolFromString(conversionProfileINI.GetKey("ProfileData", "ApplyMapOptimization", "false"), false);
- useMapCompress = Conversion.GetBoolFromString(conversionProfileINI.GetKey("ProfileData", "ApplyMapCompress", "false"), false);
- deleteObjectsOutsideMapBounds = Conversion.GetBoolFromString(conversionProfileINI.GetKey("ProfileData", "DeleteObjectsOutsideMapBounds",
- "false"), false);
- fixTunnels = Conversion.GetBoolFromString(conversionProfileINI.GetKey("ProfileData", "FixTunnels", "false"), false);
-
- // Parse tile data options.
- string sortMode = conversionProfileINI.GetKey("IsoMapPack5", "SortBy", null);
- if (sortMode != null)
- {
- Enum.TryParse(sortMode.Replace("_", ""), true, out isoMapPack5SortBy);
- }
- removeLevel0ClearTiles = Conversion.GetBoolFromString(conversionProfileINI.GetKey("IsoMapPack5", "RemoveLevel0ClearTiles", "false"), false);
-
- // Parse theater rules.
- newTheater = conversionProfileINI.GetKey("TheaterRules", "NewTheater", null);
- if (newTheater != null)
- newTheater = newTheater.ToUpper();
-
- string[] applicableTheaters = conversionProfileINI.GetKey("TheaterRules", "ApplicableTheaters", "").Split(',');
- if (applicableTheaters != null)
- {
- for (int i = 0; i < applicableTheaters.Length; i++)
- {
- string theater = applicableTheaters[i].Trim().ToUpper();
- if (theater == "")
- continue;
- this.applicableTheaters.Add(theater);
- }
- }
-
- if (this.applicableTheaters.Count < 1)
- this.applicableTheaters.AddRange(new string[] { "TEMPERATE", "SNOW", "URBAN", "DESERT", "LUNAR", "NEWURBAN" });
-
- // Parse theater-specific global tile offsets.
- string[] theaterOffsetKeys = conversionProfileINI.GetKeys("TheaterTileOffsets");
- if (theaterOffsetKeys != null)
- {
- foreach (string key in theaterOffsetKeys)
- {
- int newOffset = int.MinValue;
- string[] values = conversionProfileINI.GetKey("TheaterTileOffsets", key, "").Split(',');
- int originalOffset;
- if (values.Length < 1)
- continue;
- else if (values.Length < 2)
- {
- originalOffset = Conversion.GetIntFromString(values[0], 0);
- }
- else
- {
- originalOffset = Conversion.GetIntFromString(values[0], 0);
- newOffset = Conversion.GetIntFromString(values[1], int.MinValue);
- }
- theaterTileOffsets.Add(key.ToUpper(), new Tuple(originalOffset, newOffset));
- }
- }
-
- // Parse conversion rules.
- string[] tilerules = conversionProfileINI.GetKeys("TileRules");
- string[] overlayrules = conversionProfileINI.GetKeys("OverlayRules");
- string[] objectrules = conversionProfileINI.GetKeys("ObjectRules");
- string[] sectionrules = MergeKeyValuePairs(conversionProfileINI.GetKeyValuePairs("SectionRules"));
-
- // Allow saving map without any other changes if either of these are set and ApplicableTheaters allows it.
- bool allowSaving = (useMapCompress || useMapOptimize || deleteObjectsOutsideMapBounds || fixTunnels ||
- isoMapPack5SortBy != IsoMapPack5SortMode.NotDefined) && IsCurrentTheaterAllowed();
-
- if (!allowSaving && tilerules == null && overlayrules == null && objectrules == null && sectionrules == null &&
- string.IsNullOrEmpty(newTheater))
- {
- Logger.Error("No conversion rules to apply in the conversion profile file.");
- Initialized = false;
- return;
- }
-
- ParseConversionRules(tilerules, tileRules);
- ParseConversionRules(overlayrules, overlayRules);
- ParseConversionRules(objectrules, objectRules);
- ParseConversionRules(sectionrules, sectionRules);
- }
-
- Initialized = true;
- }
-
- ///
- /// Saves the map file.
- ///
- public void Save()
- {
- if (deleteObjectsOutsideMapBounds)
- {
- if (mapWidth > 0 && mapHeight > 0)
- {
- Logger.Info("DeleteObjectsOutsideMapBounds set: Objects & overlays outside map bounds will be deleted.");
- CalculateCoordinateValidity();
- DeleteObjectsOutsideBounds();
- DeleteOverlaysOutsideBounds();
- }
- else
- Logger.Warn("DeleteObjectsOutsideMapBounds set but because map has invalid Size value set, no objects or overlays will be deleted.");
- }
- if (useMapOptimize)
- {
- Logger.Info("ApplyMapOptimization set: Saved map will have map section order optimizations applied.");
- mapINI.MoveSectionToFirst("Basic");
- mapINI.MoveSectionToFirst("MultiplayerDialogSettings");
- mapINI.MoveSectionToLast("Digest");
- MapAltered = true;
- }
- if (fixTunnels)
- {
- Logger.Info("FixTunnels set: Saved map will have [Tubes] section fixed to remove errors caused by map editor.");
- FixTubesSection();
- }
- if (useMapCompress)
- Logger.Info("ApplyMapCompress set: Saved map will have no unnecessary whitespaces or comments.");
-
- string error;
- if (MapAltered || useMapCompress)
- error = mapINI.Save(FilenameOutput, !useMapCompress, !useMapCompress);
- else
- {
- Logger.Info("Skipping saving map file as no changes have been made to it.");
- return;
- }
-
- if (string.IsNullOrEmpty(error))
- Logger.Info("Map file successfully saved to '" + FilenameOutput + "'.");
- else
- {
- Logger.Error("Error encountered saving map file to '" + FilenameOutput + "'.");
- Logger.Error("Message: " + error);
- }
- }
-
- ///
- /// Checks if theater name is valid.
- ///
- /// Theater name.
- /// True if a valid theater name, otherwise false.
- public static bool IsValidTheaterName(string theaterName)
- {
- if (theaterName == "TEMPERATE" || theaterName == "SNOW" || theaterName == "LUNAR" || theaterName == "DESERT" ||
- theaterName == "URBAN" || theaterName == "NEWURBAN")
- return true;
-
- return false;
- }
-
- ///
- /// Checks if the currently set map theater exists in current list of theaters the map tool is allowed to process.
- ///
- /// True if map theater exists in applicable theaters, otherwise false.
- private bool IsCurrentTheaterAllowed()
- {
- if (applicableTheaters == null || mapTheater == null || !applicableTheaters.Contains(mapTheater))
- return false;
-
- return true;
- }
-
- #region map_pack_handling
-
- ///
- /// Parses IsoMapPack5 section of the map file.
- ///
- /// Error message if something went wrong, otherwise null.
- private string ParseMapPack()
- {
- int cellCount;
- byte[] isoMapPack;
-
- if (mapHeight < 1 || mapWidth < 1)
- return ("Map Size is invalid.");
-
- cellCount = (mapWidth * 2 - 1) * mapHeight;
- int lzoPackSize = cellCount * 11 + 4;
- isoMapPack = new byte[lzoPackSize];
-
- // Fill up and filter later
- int j = 0;
- for (int i = 0; i < cellCount; i++)
- {
- isoMapPack[j] = 0x88;
- isoMapPack[j + 1] = 0x40;
- isoMapPack[j + 2] = 0x88;
- isoMapPack[j + 3] = 0x40;
- j += 11;
- }
-
- string errorMessage = ParseEncodedMapSectionData("IsoMapPack5", ref isoMapPack);
- if (errorMessage != null)
- return errorMessage;
-
- int bytesRead = 0;
- for (int i = 0; i < cellCount; i++)
- {
- ushort x = BitConverter.ToUInt16(isoMapPack, bytesRead);
- bytesRead += 2;
- ushort y = BitConverter.ToUInt16(isoMapPack, bytesRead);
- bytesRead += 2;
- int tileNum = BitConverter.ToInt32(isoMapPack, bytesRead);
- bytesRead += 4;
- byte subTile = isoMapPack[bytesRead++];
- byte level = isoMapPack[bytesRead++];
- byte iceGrowth = isoMapPack[bytesRead++];
- if (x > 0 && y > 0 && x <= 16384 && y <= 16384)
- {
- isoMapPack5.Add(new MapTile((short)x, (short)y, tileNum, subTile, level, iceGrowth));
- }
- }
- return null;
- }
-
- ///
- /// Saves IsoMapPack5 section of the map file.
- ///
- private void SaveMapPack()
- {
- if (!Initialized || isoMapPack5.Count < 1)
- return;
-
- byte[] isoMapPack = new byte[isoMapPack5.Count * 11 + 4];
- int i = 0;
-
- foreach (MapTile t in isoMapPack5)
- {
- byte[] x = BitConverter.GetBytes(t.X);
- byte[] y = BitConverter.GetBytes(t.Y);
- byte[] tilei = BitConverter.GetBytes(t.TileIndex);
- isoMapPack[i] = x[0];
- isoMapPack[i + 1] = x[1];
- isoMapPack[i + 2] = y[0];
- isoMapPack[i + 3] = y[1];
- isoMapPack[i + 4] = tilei[0];
- isoMapPack[i + 5] = tilei[1];
- isoMapPack[i + 6] = tilei[2];
- isoMapPack[i + 7] = tilei[3];
- isoMapPack[i + 8] = t.SubTileIndex;
- isoMapPack[i + 9] = t.Level;
- isoMapPack[i + 10] = t.IceGrowth;
- i += 11;
- }
-
- byte[] lzo = MapPackHelper.Compress(isoMapPack);
- string data = Convert.ToBase64String(lzo, Base64FormattingOptions.None);
- ReplacePackedSectionData("IsoMapPack5", data);
- }
-
- ///
- /// Parses Overlay(Data)Pack sections of the map file.
- ///
- private void ParseOverlayPacks()
- {
- overlayPack = new byte[1 << 18];
- string errorMessage = ParseEncodedMapSectionData("OverlayPack", ref overlayPack, true);
- if (errorMessage != null)
- Logger.Warn(errorMessage);
-
- overlayDataPack = new byte[1 << 18];
- errorMessage = ParseEncodedMapSectionData("OverlayDataPack", ref overlayDataPack, true);
- if (errorMessage != null)
- Logger.Warn(errorMessage);
- }
-
-
- ///
- /// Saves Overlay(Data)Pack sections of the map file.
- ///
- private void SaveOverlayPacks()
- {
- string base64_overlayPack = Convert.ToBase64String(MapPackHelper.Compress(overlayPack, true), Base64FormattingOptions.None);
- string base64_overlayDataPack = Convert.ToBase64String(MapPackHelper.Compress(overlayDataPack, true), Base64FormattingOptions.None);
- ReplacePackedSectionData("OverlayPack", base64_overlayPack);
- ReplacePackedSectionData("OverlayDataPack", base64_overlayDataPack);
- }
-
- ///
- /// Parses and decompresses Base64-encoded and compressed data from specified section of map file.
- ///
- /// Name of the section.
- /// Array to put the decompressed data to.
- /// If set to true, treat data as LCW-compressed instead of LZO.
- /// Error message if something went wrong, otherwise null.
- private string ParseEncodedMapSectionData(string sectionName, ref byte[] outputData, bool useLCW = false)
- {
- Logger.Info("Parsing " + sectionName + ".");
- string[] values = mapINI.GetValues(sectionName);
- if (values == null || values.Length < 1)
- return sectionName + " data is empty.";
- byte[] compressedData;
- try
- {
- compressedData = Convert.FromBase64String(string.Join("", values));
- }
- catch (Exception)
- {
- return sectionName + " is malformed.";
- }
- if (MapPackHelper.Decompress(compressedData, outputData, out _, useLCW))
- {
- return null;
- }
- else
- {
- return sectionName + " data is invalid or corrupted.";
- }
- }
-
- ///
- /// Replaces contents of a Base64-encoded section of map file.
- ///
- /// Name of the section.
- /// Contents to replace the existing contents with.
- private void ReplacePackedSectionData(string sectionName, string data)
- {
- int lx = 70;
- List lines = new List();
- for (int x = 0; x < data.Length; x += lx)
- {
- lines.Add(data.Substring(x, Math.Min(lx, data.Length - x)));
- }
- mapINI.ReplaceSectionWithStrings(sectionName, lines);
- }
-
- #endregion
-
- #region conversion_rule_parsing
-
- ///
- /// Parses conversion profile information for tile conversion rules.
- ///
- private void ParseConversionRules(string[] ruleStrings, List currentRules)
- {
- if (ruleStrings == null || ruleStrings.Length < 1 || currentRules == null) return;
- currentRules.Clear();
-
- foreach (string ruleString in ruleStrings)
- {
- string ruleStringFiltered = GetCoordinateFilters(ruleString, out int coordFilterX, out int coordFilterY);
-
- string[] values = ruleStringFiltered.Split('|');
-
- if (values.Length < 2)
- continue;
-
- ParseValueRange(values[0], out int oldValueStart, out int oldValueEnd, out bool oldValueIsRange, out _);
- ParseValueRange(values[1], out int newValueStart, out int newValueEnd, out bool newValueIsRange, out bool newValueIsRandom, true);
-
- int heightOverride = -1;
- int subTileOverride = -1;
- int iceGrowthOverride = -1;
- if (values.Length >= 3 && values[2] != null && !values[2].Equals("*", StringComparison.InvariantCultureIgnoreCase))
- {
- heightOverride = Conversion.GetIntFromString(values[2], -1);
- }
- if (values.Length >= 4 && values[3] != null && !values[3].Equals("*", StringComparison.InvariantCultureIgnoreCase))
- {
- subTileOverride = Conversion.GetIntFromString(values[3], -1);
- }
- if (values.Length >= 5 && values[4] != null && !values[4].Equals("*", StringComparison.InvariantCultureIgnoreCase))
- {
- iceGrowthOverride = Conversion.GetIntFromString(values[4], -1);
- }
-
- if (oldValueIsRange && !newValueIsRange)
- {
- int diff = newValueStart + (oldValueEnd - newValueStart);
- currentRules.Add(new TileConversionRule(oldValueStart, newValueStart, oldValueEnd, diff, newValueIsRandom, heightOverride, subTileOverride, iceGrowthOverride, coordFilterX, coordFilterY));
- }
- else if (!oldValueIsRange && newValueIsRange)
- {
- currentRules.Add(new TileConversionRule(oldValueStart, newValueStart, oldValueStart, newValueEnd, newValueIsRandom, heightOverride, subTileOverride, iceGrowthOverride, coordFilterX, coordFilterY));
- }
- else
- {
- currentRules.Add(new TileConversionRule(oldValueStart, newValueStart, oldValueEnd, newValueEnd, newValueIsRandom, heightOverride, subTileOverride, iceGrowthOverride, coordFilterX, coordFilterY));
- }
- }
- }
-
- ///
- /// Parses conversion profile information for overlay conversion rules.
- ///
- private void ParseConversionRules(string[] ruleStrings, List currentRules)
- {
- if (ruleStrings == null || ruleStrings.Length < 1 || currentRules == null) return;
- currentRules.Clear();
-
- foreach (string ruleString in ruleStrings)
- {
- string ruleStringFiltered = GetCoordinateFilters(ruleString, out int coordFilterX, out int coordFilterY);
-
- string[] values = ruleStringFiltered.Split('|');
-
- if (values.Length < 2)
- continue;
-
- ParseValueRange(values[0], out int oldValueStart, out int oldValueEnd, out bool oldValueIsRange, out _);
- ParseValueRange(values[1], out int newValueStart, out int newValueEnd, out bool newValueIsRange, out bool newValueIsRandom, true);
- ParseValueRange(values.Length >= 4 ? values[2] : "", out int frameOldValueStart, out int frameOldValueEnd, out bool frameOldValueIsRange, out _);
- ParseValueRange(values.Length >= 4 ? values[3] : "", out int frameNewValueStart, out int frameNewValueEnd, out bool frameNewValueIsRange, out bool frameNewValueIsRandom, true);
-
- int frameOldEndIndex = frameOldValueEnd;
- int frameNewEndIndex = frameNewValueEnd;
-
- if (frameOldValueIsRange && !frameNewValueIsRange)
- {
- frameOldEndIndex = frameOldValueEnd;
- frameNewEndIndex = frameNewValueStart + (frameOldValueEnd - frameNewValueStart);
- }
- else if (!frameOldValueIsRange && frameNewValueIsRange)
- {
- frameOldEndIndex = frameOldValueStart;
- frameNewEndIndex = frameNewValueEnd;
- }
-
- if (oldValueIsRange && !newValueIsRange)
- {
- int diff = newValueStart + (oldValueEnd - newValueStart);
- currentRules.Add(new OverlayConversionRule(oldValueStart, newValueStart, oldValueEnd, diff, newValueIsRandom,
- frameOldValueStart, frameNewValueStart, frameOldEndIndex, frameNewEndIndex, frameNewValueIsRandom, coordFilterX, coordFilterY));
- }
- else if (!oldValueIsRange && newValueIsRange)
- {
- currentRules.Add(new OverlayConversionRule(oldValueStart, newValueStart, oldValueStart, newValueEnd, newValueIsRandom,
- frameOldValueStart, frameNewValueStart, frameOldEndIndex, frameNewEndIndex, frameNewValueIsRandom, coordFilterX, coordFilterY));
- }
- else
- {
- currentRules.Add(new OverlayConversionRule(oldValueStart, newValueStart, oldValueEnd, newValueEnd, newValueIsRandom,
- frameOldValueStart, frameNewValueStart, frameOldEndIndex, frameNewEndIndex, frameNewValueIsRandom, coordFilterX, coordFilterY));
- }
- }
- }
-
- ///
- /// Parses a value range for byte ID-type conversion rules from string.
- ///
- /// String from which the value will be parsed.
- /// Will be set to the first value of value range.
- /// Will be set to the second value of value range.
- /// Will be set to true if value range truly is a range of values, false otherwise.
- /// Will be set to true if value range is a randomized range, false otherwise.
- /// If set to true, allows parsing of random value ranges.
- /// True is value range was completely parsed, false otherwise.
- private bool ParseValueRange(string value, out int valueA, out int valueB, out bool isRange, out bool isRandom, bool allowRandomRange = false)
- {
- valueB = -1;
- isRange = false;
- isRandom = false;
-
- if (allowRandomRange && value.Contains('~'))
- {
- isRange = true;
- isRandom = true;
- string[] parts = value.Split('~');
- valueA = Conversion.GetIntFromString(parts[0], -1);
- valueB = Conversion.GetIntFromString(parts[1], -1);
- if (valueA < 0 || valueB < 0)
- return false;
- }
- else if (value.Contains('-'))
- {
- isRange = true;
- string[] parts = value.Split('-');
- valueA = Conversion.GetIntFromString(parts[0], -1);
- valueB = Conversion.GetIntFromString(parts[1], -1);
- if (valueA < 0 || valueB < 0)
- return false;
- }
- else
- {
- valueA = Conversion.GetIntFromString(value, -1);
- if (valueA < 0)
- return false;
- }
-
- return true;
- }
-
- ///
- /// Parses conversion profile information for general object rules.
- ///
- private void ParseConversionRules(string[] ruleStrings, List currentRules)
- {
- if (ruleStrings == null || ruleStrings.Length < 1 || currentRules == null)
- return;
-
- currentRules.Clear();
-
- foreach (string ruleString in ruleStrings)
- {
- string ruleStringFiltered = GetCoordinateFilters(ruleString, out int coordFilterX, out int coordFilterY);
-
- string[] values = ruleStringFiltered.Split('|');
- if (values.Length == 1)
- currentRules.Add(new ObjectConversionRule(values[0], null, coordFilterX, coordFilterY));
- else if (values.Length >= 2)
- currentRules.Add(new ObjectConversionRule(values[0], values[1], coordFilterX, coordFilterY));
- }
- }
-
- ///
- /// Parses conversion profile information for map file section rules.
- ///
- private void ParseConversionRules(string[] ruleStrings, List currentRules)
- {
- if (ruleStrings == null || ruleStrings.Length < 1 || currentRules == null)
- return;
-
- currentRules.Clear();
-
- foreach (string ruleString in ruleStrings)
- {
- if (ruleString == null || ruleString.Length < 1)
- continue;
- string[] values = ruleString.Split('|');
- string newSection = "";
- string originalKey = "";
- string newKey = "";
- string newValue = "";
- if (values.Length > 0)
- {
- if (values[0].StartsWith("="))
- values[0] = values[0].Substring(1, values[0].Length - 1);
- string[] sec = values[0].Split('=');
- if (sec == null || sec.Length < 1)
- continue;
- string originalSection = sec[0];
- if (sec.Length == 1 && values[0].Contains('=') || sec.Length > 1 && values[0].Contains('=') &&
- string.IsNullOrEmpty(sec[1]))
- newSection = null;
- else if (sec.Length > 1)
- newSection = sec[1];
- if (values.Length > 1)
- {
- string[] key = values[1].Split('=');
- if (key != null && key.Length > 0)
- {
- originalKey = key[0];
- if (key.Length == 1 && values[1].Contains('=') || key.Length > 1 && values[1].Contains('=') &&
- string.IsNullOrEmpty(key[1])) newKey = null;
- else if (key.Length > 1) newKey = key[1];
- }
- if (values.Length > 2)
- {
- if (!(values[2] == null || values[2] == "" || values[2] == "*"))
- {
- if (values[2].Contains("$GETVAL("))
- {
- string[] valdata = Regex.Match(values[2], @"\$GETVAL\(([^)]*)\)").Groups[1].Value.Split(',');
- if (valdata.Length > 1)
- {
- string newval = mapINI.GetKey(valdata[0], valdata[1], null);
- if (newval != null)
- {
- newValue = newval;
- if (valdata.Length > 3)
- {
- bool useDouble = true;
- if (valdata.Length > 4)
- useDouble = Conversion.GetBoolFromString(valdata[4], true);
- newValue = ApplyArithmeticOp(newValue, valdata[2], valdata[3], useDouble);
- }
- }
-
- }
- }
- else
- newValue = values[2];
- }
- }
- }
- currentRules.Add(new SectionConversionRule(originalSection, newSection, originalKey, newKey, newValue));
- }
- }
- }
-
- private string ApplyArithmeticOp(string value, string opType, string operand, bool useDouble)
- {
- bool valueAvailable = double.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out double valueDouble);
- bool operandAvailable = double.TryParse(operand, NumberStyles.Number, CultureInfo.InvariantCulture, out double operandDouble);
-
- if (valueAvailable)
- {
- switch (opType)
- {
- case "+":
- valueDouble += operandDouble;
- break;
- case "-":
- valueDouble -= operandDouble;
- break;
- case "*":
- if (!operandAvailable)
- operandDouble = 1;
- valueDouble = valueDouble * operandDouble;
- break;
- case "/":
- if (operandDouble == 0)
- operandDouble = 1;
- valueDouble = valueDouble / operandDouble;
- break;
- }
- if (useDouble)
- return valueDouble.ToString(CultureInfo.InvariantCulture);
- else
- return ((int)valueDouble).ToString();
- }
- return value;
- }
-
- ///
- /// Gets coordinate filters from a conversion rule string and returns it without the filter part.
- ///
- /// Rule string.
- /// Filter coordinate X.
- /// Filter coordinate Y.
- /// Rule string without coordinate filters.
- private string GetCoordinateFilters(string ruleString, out int coordFilterX, out int coordFilterY)
- {
- string ruleStringFiltered = ruleString;
- coordFilterX = -1;
- coordFilterY = -1;
-
- if (ruleStringFiltered.StartsWith("(") && ruleStringFiltered.Contains(")"))
- {
- string coordString = ruleStringFiltered.Substring(1, ruleStringFiltered.IndexOf(")") - 1);
- string[] coords = coordString.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
- if (coords.Length >= 2)
- {
- coordFilterX = Conversion.GetIntFromString(coords[0].Replace("*", -1 + ""), -1);
- coordFilterY = Conversion.GetIntFromString(coords[1].Replace("*", -1 + ""), -1);
- }
- ruleStringFiltered = ruleStringFiltered.ReplaceFirst("(" + coordString + ")", "");
- }
- return ruleStringFiltered;
- }
-
- #endregion
-
- #region conversion
-
- ///
- /// Changes theater declaration of current map based on conversion profile.
- ///
- public void ConvertTheaterData()
- {
- if (!Initialized || string.IsNullOrEmpty(newTheater))
- return;
- else if (!IsCurrentTheaterAllowed())
- {
- Logger.Warn("Skipping altering theater data - ApplicableTheaters does not contain entry matching map theater.");
- return;
- }
-
- Logger.Info("Attempting to modify theater data of the map file.");
-
- if (IsValidTheaterName(newTheater))
- {
- mapINI.SetKey("Map", "Theater", newTheater);
- Logger.Info("Map theater declaration changed from '" + mapTheater + "' to '" + newTheater + "'.");
- MapAltered = true;
- }
- }
-
- ///
- /// Changes tile data of current map based on conversion profile.
- ///
- public void ConvertTileData()
- {
- if (!Initialized)
- return;
-
- if (tileRules.Count > 0 || removeLevel0ClearTiles || isoMapPack5SortBy != IsoMapPack5SortMode.NotDefined)
- {
- string errorMessage = ParseMapPack();
- if (errorMessage != null)
- {
- Logger.Error("Error encountered parsing tile data: Message: " + errorMessage);
- return;
- }
- }
-
- if (isoMapPack5.Count < 1)
- return;
-
- else if (!IsCurrentTheaterAllowed())
- {
- Logger.Warn("Skipping altering tile data - ApplicableTheaters does not contain entry matching map theater.");
- return;
- }
-
- bool tileDataAltered = ApplyTileConversionRules();
- tileDataAltered |= RemoveLevel0ClearTiles() || tileDataAltered;
- tileDataAltered |= SortIsoMapPack() || tileDataAltered;
-
- if (tileDataAltered)
- SaveMapPack();
-
- MapAltered |= tileDataAltered;
- }
-
- ///
- /// Processes tile data conversion rules.
- ///
- /// Returns true if tile data was changed, false if not.
- private bool ApplyTileConversionRules()
- {
- if (tileRules == null || tileRules.Count < 1)
- return false;
-
- Logger.Info("Attempting to apply TileRules on map tile data.");
-
- int originalOffset = 0, newOffset = 0;
- bool tileDataAltered = false;
-
- if (theaterTileOffsets.ContainsKey(mapTheater.ToUpper()))
- {
- originalOffset = theaterTileOffsets[mapTheater.ToUpper()].Item1;
- newOffset = theaterTileOffsets[mapTheater.ToUpper()].Item2;
- if (newOffset == int.MinValue)
- newOffset = originalOffset;
- if (originalOffset != 0 && newOffset != 0)
- Logger.Info("Global tile rule offsets for theater " + mapTheater.ToUpper() + ": " + originalOffset + " (original), " + newOffset + " (new)");
- }
-
- foreach (MapTile tile in isoMapPack5)
- {
- if (tile.TileIndex < 0 || tile.TileIndex == 65535)
- tile.TileIndex = 0;
-
- foreach (TileConversionRule rule in tileRules)
- {
- if (rule.CoordinateFilterX > -1 && rule.CoordinateFilterX != tile.X ||
- rule.CoordinateFilterY > -1 && rule.CoordinateFilterY != tile.Y)
- continue;
-
- int ruleOriginalStartIndex = rule.OriginalStartIndex + originalOffset;
- int ruleOriginalEndIndex = rule.OriginalEndIndex + originalOffset;
- int ruleNewStartIndex = rule.NewStartIndex + newOffset;
- int ruleNewEndIndex = rule.NewEndIndex + newOffset;
-
- if (tile.TileIndex >= ruleOriginalStartIndex && tile.TileIndex <= ruleOriginalEndIndex)
- {
- if (rule.HeightOverride > -1)
- {
- byte height = (byte)Math.Min(rule.HeightOverride, 14);
- if (tile.Level != height)
- {
- Logger.Debug("TileRules: Tile index " + tile.TileIndex + " at X: " + tile.X + ", Y:" + tile.Y + " - height changed from " + tile.Level + " to " + height + ".");
- tile.Level = height;
- }
- }
- if (rule.SubIndexOverride > -1)
- {
- byte subtileIndex = (byte)Math.Min(rule.SubIndexOverride, 255);
- if (tile.SubTileIndex != subtileIndex)
- {
- Logger.Debug("TileRules: Tile index " + tile.TileIndex + " at X: " + tile.X + ", Y:" + tile.Y + " - sub tile index changed from " + tile.SubTileIndex + " to " + subtileIndex + ".");
- tile.SubTileIndex = subtileIndex;
- }
- }
- if (rule.IceGrowthOverride > -1)
- {
- byte iceGrowth = Convert.ToByte(Convert.ToBoolean(rule.IceGrowthOverride));
- if (tile.IceGrowth != iceGrowth)
- {
- Logger.Debug("TileRules: Tile index " + tile.TileIndex + " at X: " + tile.X + ", Y:" + tile.Y + " - ice growth flag changed from " + tile.IceGrowth + " to " + iceGrowth + ".");
- tile.IceGrowth = iceGrowth;
- }
- }
-
- if (rule.IsRandomizer)
- {
- int newindex = random.Next(ruleNewStartIndex, ruleNewEndIndex);
- Logger.Debug("TileRules: Tile rule random range: [" + ruleNewStartIndex + "-" + ruleNewEndIndex + "]. Picked: " + newindex);
- if (newindex != tile.TileIndex)
- {
- Logger.Debug("TileRules: Tile index " + tile.TileIndex + " at X: " + tile.X + ", Y:" + tile.Y + " - index changed to " + newindex);
- tile.TileIndex = newindex;
- tileDataAltered = true;
- }
- break;
- }
- else if (ruleNewEndIndex == ruleNewStartIndex)
- {
- Logger.Debug("TileRules: Tile index " + tile.TileIndex + " at X: " + tile.X + ", Y: " + tile.Y + " - index changed to " + ruleNewStartIndex);
- tile.TileIndex = ruleNewStartIndex;
- tileDataAltered = true;
- break;
- }
- else
- {
- Logger.Debug("TileRules: Tile ID " + tile.TileIndex + " at X: " + tile.X + ", Y: " + tile.Y + " - index changed to " +
- (ruleNewStartIndex + Math.Abs(ruleOriginalStartIndex - tile.TileIndex)));
- tile.TileIndex = ruleNewStartIndex + Math.Abs(ruleOriginalStartIndex - tile.TileIndex);
- tileDataAltered = true;
- break;
- }
- }
- }
- }
-
- return tileDataAltered;
- }
-
- ///
- /// Removes level 0 clear tiles from IsoMapPack5 data.
- ///
- /// Returns true if tile data was changed, false if not.
- private bool RemoveLevel0ClearTiles()
- {
- if (!Initialized || !removeLevel0ClearTiles)
- return false;
-
- Logger.Info("RemoveLevel0ClearTiles set: All tile data with tile index & level set to 0 is removed.");
-
- List removeTiles = new List();
-
- foreach (MapTile tile in isoMapPack5)
- {
- if (tile.TileIndex < 1 && tile.Level < 1 && tile.SubTileIndex < 1 && tile.IceGrowth < 1)
- removeTiles.Add(tile);
- }
-
- int removeCount = isoMapPack5.RemoveAll(x => removeTiles.Contains(x));
-
- return removeCount > 0;
- }
-
- ///
- /// Sorts tiles in map pack based on the set sorting method.
- ///
- /// Returns true if tile data was changed, false if not.
- private bool SortIsoMapPack()
- {
- if (!Initialized || isoMapPack5.Count < 1 || isoMapPack5SortBy == IsoMapPack5SortMode.NotDefined)
- return false;
-
- Logger.Info("IsoMapPack5SortBy set: IsoMapPack5 data will be sorted using sorting mode: " + isoMapPack5SortBy);
- switch (isoMapPack5SortBy)
- {
- case IsoMapPack5SortMode.XLevelTileIndex:
- isoMapPack5 = isoMapPack5.OrderBy(x => x.X).ThenBy(x => x.Level).ThenBy(x => x.TileIndex).ToList();
- break;
- case IsoMapPack5SortMode.XTileIndexLevel:
- isoMapPack5 = isoMapPack5.OrderBy(x => x.X).ThenBy(x => x.TileIndex).ThenBy(x => x.Level).ToList();
- break;
- case IsoMapPack5SortMode.TileIndexXLevel:
- isoMapPack5 = isoMapPack5.OrderBy(x => x.TileIndex).ThenBy(x => x.X).ThenBy(x => x.Level).ToList();
- break;
- case IsoMapPack5SortMode.LevelXTileIndex:
- isoMapPack5 = isoMapPack5.OrderBy(x => x.Level).ThenBy(x => x.X).ThenBy(x => x.TileIndex).ToList();
- break;
- case IsoMapPack5SortMode.X:
- isoMapPack5 = isoMapPack5.OrderBy(x => x.X).ToList();
- break;
- case IsoMapPack5SortMode.Level:
- isoMapPack5 = isoMapPack5.OrderBy(x => x.Level).ToList();
- break;
- case IsoMapPack5SortMode.TileIndex:
- isoMapPack5 = isoMapPack5.OrderBy(x => x.TileIndex).ToList();
- break;
- case IsoMapPack5SortMode.SubTileIndex:
- isoMapPack5 = isoMapPack5.OrderBy(x => x.SubTileIndex).ToList();
- break;
- case IsoMapPack5SortMode.IceGrowth:
- isoMapPack5 = isoMapPack5.OrderBy(x => x.IceGrowth).ToList();
- break;
- case IsoMapPack5SortMode.Y:
- isoMapPack5 = isoMapPack5.OrderBy(x => x.Y).ToList();
- break;
- default:
- return false;
- }
-
- return true;
- }
-
- ///
- /// Changes overlay data of current map based on conversion profile.
- ///
- public void ConvertOverlayData()
- {
- if (!Initialized || overlayRules == null || overlayRules.Count < 1)
- return;
-
- else if (!IsCurrentTheaterAllowed())
- {
- Logger.Warn("Skipping altering overlay data - ApplicableTheaters does not contain entry matching map theater.");
- return;
- }
-
- ParseOverlayPacks();
-
- bool overlayDataAltered = ApplyOverlayConversionRules();
-
- if (overlayDataAltered)
- SaveOverlayPacks();
-
- MapAltered |= overlayDataAltered;
- }
-
- ///
- /// Processes overlay data conversion rules.
- ///
- /// Returns true if overlay data was changed, false if not.
- private bool ApplyOverlayConversionRules()
- {
- Logger.Info("Attempting to apply OverlayRules on map overlay data.");
-
- bool overlayDataChanged = false;
-
- for (int i = 0; i < OVERLAY_DATA_LENGTH; i++)
- {
- /*
- if (overlayPack[i] == 255)
- {
- overlayDataPack[i] = 0;
- continue;
- }*/
- if (overlayPack[i] < 0 || overlayPack[i] > 255)
- overlayPack[i] = 255;
- if (overlayDataPack[i] < 0 || overlayDataPack[i] > 255)
- overlayDataPack[i] = 0;
-
- int x = i % 512;
- int y = (i - x) / 512;
-
- foreach (OverlayConversionRule rule in overlayRules)
- {
- if (!rule.IsValid)
- continue;
-
- if (rule.CoordinateFilterX > -1 && rule.CoordinateFilterX != x ||
- rule.CoordinateFilterY > -1 && rule.CoordinateFilterY != y)
- continue;
-
- bool overlayPackChanged = ChangeOverlayData(overlayPack, i, x, y, rule.OriginalStartIndex, rule.OriginalEndIndex,
- rule.NewStartIndex, rule.NewEndIndex, rule.IsRandomizer, false);
-
- bool overlayDataPackChanged = ChangeOverlayData(overlayDataPack, i, x, y, rule.OriginalStartFrameIndex, rule.OriginalEndFrameIndex,
- rule.NewStartFrameIndex, rule.NewEndFrameIndex, rule.IsFrameRandomizer, true);
-
- if (overlayPackChanged || overlayDataPackChanged)
- {
- overlayDataChanged = true;
- break;
- }
- }
- }
- return overlayDataChanged;
- }
-
- ///
- /// Changes overlay data.
- ///
- /// Data collection to change.
- /// Overlay index in the data collection.
- /// Overlay X coordinate.
- /// Overlay Y coordinate.
- /// Original start index.
- /// Original end index.
- /// New start index.
- /// New end index.
- /// If true, use a random range.
- /// If true, treat changes as being made to frame data rather than overlay ID data.
- /// Returns true if overlay data was changed, false if not.
- private bool ChangeOverlayData(byte[] data, int index, int x, int y, int originalStartIndex, int originalEndIndex,
- int newStartIndex, int newEndIndex, bool useRandomRange, bool changeFrameData)
- {
- string dataType = changeFrameData ? "frame" : "ID";
- if (data[index] >= originalStartIndex && data[index] <= originalEndIndex)
- {
- if (useRandomRange)
- {
- byte newindex = (byte)random.Next(newStartIndex, newEndIndex);
- Logger.Debug("OverlayRules: Random " + dataType + " range [" + newStartIndex + "-" + newEndIndex + "]. Picked: " + newindex);
- if (newindex != data[index])
- {
- Logger.Debug("OverlayRules: Overlay " + dataType + " " + data[index] + " at array slot " + index + " (X: " + x + ", Y: " + y + ") changed to " +
- newindex + ".");
- data[index] = newindex;
- return true;
- }
- }
- else if (newEndIndex == newStartIndex)
- {
- Logger.Debug("OverlayRules: Overlay " + dataType + " " + data[index] + " at array slot " + index + " (X: " + x + ", Y: " + y + ") changed to " +
- newStartIndex + ".");
- data[index] = (byte)newStartIndex;
- return true;
- }
- else
- {
- Logger.Debug("OverlayRules: Overlay " + dataType + " " + data[index] + " at array slot " + index + " (X: " + x + ", Y: " + y + ") changed to " +
- (newStartIndex + Math.Abs(originalStartIndex - data[index])) + ".");
- data[index] = (byte)(newStartIndex + Math.Abs(originalStartIndex - data[index]));
- return true;
- }
- return false;
- }
- else
- return false;
- }
-
- ///
- /// Changes object data of current map based on conversion profile.
- ///
- public void ConvertObjectData()
- {
- if (!Initialized || overlayRules == null || objectRules.Count < 1) return;
- else if (mapTheater != null && applicableTheaters != null && !applicableTheaters.Contains(mapTheater))
- {
- Logger.Warn("Conversion profile not applicable to maps belonging to this theater. No alterations will be made to the object data.");
- return;
- }
- Logger.Info("Attempting to modify object data of the map file.");
- ApplyObjectConversionRules("Aircraft");
- ApplyObjectConversionRules("Units");
- ApplyObjectConversionRules("Infantry");
- ApplyObjectConversionRules("Structures");
- ApplyObjectConversionRules("Terrain");
- }
-
- ///
- /// Processes object data conversion rules.
- ///
- /// ID of the object list section to apply the rules to.
- private void ApplyObjectConversionRules(string sectionName)
- {
- if (string.IsNullOrEmpty(sectionName)) return;
- KeyValuePair[] kvps = mapINI.GetKeyValuePairs(sectionName);
- if (kvps == null) return;
- foreach (KeyValuePair kvp in kvps)
- {
- foreach (ObjectConversionRule rule in objectRules)
- {
- if (rule == null || rule.OriginalName == null)
- continue;
-
- GetObjectCoordinates(kvp, sectionName, out int x, out int y);
-
- if (rule.CoordinateFilterX > -1 && rule.CoordinateFilterX != x ||
- rule.CoordinateFilterY > -1 && rule.CoordinateFilterY != y)
- continue;
-
- if (CheckIfObjectIDMatches(kvp.Value, rule.OriginalName))
- {
- if (rule.NewName == null)
- {
- Logger.Debug("ObjectRules: Removed " + sectionName + " object with ID '" + rule.OriginalName + "' (X: " + x + ", Y: " + y + ") from the map file.");
- mapINI.RemoveKey(sectionName, kvp.Key);
- MapAltered = true;
- }
- else
- {
- Logger.Debug("ObjectRules: Replaced " + sectionName + " object with ID '" + rule.OriginalName + "' (X: " + x + ", Y: " + y + ") with object of ID '" + rule.NewName + "'.");
- mapINI.SetKey(sectionName, kvp.Key, kvp.Value.Replace(rule.OriginalName, rule.NewName));
- MapAltered = true;
- }
- }
- }
- }
- }
-
- ///
- /// Deletes objects outside map bounds.
- ///
- private void DeleteObjectsOutsideBounds()
- {
- DeleteObjectsOutsideBoundsFromSection("Units");
- DeleteObjectsOutsideBoundsFromSection("Infantry");
- DeleteObjectsOutsideBoundsFromSection("Units");
- DeleteObjectsOutsideBoundsFromSection("Structures");
-
- string[] keys = mapINI.GetKeys("Terrain");
- if (keys == null)
- return;
-
- foreach (string key in keys)
- {
- int coord = Conversion.GetIntFromString(key, -1);
- if (coord < 0)
- continue;
- int x = coord % 1000;
- int y = (coord - x) / 1000;
-
- if (!CoordinateExistsOnMap(x, y))
- {
- Logger.Debug("DeleteObjectsOutsideMapBounds: Removed Terrain object " + mapINI.GetKey("Terrain", key, "") +
- " (key: " + key + ") from cell " + x + "," + y + ".");
- mapINI.RemoveKey("Terrain", key);
- MapAltered = true;
- }
- }
- }
-
- ///
- /// Deletes specific types of objects outside map bounds.
- ///
- ///
- private void DeleteObjectsOutsideBoundsFromSection(string sectionName)
- {
- string[] keys = mapINI.GetKeys(sectionName);
- if (keys == null) return;
- foreach (string key in keys)
- {
- string[] tmp = mapINI.GetKey(sectionName, key, "").Split(',');
- if (tmp.Length < 5)
- continue;
- int x = Conversion.GetIntFromString(tmp[3], -1);
- int y = Conversion.GetIntFromString(tmp[4], -1);
- if (x < 0 || y < 0)
- continue;
- if (!CoordinateExistsOnMap(x, y))
- {
- string[] values = mapINI.GetKey(sectionName, key, "").Split(',');
- Logger.Debug("DeleteObjectsOutsideMapBounds: Removed " + sectionName + " object " + (values.Length > 1 ? values[1] : "???") +
- " (key: " + key + ") from cell " + x + "," + y + ".");
- mapINI.RemoveKey(sectionName, key);
- MapAltered = true;
- }
- }
- }
-
- ///
- /// Deletes overlays outside map bounds.
- ///
- private void DeleteOverlaysOutsideBounds()
- {
- if (overlayPack == null || overlayDataPack == null)
- {
- ParseOverlayPacks();
- }
-
- if (overlayPack == null || overlayDataPack == null)
- return;
-
- bool overlayDataAltered = false;
-
- for (int i = 0; i < overlayPack.Length; i++)
- {
- if (overlayPack[i] == 255)
- continue;
- int x = i % 512;
- int y = (i - x) / 512;
-
- if (!CoordinateExistsOnMap(x, y))
- {
- Logger.Debug("DeleteObjectsOutsideMapBounds: Removed overlay (index: " + overlayPack[i] + ") from cell " + x + "," + y + ".");
- overlayPack[i] = 255;
- overlayDataPack[i] = 0;
- overlayDataAltered = true;
- }
- }
-
- if (overlayDataAltered)
- SaveOverlayPacks();
-
- MapAltered |= overlayDataAltered;
- }
-
- ///
- /// Fixes tunnels.
- /// Based on Rampastring's FinalSun Tunnel Fixer.
- /// https://ppmforums.com/viewtopic.php?t=42008
- ///
- private void FixTubesSection()
- {
- string[] keys = mapINI.GetKeys("Tubes");
-
- if (keys == null)
- return;
-
- int counter = 0;
- foreach (string key in keys)
- {
- List values = mapINI.GetKey("Tubes", key, string.Empty).Split(',').ToList();
-
- int index = values.FindIndex(str => str == "-1");
-
- if (index < 1 || index > values.Count - 3)
- continue;
-
- if (counter % 2 == 0)
- {
- Logger.Debug("FixTunnels: Set -1 at index " + index + " in tube #" + counter + " to " + values[index - 1] + ".");
- values[index] = values[index - 1];
- values.RemoveRange(index + 2, values.Count - (index + 2));
- }
- else
- values.RemoveRange(index + 1, values.Count - (index + 1));
- mapINI.SetKey("Tubes", key, string.Join(",", values));
- MapAltered = true;
- counter++;
- }
- }
-
- ///
- /// Changes section data of current map based on conversion profile.
- ///
- public void ConvertSectionData()
- {
- if (!Initialized || sectionRules == null || sectionRules.Count < 1) return;
- else if (!IsCurrentTheaterAllowed())
- {
- Logger.Warn("Skipping altering section data - ApplicableTheaters does not contain entry matching map theater.");
- return;
- }
- Logger.Info("Attempting to modify section data of the map file.");
- ApplySectionConversionRules();
- }
-
- ///
- /// Processes section data conversion rules.
- ///
- private void ApplySectionConversionRules()
- {
- foreach (SectionConversionRule rule in sectionRules)
- {
- if (string.IsNullOrEmpty(rule.OriginalSection))
- continue;
-
- string currentSection = rule.OriginalSection;
- if (rule.NewSection == null)
- {
- Logger.Debug("SectionRules: Removed section '" + rule.OriginalSection + "'.");
- mapINI.RemoveSection(rule.OriginalSection);
- MapAltered = true;
- continue;
- }
- else if (rule.NewSection != "")
- {
- if (!mapINI.SectionExists(rule.OriginalSection))
- {
- Logger.Debug("SectionRules: Added new section '" + rule.NewSection + "'.");
- mapINI.AddSection(rule.NewSection);
- }
- else
- {
- Logger.Debug("SectionRules: Renamed section '" + rule.OriginalSection + "' to '" + rule.NewSection + "'.");
- mapINI.RenameSection(rule.OriginalSection, rule.NewSection);
- }
- MapAltered = true;
- currentSection = rule.NewSection;
- }
-
- string currentKey = rule.OriginalKey;
- if (rule.NewKey == null)
- {
- Logger.Debug("SectionRules: Removed key '" + rule.OriginalKey + "' from section '" + currentSection + "'.");
- mapINI.RemoveKey(currentSection, rule.OriginalKey);
- MapAltered = true;
- continue;
- }
- else if (rule.NewKey != "")
- {
- if (mapINI.GetKey(currentSection, rule.OriginalKey, null) == null)
- {
- Logger.Debug("SectionRules: Added a new key '" + rule.NewKey + "' to section '" + currentSection + "'.");
- mapINI.SetKey(currentSection, rule.NewKey, "");
- }
- else
- {
- Logger.Debug("SectionRules: Renamed key '" + rule.OriginalKey + "' in section '" + currentSection + "' to '" + rule.NewKey + "'.");
- mapINI.RenameKey(currentSection, rule.OriginalKey, rule.NewKey);
- }
- MapAltered = true;
- currentKey = rule.NewKey;
- }
-
- if (rule.NewValue != "")
- {
- Logger.Debug("SectionRules: Section '" + currentSection + "' key '" + currentKey + "' value changed to '" + rule.NewValue + "'.");
- mapINI.SetKey(currentSection, currentKey, rule.NewValue);
- MapAltered = true;
- }
- }
- }
-
- #endregion
-
- #region helpers
-
- ///
- /// Checks if map object declaration matches with specific object ID.
- ///
- /// Object declaration from map file.
- /// Object ID.
- /// True if a match, otherwise false.
- private bool CheckIfObjectIDMatches(string objectDeclaration, string objectID)
- {
- if (objectDeclaration.Equals(objectID))
- return true;
- string[] sp = objectDeclaration.Split(',');
- if (sp.Length < 2)
- return false;
- if (sp[1].Equals(objectID))
- return true;
- return false;
- }
-
- ///
- /// Gets coordinates of a map object.
- ///
- /// Object declaration key & value.
- /// Map object section name.
- /// Will be set to map tile x coordinate of the object.
- /// Will be set to map tile y coordinate of the object.
- private void GetObjectCoordinates(KeyValuePair kvp, string sectionName, out int x, out int y)
- {
- x = -1;
- y = -1;
-
- if (sectionName.Equals("Terrain"))
- {
- int coord = Conversion.GetIntFromString(kvp.Key, -1);
- if (coord < 0)
- return;
- x = coord % 1000;
- y = (coord - x) / 1000;
- }
- else
- {
- string[] sp = kvp.Value.Split(',');
- if (sp.Length < 5)
- return;
- x = Conversion.GetIntFromString(sp[3], -1);
- y = Conversion.GetIntFromString(sp[4], -1);
- }
- }
-
- ///
- /// Checks if location with given coordinates exists within map bounds.
- ///
- /// Location X coordinate.
- /// Location Y coordinate.
- /// True if location exists, false if not.
- private bool CoordinateExistsOnMap(int x, int y)
- {
- if (!Initialized || coordinateValidityLUT == null ||
- coordinateValidityLUT.GetLength(0) <= x || coordinateValidityLUT.GetLength(1) <= y)
- return false;
-
- return coordinateValidityLUT[x, y];
- }
-
- ///
- /// Calculates valid map coordinates from map width & height and creates a look-up table from them.
- ///
- private void CalculateCoordinateValidity()
- {
- Logger.Debug("Calculating map coordinate look-up table.");
-
- int size = Math.Max(mapWidth, mapHeight) * 2 + 1;
- coordinateValidityLUT = new bool[size, size];
-
- int yOffset = 0;
- for (int col = 1; col <= mapWidth; col++)
- {
- int startY = mapWidth - yOffset;
- for (int row = 0; row < mapHeight; row++)
- {
- int x = col + row;
- int y = startY + row;
- coordinateValidityLUT[x, y] = true;
- if (col < mapWidth)
- coordinateValidityLUT[x + 1, y] = true;
- }
- yOffset += 1;
- }
- }
-
- ///
- /// Merges array of string key-value pairs to a single string array containing strings of the keys and values separated by =.
- ///
- /// Array of string key-value pairs.
- /// Array of strings made by merging the keys and values.
- private string[] MergeKeyValuePairs(KeyValuePair[] keyValuePairs)
- {
- if (keyValuePairs == null)
- return null;
-
- string[] result = new string[keyValuePairs.Length];
- for (int i = 0; i < keyValuePairs.Length; i++)
- {
- result[i] = keyValuePairs[i].Key + "=" + keyValuePairs[i].Value;
- }
- return result;
- }
-
- #endregion
-
- ///
- /// Lists theater config file data to a text file.
- ///
- public void ListTileSetData()
- {
- if (!Initialized || theaterConfigINI == null)
- return;
-
- List tilesets = ParseTilesetData();
-
- if (tilesets == null || tilesets.Count < 1)
- {
- Logger.Error("Could not parse tileset data from theater configuration file '" +
- theaterConfigINI.Filename + "'."); return;
- };
-
- Logger.Info("Attempting to list tileset data for a theater based on file: '" + theaterConfigINI.Filename + "'.");
- List lines = new List();
- int tilecounter = 0;
- lines.Add("Theater tileset data gathered from file '" + theaterConfigINI.Filename + "'.");
- lines.Add("");
- lines.Add("");
- foreach (Tileset tileset in tilesets)
- {
- if (tileset.TilesInSet < 1)
- {
- Logger.Debug("ListTileSetData: " + tileset.SetID + " (" + tileset.SetName + ")" + " skipped due to tile count of 0.");
- continue;
- }
- lines.AddRange(tileset.GetPrintableData(tilecounter));
- lines.Add("");
- tilecounter += tileset.TilesInSet;
- Logger.Debug("ListTileSetData: " + tileset.SetID + " (" + tileset.SetName + ")" + " added to the list.");
- }
- File.WriteAllLines(FilenameOutput, lines.ToArray());
- }
-
- ///
- /// Parse tileset data from a theater configuration INI file.
- ///
- /// List of tilesets.
- private List ParseTilesetData()
- {
- List tilesets = new List();
-
- if (!Initialized || theaterConfigINI == null)
- return tilesets;
-
- string[] sections = theaterConfigINI.GetSections();
- foreach (string section in sections)
- {
- if (!Regex.IsMatch(section, "^TileSet\\d{4}$"))
- continue;
-
- Tileset tileset = new Tileset
- {
- SetID = section,
- SetNumber = Conversion.GetIntFromString(section.Substring(7, 4), -1),
- SetName = theaterConfigINI.GetKey(section, "SetName", "N/A"),
- FileName = theaterConfigINI.GetKey(section, "FileName", "N/A").ToLower(),
- TilesInSet = Conversion.GetIntFromString(theaterConfigINI.GetKey(section, "TilesInSet", "0"), 0)
- };
-
- if (tileset.SetNumber == -1)
- continue;
-
- tilesets.Add(tileset);
- }
-
- return tilesets;
- }
- }
-}
+/*
+ * Copyright 2017-2020 by Starkku
+ * This file is part of MapTool, which is free software. It is made
+ * available to you under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version. For more
+ * information, see COPYING.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.IO;
+using System.Text.RegularExpressions;
+using System.Globalization;
+using MapTool.DataStructures;
+using StarkkuUtils.FileTypes;
+using StarkkuUtils.Utilities;
+using StarkkuUtils.ExtensionMethods;
+
+namespace MapTool
+{
+ ///
+ /// Map tile data sort mode.
+ ///
+ public enum IsoMapPack5SortMode { NotDefined, XLevelTileIndex, XTileIndexLevel, TileIndexXLevel, LevelXTileIndex, X, Level, TileIndex, SubTileIndex, IceGrowth, Y }
+
+ ///
+ /// Map file modifier tool class.
+ ///
+ public class MapTool
+ {
+ #region public_properties
+
+ ///
+ /// Has tool been initialized or not.
+ ///
+ public bool Initialized { get; private set; }
+
+ ///
+ /// Has map file been altered or not.
+ ///
+ public bool MapAltered { get; private set; }
+
+ ///
+ /// Map input filename.
+ ///
+ public string FilenameInput { get; private set; }
+
+ ///
+ /// Map output filename.
+ ///
+ public string FilenameOutput { get; private set; }
+
+ #endregion
+
+ #region private_fields
+
+ ///
+ /// Map file.
+ ///
+ private readonly INIFile mapINI;
+
+ ///
+ /// Map theater.
+ ///
+ private readonly string mapTheater = null;
+
+ ///
+ /// Map full width.
+ ///
+ private readonly int mapWidth;
+
+ ///
+ /// Map full height.
+ ///
+ private readonly int mapHeight;
+
+ ///
+ /// Map tile data.
+ ///
+ private List isoMapPack5 = new List();
+
+ ///
+ /// Map overlay ID data.
+ ///
+ private byte[] overlayPack = null;
+
+ ///
+ /// Map overlay frame data.
+ ///
+ private byte[] overlayDataPack = null;
+
+ ///
+ /// Look-up table for map coordinate (X,Y) validity.
+ ///
+ private bool[,] coordinateValidityLUT;
+
+ ///
+ /// Conversion profile INI file.
+ ///
+ private readonly INIFile conversionProfileINI;
+
+ ///
+ /// Conversion profile applicable theaters.
+ ///
+ private readonly List applicableTheaters = new List();
+
+ ///
+ /// Conversion profile theater-specific global tile offsets.
+ ///
+ private readonly Dictionary> theaterTileOffsets = new Dictionary>();
+
+ ///
+ /// Conversion profile new theater.
+ ///
+ private readonly string newTheater = null;
+
+ ///
+ /// Conversion profile tile rules.
+ ///
+ private readonly List tileRules = new List();
+
+ ///
+ /// Conversion profile overlay rules.
+ ///
+ private readonly List overlayRules = new List();
+
+ ///
+ /// Conversion profile object rules.
+ ///
+ private readonly List objectRules = new List();
+
+ ///
+ /// // Conversion profile section rules.
+ ///
+ private readonly List sectionRules = new List();
+
+ ///
+ /// Optimize output map file or not.
+ ///
+ private readonly bool useMapOptimize = false;
+
+ ///
+ /// Compress output map file or not.
+ ///
+ private readonly bool useMapCompress = false;
+
+ ///
+ /// Delete objects outside visible map bounds or not.
+ ///
+ private readonly bool deleteObjectsOutsideMapBounds = false;
+
+ ///
+ /// Remove clear tiles at level 0 from map tile data (they will be filled in by the game) or not.
+ ///
+ private readonly bool removeLevel0ClearTiles = false;
+
+ ///
+ /// Fix tunnel data or not.
+ ///
+ private readonly bool fixTunnels = false;
+
+ ///
+ /// Map tile data sort mode.
+ ///
+ private readonly IsoMapPack5SortMode isoMapPack5SortBy = IsoMapPack5SortMode.NotDefined;
+
+ ///
+ /// Theater configuration file.
+ ///
+ private readonly INIFile theaterConfigINI;
+
+ ///
+ /// OverlayPack/DataPack length.
+ ///
+ private const int OVERLAY_DATA_LENGTH = 262144;
+
+ ///
+ /// Random number generator.
+ ///
+ private readonly Random random = new Random();
+
+ #endregion
+
+ ///
+ /// Initializes a new instance of MapTool.
+ ///
+ /// Input file name.
+ /// Output file name.
+ /// Conversion profile file name.
+ /// If set, it is assumed that this instance of MapTool is initialized for listing theater data rather than processing maps.
+ public MapTool(string inputFile, string outputFile, string fileConfig, bool listTheaterData)
+ {
+ Initialized = false;
+ MapAltered = false;
+ FilenameInput = inputFile;
+ FilenameOutput = outputFile;
+
+ if (listTheaterData && !string.IsNullOrEmpty(FilenameInput))
+ {
+ theaterConfigINI = new INIFile(FilenameInput);
+ }
+
+ else if (!string.IsNullOrEmpty(FilenameInput) && !string.IsNullOrEmpty(FilenameOutput))
+ {
+
+ Logger.Info("Reading map file '" + inputFile + "'.");
+ mapINI = new INIFile(inputFile);
+
+ string[] size = mapINI.GetKey("Map", "Size", "").Split(',');
+ mapWidth = Conversion.GetIntFromString(size[2], -1);
+ mapHeight = Conversion.GetIntFromString(size[3], -1);
+
+ mapTheater = mapINI.GetKey("Map", "Theater", null);
+ if (mapTheater != null)
+ mapTheater = mapTheater.ToUpper();
+
+ Logger.Info("Parsing conversion profile file.");
+ conversionProfileINI = new INIFile(fileConfig);
+ string[] sections = conversionProfileINI.GetSections();
+ if (sections == null || sections.Length < 1)
+ {
+ Logger.Error("Conversion profile file is empty.");
+ Initialized = false;
+ return;
+ }
+
+ string include = conversionProfileINI.GetKey("ProfileData", "IncludeFiles", null);
+ if (!string.IsNullOrEmpty(include))
+ {
+ string[] includeFiles = include.Split(',');
+ string basedir = Path.GetDirectoryName(fileConfig);
+ foreach (string filename in includeFiles)
+ {
+ if (File.Exists(basedir + "\\" + filename))
+ {
+ INIFile includeIni = new INIFile(basedir + "\\" + filename);
+ Logger.Info("Merging included file '" + filename + "' to conversion profile.");
+ conversionProfileINI.Merge(includeIni);
+ }
+ }
+ }
+
+ // Parse general options.
+ useMapOptimize = Conversion.GetBoolFromString(conversionProfileINI.GetKey("ProfileData", "ApplyMapOptimization", "false"), false);
+ useMapCompress = Conversion.GetBoolFromString(conversionProfileINI.GetKey("ProfileData", "ApplyMapCompress", "false"), false);
+ deleteObjectsOutsideMapBounds = Conversion.GetBoolFromString(conversionProfileINI.GetKey("ProfileData", "DeleteObjectsOutsideMapBounds",
+ "false"), false);
+ fixTunnels = Conversion.GetBoolFromString(conversionProfileINI.GetKey("ProfileData", "FixTunnels", "false"), false);
+
+ // Parse tile data options.
+ string sortMode = conversionProfileINI.GetKey("IsoMapPack5", "SortBy", null);
+ if (sortMode != null)
+ {
+ Enum.TryParse(sortMode.Replace("_", ""), true, out isoMapPack5SortBy);
+ }
+ removeLevel0ClearTiles = Conversion.GetBoolFromString(conversionProfileINI.GetKey("IsoMapPack5", "RemoveLevel0ClearTiles", "false"), false);
+
+ // Parse theater rules.
+ newTheater = conversionProfileINI.GetKey("TheaterRules", "NewTheater", null);
+ if (newTheater != null)
+ newTheater = newTheater.ToUpper();
+
+ string[] applicableTheaters = conversionProfileINI.GetKey("TheaterRules", "ApplicableTheaters", "").Split(',');
+ if (applicableTheaters != null)
+ {
+ for (int i = 0; i < applicableTheaters.Length; i++)
+ {
+ string theater = applicableTheaters[i].Trim().ToUpper();
+ if (theater == "")
+ continue;
+ this.applicableTheaters.Add(theater);
+ }
+ }
+
+ if (this.applicableTheaters.Count < 1)
+ this.applicableTheaters.AddRange(new string[] { "TEMPERATE", "SNOW", "URBAN", "DESERT", "LUNAR", "NEWURBAN" });
+
+ // Parse theater-specific global tile offsets.
+ string[] theaterOffsetKeys = conversionProfileINI.GetKeys("TheaterTileOffsets");
+ if (theaterOffsetKeys != null)
+ {
+ foreach (string key in theaterOffsetKeys)
+ {
+ int newOffset = int.MinValue;
+ string[] values = conversionProfileINI.GetKey("TheaterTileOffsets", key, "").Split(',');
+ int originalOffset;
+ if (values.Length < 1)
+ continue;
+ else if (values.Length < 2)
+ {
+ originalOffset = Conversion.GetIntFromString(values[0], 0);
+ }
+ else
+ {
+ originalOffset = Conversion.GetIntFromString(values[0], 0);
+ newOffset = Conversion.GetIntFromString(values[1], int.MinValue);
+ }
+ theaterTileOffsets.Add(key.ToUpper(), new Tuple(originalOffset, newOffset));
+ }
+ }
+
+ // Parse conversion rules.
+ string[] tilerules = conversionProfileINI.GetKeys("TileRules");
+ string[] overlayrules = conversionProfileINI.GetKeys("OverlayRules");
+ string[] objectrules = conversionProfileINI.GetKeys("ObjectRules");
+ string[] sectionrules = MergeKeyValuePairs(conversionProfileINI.GetKeyValuePairs("SectionRules"));
+
+ // Allow saving map without any other changes if either of these are set and ApplicableTheaters allows it.
+ bool allowSaving = (useMapCompress || useMapOptimize || deleteObjectsOutsideMapBounds || fixTunnels ||
+ isoMapPack5SortBy != IsoMapPack5SortMode.NotDefined) && IsCurrentTheaterAllowed();
+
+ if (!allowSaving && tilerules == null && overlayrules == null && objectrules == null && sectionrules == null &&
+ string.IsNullOrEmpty(newTheater))
+ {
+ Logger.Error("No conversion rules to apply in the conversion profile file.");
+ Initialized = false;
+ return;
+ }
+
+ ParseConversionRules(tilerules, tileRules);
+ ParseConversionRules(overlayrules, overlayRules);
+ ParseConversionRules(objectrules, objectRules);
+ ParseConversionRules(sectionrules, sectionRules);
+ }
+
+ Initialized = true;
+ }
+
+ ///
+ /// Saves the map file.
+ ///
+ public void Save()
+ {
+ if (deleteObjectsOutsideMapBounds)
+ {
+ if (mapWidth > 0 && mapHeight > 0)
+ {
+ Logger.Info("DeleteObjectsOutsideMapBounds set: Objects & overlays outside map bounds will be deleted.");
+ CalculateCoordinateValidity();
+ DeleteObjectsOutsideBounds();
+ DeleteOverlaysOutsideBounds();
+ }
+ else
+ Logger.Warn("DeleteObjectsOutsideMapBounds set but because map has invalid Size value set, no objects or overlays will be deleted.");
+ }
+ if (useMapOptimize)
+ {
+ Logger.Info("ApplyMapOptimization set: Saved map will have map section order optimizations applied.");
+ mapINI.MoveSectionToFirst("Basic");
+ mapINI.MoveSectionToFirst("MultiplayerDialogSettings");
+ mapINI.MoveSectionToLast("Digest");
+ MapAltered = true;
+ }
+ if (fixTunnels)
+ {
+ Logger.Info("FixTunnels set: Saved map will have [Tubes] section fixed to remove errors caused by map editor.");
+ FixTubesSection();
+ }
+ if (useMapCompress)
+ Logger.Info("ApplyMapCompress set: Saved map will have no unnecessary whitespaces or comments.");
+
+ string error;
+ if (MapAltered || useMapCompress)
+ error = mapINI.Save(FilenameOutput, !useMapCompress, !useMapCompress);
+ else
+ {
+ Logger.Info("Skipping saving map file as no changes have been made to it.");
+ return;
+ }
+
+ if (string.IsNullOrEmpty(error))
+ Logger.Info("Map file successfully saved to '" + FilenameOutput + "'.");
+ else
+ {
+ Logger.Error("Error encountered saving map file to '" + FilenameOutput + "'.");
+ Logger.Error("Message: " + error);
+ }
+ }
+
+ ///
+ /// Checks if theater name is valid.
+ ///
+ /// Theater name.
+ /// True if a valid theater name, otherwise false.
+ public static bool IsValidTheaterName(string theaterName)
+ {
+ if (theaterName == "TEMPERATE" || theaterName == "SNOW" || theaterName == "LUNAR" || theaterName == "DESERT" ||
+ theaterName == "URBAN" || theaterName == "NEWURBAN")
+ return true;
+
+ return false;
+ }
+
+ ///
+ /// Checks if the currently set map theater exists in current list of theaters the map tool is allowed to process.
+ ///
+ /// True if map theater exists in applicable theaters, otherwise false.
+ private bool IsCurrentTheaterAllowed()
+ {
+ if (applicableTheaters == null || mapTheater == null || !applicableTheaters.Contains(mapTheater))
+ return false;
+
+ return true;
+ }
+
+ #region map_pack_handling
+
+ ///
+ /// Parses IsoMapPack5 section of the map file.
+ ///
+ /// Error message if something went wrong, otherwise null.
+ private string ParseMapPack()
+ {
+ int cellCount;
+ byte[] isoMapPack;
+
+ if (mapHeight < 1 || mapWidth < 1)
+ return ("Map Size is invalid.");
+
+ cellCount = (mapWidth * 2 - 1) * mapHeight;
+ int lzoPackSize = cellCount * 11 + 4;
+ isoMapPack = new byte[lzoPackSize];
+
+ // Fill up and filter later
+ int j = 0;
+ for (int i = 0; i < cellCount; i++)
+ {
+ isoMapPack[j] = 0x88;
+ isoMapPack[j + 1] = 0x40;
+ isoMapPack[j + 2] = 0x88;
+ isoMapPack[j + 3] = 0x40;
+ j += 11;
+ }
+
+ string errorMessage = ParseEncodedMapSectionData("IsoMapPack5", ref isoMapPack);
+ if (errorMessage != null)
+ return errorMessage;
+
+ int bytesRead = 0;
+ for (int i = 0; i < cellCount; i++)
+ {
+ ushort x = BitConverter.ToUInt16(isoMapPack, bytesRead);
+ bytesRead += 2;
+ ushort y = BitConverter.ToUInt16(isoMapPack, bytesRead);
+ bytesRead += 2;
+ int tileNum = BitConverter.ToInt32(isoMapPack, bytesRead);
+ bytesRead += 4;
+ byte subTile = isoMapPack[bytesRead++];
+ byte level = isoMapPack[bytesRead++];
+ byte iceGrowth = isoMapPack[bytesRead++];
+ if (x > 0 && y > 0 && x <= 16384 && y <= 16384)
+ {
+ isoMapPack5.Add(new MapTile((short)x, (short)y, tileNum, subTile, level, iceGrowth));
+ }
+ }
+ return null;
+ }
+
+ ///
+ /// Saves IsoMapPack5 section of the map file.
+ ///
+ private void SaveMapPack()
+ {
+ if (!Initialized || isoMapPack5.Count < 1)
+ return;
+
+ byte[] isoMapPack = new byte[isoMapPack5.Count * 11 + 4];
+ int i = 0;
+
+ foreach (MapTile t in isoMapPack5)
+ {
+ byte[] x = BitConverter.GetBytes(t.X);
+ byte[] y = BitConverter.GetBytes(t.Y);
+ byte[] tilei = BitConverter.GetBytes(t.TileIndex);
+ isoMapPack[i] = x[0];
+ isoMapPack[i + 1] = x[1];
+ isoMapPack[i + 2] = y[0];
+ isoMapPack[i + 3] = y[1];
+ isoMapPack[i + 4] = tilei[0];
+ isoMapPack[i + 5] = tilei[1];
+ isoMapPack[i + 6] = tilei[2];
+ isoMapPack[i + 7] = tilei[3];
+ isoMapPack[i + 8] = t.SubTileIndex;
+ isoMapPack[i + 9] = t.Level;
+ isoMapPack[i + 10] = t.IceGrowth;
+ i += 11;
+ }
+
+ byte[] lzo = MapPackHelper.Compress(isoMapPack);
+ string data = Convert.ToBase64String(lzo, Base64FormattingOptions.None);
+ ReplacePackedSectionData("IsoMapPack5", data);
+ }
+
+ ///
+ /// Parses Overlay(Data)Pack sections of the map file.
+ ///
+ private void ParseOverlayPacks()
+ {
+ overlayPack = new byte[1 << 18];
+ string errorMessage = ParseEncodedMapSectionData("OverlayPack", ref overlayPack, true);
+ if (errorMessage != null)
+ Logger.Warn(errorMessage);
+
+ overlayDataPack = new byte[1 << 18];
+ errorMessage = ParseEncodedMapSectionData("OverlayDataPack", ref overlayDataPack, true);
+ if (errorMessage != null)
+ Logger.Warn(errorMessage);
+ }
+
+
+ ///
+ /// Saves Overlay(Data)Pack sections of the map file.
+ ///
+ private void SaveOverlayPacks()
+ {
+ string base64_overlayPack = Convert.ToBase64String(MapPackHelper.Compress(overlayPack, true), Base64FormattingOptions.None);
+ string base64_overlayDataPack = Convert.ToBase64String(MapPackHelper.Compress(overlayDataPack, true), Base64FormattingOptions.None);
+ ReplacePackedSectionData("OverlayPack", base64_overlayPack);
+ ReplacePackedSectionData("OverlayDataPack", base64_overlayDataPack);
+ }
+
+ ///
+ /// Parses and decompresses Base64-encoded and compressed data from specified section of map file.
+ ///
+ /// Name of the section.
+ /// Array to put the decompressed data to.
+ /// If set to true, treat data as LCW-compressed instead of LZO.
+ /// Error message if something went wrong, otherwise null.
+ private string ParseEncodedMapSectionData(string sectionName, ref byte[] outputData, bool useLCW = false)
+ {
+ Logger.Info("Parsing " + sectionName + ".");
+ string[] values = mapINI.GetValues(sectionName);
+ if (values == null || values.Length < 1)
+ return sectionName + " data is empty.";
+ byte[] compressedData;
+ try
+ {
+ compressedData = Convert.FromBase64String(string.Join("", values));
+ }
+ catch (Exception)
+ {
+ return sectionName + " is malformed.";
+ }
+ if (MapPackHelper.Decompress(compressedData, outputData, out _, useLCW))
+ {
+ return null;
+ }
+ else
+ {
+ return sectionName + " data is invalid or corrupted.";
+ }
+ }
+
+ ///
+ /// Replaces contents of a Base64-encoded section of map file.
+ ///
+ /// Name of the section.
+ /// Contents to replace the existing contents with.
+ private void ReplacePackedSectionData(string sectionName, string data)
+ {
+ int lx = 70;
+ List lines = new List();
+ for (int x = 0; x < data.Length; x += lx)
+ {
+ lines.Add(data.Substring(x, Math.Min(lx, data.Length - x)));
+ }
+ mapINI.ReplaceSectionWithStrings(sectionName, lines);
+ }
+
+ #endregion
+
+ #region conversion_rule_parsing
+
+ ///
+ /// Parses conversion profile information for tile conversion rules.
+ ///
+ private void ParseConversionRules(string[] ruleStrings, List currentRules)
+ {
+ if (ruleStrings == null || ruleStrings.Length < 1 || currentRules == null) return;
+ currentRules.Clear();
+
+ foreach (string ruleString in ruleStrings)
+ {
+ string ruleStringFiltered = GetCoordinateFilters(ruleString, out int coordFilterX, out int coordFilterY);
+
+ string[] values = ruleStringFiltered.Split('|');
+
+ if (values.Length < 2)
+ continue;
+
+ ParseValueRange(values[0], out int oldValueStart, out int oldValueEnd, out bool oldValueIsRange, out _);
+ ParseValueRange(values[1], out int newValueStart, out int newValueEnd, out bool newValueIsRange, out bool newValueIsRandom, true);
+
+ int heightOverride = -1;
+ int subTileOverride = -1;
+ int iceGrowthOverride = -1;
+ if (values.Length >= 3 && values[2] != null && !values[2].Equals("*", StringComparison.InvariantCultureIgnoreCase))
+ {
+ heightOverride = Conversion.GetIntFromString(values[2], -1);
+ }
+ if (values.Length >= 4 && values[3] != null && !values[3].Equals("*", StringComparison.InvariantCultureIgnoreCase))
+ {
+ subTileOverride = Conversion.GetIntFromString(values[3], -1);
+ }
+ if (values.Length >= 5 && values[4] != null && !values[4].Equals("*", StringComparison.InvariantCultureIgnoreCase))
+ {
+ iceGrowthOverride = Conversion.GetIntFromString(values[4], -1);
+ }
+
+ if (oldValueIsRange && !newValueIsRange)
+ {
+ int diff = newValueStart + (oldValueEnd - newValueStart);
+ currentRules.Add(new TileConversionRule(oldValueStart, newValueStart, oldValueEnd, diff, newValueIsRandom, heightOverride, subTileOverride, iceGrowthOverride, coordFilterX, coordFilterY));
+ }
+ else if (!oldValueIsRange && newValueIsRange)
+ {
+ currentRules.Add(new TileConversionRule(oldValueStart, newValueStart, oldValueStart, newValueEnd, newValueIsRandom, heightOverride, subTileOverride, iceGrowthOverride, coordFilterX, coordFilterY));
+ }
+ else
+ {
+ currentRules.Add(new TileConversionRule(oldValueStart, newValueStart, oldValueEnd, newValueEnd, newValueIsRandom, heightOverride, subTileOverride, iceGrowthOverride, coordFilterX, coordFilterY));
+ }
+ }
+ }
+
+ ///
+ /// Parses conversion profile information for overlay conversion rules.
+ ///
+ private void ParseConversionRules(string[] ruleStrings, List currentRules)
+ {
+ if (ruleStrings == null || ruleStrings.Length < 1 || currentRules == null) return;
+ currentRules.Clear();
+
+ foreach (string ruleString in ruleStrings)
+ {
+ string ruleStringFiltered = GetCoordinateFilters(ruleString, out int coordFilterX, out int coordFilterY);
+
+ string[] values = ruleStringFiltered.Split('|');
+
+ if (values.Length < 2)
+ continue;
+
+ ParseValueRange(values[0], out int oldValueStart, out int oldValueEnd, out bool oldValueIsRange, out _);
+ ParseValueRange(values[1], out int newValueStart, out int newValueEnd, out bool newValueIsRange, out bool newValueIsRandom, true);
+ ParseValueRange(values.Length >= 4 ? values[2] : "", out int frameOldValueStart, out int frameOldValueEnd, out bool frameOldValueIsRange, out _);
+ ParseValueRange(values.Length >= 4 ? values[3] : "", out int frameNewValueStart, out int frameNewValueEnd, out bool frameNewValueIsRange, out bool frameNewValueIsRandom, true);
+
+ int frameOldEndIndex = frameOldValueEnd;
+ int frameNewEndIndex = frameNewValueEnd;
+
+ if (frameOldValueIsRange && !frameNewValueIsRange)
+ {
+ frameOldEndIndex = frameOldValueEnd;
+ frameNewEndIndex = frameNewValueStart + (frameOldValueEnd - frameNewValueStart);
+ }
+ else if (!frameOldValueIsRange && frameNewValueIsRange)
+ {
+ frameOldEndIndex = frameOldValueStart;
+ frameNewEndIndex = frameNewValueEnd;
+ }
+
+ if (oldValueIsRange && !newValueIsRange)
+ {
+ int diff = newValueStart + (oldValueEnd - newValueStart);
+ currentRules.Add(new OverlayConversionRule(oldValueStart, newValueStart, oldValueEnd, diff, newValueIsRandom,
+ frameOldValueStart, frameNewValueStart, frameOldEndIndex, frameNewEndIndex, frameNewValueIsRandom, coordFilterX, coordFilterY));
+ }
+ else if (!oldValueIsRange && newValueIsRange)
+ {
+ currentRules.Add(new OverlayConversionRule(oldValueStart, newValueStart, oldValueStart, newValueEnd, newValueIsRandom,
+ frameOldValueStart, frameNewValueStart, frameOldEndIndex, frameNewEndIndex, frameNewValueIsRandom, coordFilterX, coordFilterY));
+ }
+ else
+ {
+ currentRules.Add(new OverlayConversionRule(oldValueStart, newValueStart, oldValueEnd, newValueEnd, newValueIsRandom,
+ frameOldValueStart, frameNewValueStart, frameOldEndIndex, frameNewEndIndex, frameNewValueIsRandom, coordFilterX, coordFilterY));
+ }
+ }
+ }
+
+ ///
+ /// Parses a value range for byte ID-type conversion rules from string.
+ ///
+ /// String from which the value will be parsed.
+ /// Will be set to the first value of value range.
+ /// Will be set to the second value of value range.
+ /// Will be set to true if value range truly is a range of values, false otherwise.
+ /// Will be set to true if value range is a randomized range, false otherwise.
+ /// If set to true, allows parsing of random value ranges.
+ /// True is value range was completely parsed, false otherwise.
+ private bool ParseValueRange(string value, out int valueA, out int valueB, out bool isRange, out bool isRandom, bool allowRandomRange = false)
+ {
+ valueB = -1;
+ isRange = false;
+ isRandom = false;
+
+ if (allowRandomRange && value.Contains('~'))
+ {
+ isRange = true;
+ isRandom = true;
+ string[] parts = value.Split('~');
+ valueA = Conversion.GetIntFromString(parts[0], -1);
+ valueB = Conversion.GetIntFromString(parts[1], -1);
+ if (valueA < 0 || valueB < 0)
+ return false;
+ }
+ else if (value.Contains('-'))
+ {
+ isRange = true;
+ string[] parts = value.Split('-');
+ valueA = Conversion.GetIntFromString(parts[0], -1);
+ valueB = Conversion.GetIntFromString(parts[1], -1);
+ if (valueA < 0 || valueB < 0)
+ return false;
+ }
+ else
+ {
+ valueA = Conversion.GetIntFromString(value, -1);
+ if (valueA < 0)
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Parses conversion profile information for general object rules.
+ ///
+ private void ParseConversionRules(string[] ruleStrings, List currentRules)
+ {
+ if (ruleStrings == null || ruleStrings.Length < 1 || currentRules == null)
+ return;
+
+ currentRules.Clear();
+
+ foreach (string ruleString in ruleStrings)
+ {
+ string ruleStringFiltered = GetCoordinateFilters(ruleString, out int coordFilterX, out int coordFilterY);
+
+ string[] values = ruleStringFiltered.Split('|');
+ if (values.Length == 1)
+ currentRules.Add(new ObjectConversionRule(values[0], null, coordFilterX, coordFilterY));
+ else if (values.Length >= 2)
+ currentRules.Add(new ObjectConversionRule(values[0], values[1], coordFilterX, coordFilterY));
+ }
+ }
+
+ ///
+ /// Parses conversion profile information for map file section rules.
+ ///
+ private void ParseConversionRules(string[] ruleStrings, List currentRules)
+ {
+ if (ruleStrings == null || ruleStrings.Length < 1 || currentRules == null)
+ return;
+
+ currentRules.Clear();
+
+ foreach (string ruleString in ruleStrings)
+ {
+ if (ruleString == null || ruleString.Length < 1)
+ continue;
+ string[] values = ruleString.Split('|');
+ string newSection = "";
+ string originalKey = "";
+ string newKey = "";
+ string newValue = "";
+ if (values.Length > 0)
+ {
+ if (values[0].StartsWith("="))
+ values[0] = values[0].Substring(1, values[0].Length - 1);
+ string[] sec = values[0].Split('=');
+ if (sec == null || sec.Length < 1)
+ continue;
+ string originalSection = sec[0];
+ if (sec.Length == 1 && values[0].Contains('=') || sec.Length > 1 && values[0].Contains('=') &&
+ string.IsNullOrEmpty(sec[1]))
+ newSection = null;
+ else if (sec.Length > 1)
+ newSection = sec[1];
+ if (values.Length > 1)
+ {
+ string[] key = values[1].Split('=');
+ if (key != null && key.Length > 0)
+ {
+ originalKey = key[0];
+ if (key.Length == 1 && values[1].Contains('=') || key.Length > 1 && values[1].Contains('=') &&
+ string.IsNullOrEmpty(key[1])) newKey = null;
+ else if (key.Length > 1) newKey = key[1];
+ }
+ if (values.Length > 2)
+ {
+ if (!(values[2] == null || values[2] == "" || values[2] == "*"))
+ {
+ if (values[2].Contains("$GETVAL("))
+ {
+ string[] valdata = Regex.Match(values[2], @"\$GETVAL\(([^)]*)\)").Groups[1].Value.Split(',');
+ if (valdata.Length > 1)
+ {
+ string newval = mapINI.GetKey(valdata[0], valdata[1], null);
+ if (newval != null)
+ {
+ newValue = newval;
+ if (valdata.Length > 3)
+ {
+ bool useDouble = true;
+ if (valdata.Length > 4)
+ useDouble = Conversion.GetBoolFromString(valdata[4], true);
+ newValue = ApplyArithmeticOp(newValue, valdata[2], valdata[3], useDouble);
+ }
+ }
+
+ }
+ }
+ else
+ newValue = values[2];
+ }
+ }
+ }
+ currentRules.Add(new SectionConversionRule(originalSection, newSection, originalKey, newKey, newValue));
+ }
+ }
+ }
+
+ private string ApplyArithmeticOp(string value, string opType, string operand, bool useDouble)
+ {
+ bool valueAvailable = double.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out double valueDouble);
+ bool operandAvailable = double.TryParse(operand, NumberStyles.Number, CultureInfo.InvariantCulture, out double operandDouble);
+
+ if (valueAvailable)
+ {
+ switch (opType)
+ {
+ case "+":
+ valueDouble += operandDouble;
+ break;
+ case "-":
+ valueDouble -= operandDouble;
+ break;
+ case "*":
+ if (!operandAvailable)
+ operandDouble = 1;
+ valueDouble = valueDouble * operandDouble;
+ break;
+ case "/":
+ if (operandDouble == 0)
+ operandDouble = 1;
+ valueDouble = valueDouble / operandDouble;
+ break;
+ }
+ if (useDouble)
+ return valueDouble.ToString(CultureInfo.InvariantCulture);
+ else
+ return ((int)valueDouble).ToString();
+ }
+ return value;
+ }
+
+ ///
+ /// Gets coordinate filters from a conversion rule string and returns it without the filter part.
+ ///
+ /// Rule string.
+ /// Filter coordinate X.
+ /// Filter coordinate Y.
+ /// Rule string without coordinate filters.
+ private string GetCoordinateFilters(string ruleString, out int coordFilterX, out int coordFilterY)
+ {
+ string ruleStringFiltered = ruleString;
+ coordFilterX = -1;
+ coordFilterY = -1;
+
+ if (ruleStringFiltered.StartsWith("(") && ruleStringFiltered.Contains(")"))
+ {
+ string coordString = ruleStringFiltered.Substring(1, ruleStringFiltered.IndexOf(")") - 1);
+ string[] coords = coordString.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
+ if (coords.Length >= 2)
+ {
+ coordFilterX = Conversion.GetIntFromString(coords[0].Replace("*", -1 + ""), -1);
+ coordFilterY = Conversion.GetIntFromString(coords[1].Replace("*", -1 + ""), -1);
+ }
+ ruleStringFiltered = ruleStringFiltered.ReplaceFirst("(" + coordString + ")", "");
+ }
+ return ruleStringFiltered;
+ }
+
+ #endregion
+
+ #region conversion
+
+ ///
+ /// Changes theater declaration of current map based on conversion profile.
+ ///
+ public void ConvertTheaterData()
+ {
+ if (!Initialized || string.IsNullOrEmpty(newTheater))
+ return;
+ else if (!IsCurrentTheaterAllowed())
+ {
+ Logger.Warn("Skipping altering theater data - ApplicableTheaters does not contain entry matching map theater.");
+ return;
+ }
+
+ Logger.Info("Attempting to modify theater data of the map file.");
+
+ if (IsValidTheaterName(newTheater))
+ {
+ mapINI.SetKey("Map", "Theater", newTheater);
+ Logger.Info("Map theater declaration changed from '" + mapTheater + "' to '" + newTheater + "'.");
+ MapAltered = true;
+ }
+ }
+
+ ///
+ /// Changes tile data of current map based on conversion profile.
+ ///
+ public void ConvertTileData()
+ {
+ if (!Initialized)
+ return;
+
+ if (tileRules.Count > 0 || removeLevel0ClearTiles || isoMapPack5SortBy != IsoMapPack5SortMode.NotDefined)
+ {
+ string errorMessage = ParseMapPack();
+ if (errorMessage != null)
+ {
+ Logger.Error("Error encountered parsing tile data: Message: " + errorMessage);
+ return;
+ }
+ }
+
+ if (isoMapPack5.Count < 1)
+ return;
+
+ else if (!IsCurrentTheaterAllowed())
+ {
+ Logger.Warn("Skipping altering tile data - ApplicableTheaters does not contain entry matching map theater.");
+ return;
+ }
+
+ bool tileDataAltered = ApplyTileConversionRules();
+ tileDataAltered |= RemoveLevel0ClearTiles() || tileDataAltered;
+ tileDataAltered |= SortIsoMapPack() || tileDataAltered;
+
+ if (tileDataAltered)
+ SaveMapPack();
+
+ MapAltered |= tileDataAltered;
+ }
+
+ ///
+ /// Processes tile data conversion rules.
+ ///
+ /// Returns true if tile data was changed, false if not.
+ private bool ApplyTileConversionRules()
+ {
+ if (tileRules == null || tileRules.Count < 1)
+ return false;
+
+ Logger.Info("Attempting to apply TileRules on map tile data.");
+
+ int originalOffset = 0, newOffset = 0;
+ bool tileDataAltered = false;
+
+ if (theaterTileOffsets.ContainsKey(mapTheater.ToUpper()))
+ {
+ originalOffset = theaterTileOffsets[mapTheater.ToUpper()].Item1;
+ newOffset = theaterTileOffsets[mapTheater.ToUpper()].Item2;
+ if (newOffset == int.MinValue)
+ newOffset = originalOffset;
+ if (originalOffset != 0 && newOffset != 0)
+ Logger.Info("Global tile rule offsets for theater " + mapTheater.ToUpper() + ": " + originalOffset + " (original), " + newOffset + " (new)");
+ }
+
+ foreach (MapTile tile in isoMapPack5)
+ {
+ if (tile.TileIndex < 0 || tile.TileIndex == 65535)
+ tile.TileIndex = 0;
+
+ foreach (TileConversionRule rule in tileRules)
+ {
+ if (rule.CoordinateFilterX > -1 && rule.CoordinateFilterX != tile.X ||
+ rule.CoordinateFilterY > -1 && rule.CoordinateFilterY != tile.Y)
+ continue;
+
+ int ruleOriginalStartIndex = rule.OriginalStartIndex + originalOffset;
+ int ruleOriginalEndIndex = rule.OriginalEndIndex + originalOffset;
+ int ruleNewStartIndex = rule.NewStartIndex + newOffset;
+ int ruleNewEndIndex = rule.NewEndIndex + newOffset;
+
+ if (tile.TileIndex >= ruleOriginalStartIndex && tile.TileIndex <= ruleOriginalEndIndex)
+ {
+ if (rule.HeightOverride > -1)
+ {
+ byte height = (byte)Math.Min(rule.HeightOverride, 14);
+ if (tile.Level != height)
+ {
+ Logger.Debug("TileRules: Tile index " + tile.TileIndex + " at X: " + tile.X + ", Y:" + tile.Y + " - height changed from " + tile.Level + " to " + height + ".");
+ tile.Level = height;
+ }
+ }
+ if (rule.SubIndexOverride > -1)
+ {
+ byte subtileIndex = (byte)Math.Min(rule.SubIndexOverride, 255);
+ if (tile.SubTileIndex != subtileIndex)
+ {
+ Logger.Debug("TileRules: Tile index " + tile.TileIndex + " at X: " + tile.X + ", Y:" + tile.Y + " - sub tile index changed from " + tile.SubTileIndex + " to " + subtileIndex + ".");
+ tile.SubTileIndex = subtileIndex;
+ }
+ }
+ if (rule.IceGrowthOverride > -1)
+ {
+ byte iceGrowth = Convert.ToByte(Convert.ToBoolean(rule.IceGrowthOverride));
+ if (tile.IceGrowth != iceGrowth)
+ {
+ Logger.Debug("TileRules: Tile index " + tile.TileIndex + " at X: " + tile.X + ", Y:" + tile.Y + " - ice growth flag changed from " + tile.IceGrowth + " to " + iceGrowth + ".");
+ tile.IceGrowth = iceGrowth;
+ }
+ }
+
+ if (rule.IsRandomizer)
+ {
+ int newindex = random.Next(ruleNewStartIndex, ruleNewEndIndex);
+ Logger.Debug("TileRules: Tile rule random range: [" + ruleNewStartIndex + "-" + ruleNewEndIndex + "]. Picked: " + newindex);
+ if (newindex != tile.TileIndex)
+ {
+ Logger.Debug("TileRules: Tile index " + tile.TileIndex + " at X: " + tile.X + ", Y:" + tile.Y + " - index changed to " + newindex);
+ tile.TileIndex = newindex;
+ tileDataAltered = true;
+ }
+ break;
+ }
+ else if (ruleNewEndIndex == ruleNewStartIndex)
+ {
+ Logger.Debug("TileRules: Tile index " + tile.TileIndex + " at X: " + tile.X + ", Y: " + tile.Y + " - index changed to " + ruleNewStartIndex);
+ tile.TileIndex = ruleNewStartIndex;
+ tileDataAltered = true;
+ break;
+ }
+ else
+ {
+ Logger.Debug("TileRules: Tile ID " + tile.TileIndex + " at X: " + tile.X + ", Y: " + tile.Y + " - index changed to " +
+ (ruleNewStartIndex + Math.Abs(ruleOriginalStartIndex - tile.TileIndex)));
+ tile.TileIndex = ruleNewStartIndex + Math.Abs(ruleOriginalStartIndex - tile.TileIndex);
+ tileDataAltered = true;
+ break;
+ }
+ }
+ }
+ }
+
+ return tileDataAltered;
+ }
+
+ ///
+ /// Removes level 0 clear tiles from IsoMapPack5 data.
+ ///
+ /// Returns true if tile data was changed, false if not.
+ private bool RemoveLevel0ClearTiles()
+ {
+ if (!Initialized || !removeLevel0ClearTiles)
+ return false;
+
+ Logger.Info("RemoveLevel0ClearTiles set: All tile data with tile index & level set to 0 is removed.");
+
+ List removeTiles = new List();
+
+ foreach (MapTile tile in isoMapPack5)
+ {
+ if (tile.TileIndex < 1 && tile.Level < 1 && tile.SubTileIndex < 1 && tile.IceGrowth < 1)
+ removeTiles.Add(tile);
+ }
+
+ int removeCount = isoMapPack5.RemoveAll(x => removeTiles.Contains(x));
+
+ return removeCount > 0;
+ }
+
+ ///
+ /// Sorts tiles in map pack based on the set sorting method.
+ ///
+ /// Returns true if tile data was changed, false if not.
+ private bool SortIsoMapPack()
+ {
+ if (!Initialized || isoMapPack5.Count < 1 || isoMapPack5SortBy == IsoMapPack5SortMode.NotDefined)
+ return false;
+
+ Logger.Info("IsoMapPack5SortBy set: IsoMapPack5 data will be sorted using sorting mode: " + isoMapPack5SortBy);
+ switch (isoMapPack5SortBy)
+ {
+ case IsoMapPack5SortMode.XLevelTileIndex:
+ isoMapPack5 = isoMapPack5.OrderBy(x => x.X).ThenBy(x => x.Level).ThenBy(x => x.TileIndex).ToList();
+ break;
+ case IsoMapPack5SortMode.XTileIndexLevel:
+ isoMapPack5 = isoMapPack5.OrderBy(x => x.X).ThenBy(x => x.TileIndex).ThenBy(x => x.Level).ToList();
+ break;
+ case IsoMapPack5SortMode.TileIndexXLevel:
+ isoMapPack5 = isoMapPack5.OrderBy(x => x.TileIndex).ThenBy(x => x.X).ThenBy(x => x.Level).ToList();
+ break;
+ case IsoMapPack5SortMode.LevelXTileIndex:
+ isoMapPack5 = isoMapPack5.OrderBy(x => x.Level).ThenBy(x => x.X).ThenBy(x => x.TileIndex).ToList();
+ break;
+ case IsoMapPack5SortMode.X:
+ isoMapPack5 = isoMapPack5.OrderBy(x => x.X).ToList();
+ break;
+ case IsoMapPack5SortMode.Level:
+ isoMapPack5 = isoMapPack5.OrderBy(x => x.Level).ToList();
+ break;
+ case IsoMapPack5SortMode.TileIndex:
+ isoMapPack5 = isoMapPack5.OrderBy(x => x.TileIndex).ToList();
+ break;
+ case IsoMapPack5SortMode.SubTileIndex:
+ isoMapPack5 = isoMapPack5.OrderBy(x => x.SubTileIndex).ToList();
+ break;
+ case IsoMapPack5SortMode.IceGrowth:
+ isoMapPack5 = isoMapPack5.OrderBy(x => x.IceGrowth).ToList();
+ break;
+ case IsoMapPack5SortMode.Y:
+ isoMapPack5 = isoMapPack5.OrderBy(x => x.Y).ToList();
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Changes overlay data of current map based on conversion profile.
+ ///
+ public void ConvertOverlayData()
+ {
+ if (!Initialized || overlayRules == null || overlayRules.Count < 1)
+ return;
+
+ else if (!IsCurrentTheaterAllowed())
+ {
+ Logger.Warn("Skipping altering overlay data - ApplicableTheaters does not contain entry matching map theater.");
+ return;
+ }
+
+ ParseOverlayPacks();
+
+ bool overlayDataAltered = ApplyOverlayConversionRules();
+
+ if (overlayDataAltered)
+ SaveOverlayPacks();
+
+ MapAltered |= overlayDataAltered;
+ }
+
+ ///
+ /// Processes overlay data conversion rules.
+ ///
+ /// Returns true if overlay data was changed, false if not.
+ private bool ApplyOverlayConversionRules()
+ {
+ Logger.Info("Attempting to apply OverlayRules on map overlay data.");
+
+ bool overlayDataChanged = false;
+
+ for (int i = 0; i < OVERLAY_DATA_LENGTH; i++)
+ {
+ /*
+ if (overlayPack[i] == 255)
+ {
+ overlayDataPack[i] = 0;
+ continue;
+ }*/
+ if (overlayPack[i] < 0 || overlayPack[i] > 255)
+ overlayPack[i] = 255;
+ if (overlayDataPack[i] < 0 || overlayDataPack[i] > 255)
+ overlayDataPack[i] = 0;
+
+ int x = i % 512;
+ int y = (i - x) / 512;
+
+ foreach (OverlayConversionRule rule in overlayRules)
+ {
+ if (!rule.IsValid)
+ continue;
+
+ if (rule.CoordinateFilterX > -1 && rule.CoordinateFilterX != x ||
+ rule.CoordinateFilterY > -1 && rule.CoordinateFilterY != y)
+ continue;
+
+ bool overlayPackChanged = ChangeOverlayData(overlayPack, i, x, y, rule.OriginalStartIndex, rule.OriginalEndIndex,
+ rule.NewStartIndex, rule.NewEndIndex, rule.IsRandomizer, false);
+
+ bool overlayDataPackChanged = ChangeOverlayData(overlayDataPack, i, x, y, rule.OriginalStartFrameIndex, rule.OriginalEndFrameIndex,
+ rule.NewStartFrameIndex, rule.NewEndFrameIndex, rule.IsFrameRandomizer, true);
+
+ if (overlayPackChanged || overlayDataPackChanged)
+ {
+ overlayDataChanged = true;
+ break;
+ }
+ }
+ }
+ return overlayDataChanged;
+ }
+
+ ///
+ /// Changes overlay data.
+ ///
+ /// Data collection to change.
+ /// Overlay index in the data collection.
+ /// Overlay X coordinate.
+ /// Overlay Y coordinate.
+ /// Original start index.
+ /// Original end index.
+ /// New start index.
+ /// New end index.
+ /// If true, use a random range.
+ /// If true, treat changes as being made to frame data rather than overlay ID data.
+ /// Returns true if overlay data was changed, false if not.
+ private bool ChangeOverlayData(byte[] data, int index, int x, int y, int originalStartIndex, int originalEndIndex,
+ int newStartIndex, int newEndIndex, bool useRandomRange, bool changeFrameData)
+ {
+ string dataType = changeFrameData ? "frame" : "ID";
+ if (data[index] >= originalStartIndex && data[index] <= originalEndIndex)
+ {
+ if (useRandomRange)
+ {
+ byte newindex = (byte)random.Next(newStartIndex, newEndIndex);
+ Logger.Debug("OverlayRules: Random " + dataType + " range [" + newStartIndex + "-" + newEndIndex + "]. Picked: " + newindex);
+ if (newindex != data[index])
+ {
+ Logger.Debug("OverlayRules: Overlay " + dataType + " " + data[index] + " at array slot " + index + " (X: " + x + ", Y: " + y + ") changed to " +
+ newindex + ".");
+ data[index] = newindex;
+ return true;
+ }
+ }
+ else if (newEndIndex == newStartIndex)
+ {
+ Logger.Debug("OverlayRules: Overlay " + dataType + " " + data[index] + " at array slot " + index + " (X: " + x + ", Y: " + y + ") changed to " +
+ newStartIndex + ".");
+ data[index] = (byte)newStartIndex;
+ return true;
+ }
+ else
+ {
+ Logger.Debug("OverlayRules: Overlay " + dataType + " " + data[index] + " at array slot " + index + " (X: " + x + ", Y: " + y + ") changed to " +
+ (newStartIndex + Math.Abs(originalStartIndex - data[index])) + ".");
+ data[index] = (byte)(newStartIndex + Math.Abs(originalStartIndex - data[index]));
+ return true;
+ }
+ return false;
+ }
+ else
+ return false;
+ }
+
+ ///
+ /// Changes object data of current map based on conversion profile.
+ ///
+ public void ConvertObjectData()
+ {
+ if (!Initialized || overlayRules == null || objectRules.Count < 1) return;
+ else if (mapTheater != null && applicableTheaters != null && !applicableTheaters.Contains(mapTheater))
+ {
+ Logger.Warn("Conversion profile not applicable to maps belonging to this theater. No alterations will be made to the object data.");
+ return;
+ }
+ Logger.Info("Attempting to modify object data of the map file.");
+ ApplyObjectConversionRules("Aircraft");
+ ApplyObjectConversionRules("Units");
+ ApplyObjectConversionRules("Infantry");
+ ApplyObjectConversionRules("Structures");
+ ApplyObjectConversionRules("Terrain");
+ }
+
+ ///
+ /// Processes object data conversion rules.
+ ///
+ /// ID of the object list section to apply the rules to.
+ private void ApplyObjectConversionRules(string sectionName)
+ {
+ if (string.IsNullOrEmpty(sectionName)) return;
+ KeyValuePair[] kvps = mapINI.GetKeyValuePairs(sectionName);
+ if (kvps == null) return;
+ foreach (KeyValuePair kvp in kvps)
+ {
+ foreach (ObjectConversionRule rule in objectRules)
+ {
+ if (rule == null || rule.OriginalName == null)
+ continue;
+
+ GetObjectCoordinates(kvp, sectionName, out int x, out int y);
+
+ if (rule.CoordinateFilterX > -1 && rule.CoordinateFilterX != x ||
+ rule.CoordinateFilterY > -1 && rule.CoordinateFilterY != y)
+ continue;
+
+ if (CheckIfObjectIDMatches(kvp.Value, rule.OriginalName))
+ {
+ if (rule.NewName == null)
+ {
+ Logger.Debug("ObjectRules: Removed " + sectionName + " object with ID '" + rule.OriginalName + "' (X: " + x + ", Y: " + y + ") from the map file.");
+ mapINI.RemoveKey(sectionName, kvp.Key);
+ MapAltered = true;
+ }
+ else
+ {
+ Logger.Debug("ObjectRules: Replaced " + sectionName + " object with ID '" + rule.OriginalName + "' (X: " + x + ", Y: " + y + ") with object of ID '" + rule.NewName + "'.");
+ mapINI.SetKey(sectionName, kvp.Key, kvp.Value.Replace(rule.OriginalName, rule.NewName));
+ MapAltered = true;
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Deletes objects outside map bounds.
+ ///
+ private void DeleteObjectsOutsideBounds()
+ {
+ DeleteObjectsOutsideBoundsFromSection("Units");
+ DeleteObjectsOutsideBoundsFromSection("Infantry");
+ DeleteObjectsOutsideBoundsFromSection("Units");
+ DeleteObjectsOutsideBoundsFromSection("Structures");
+
+ string[] keys = mapINI.GetKeys("Terrain");
+ if (keys == null)
+ return;
+
+ foreach (string key in keys)
+ {
+ int coord = Conversion.GetIntFromString(key, -1);
+ if (coord < 0)
+ continue;
+ int x = coord % 1000;
+ int y = (coord - x) / 1000;
+
+ if (!CoordinateExistsOnMap(x, y))
+ {
+ Logger.Debug("DeleteObjectsOutsideMapBounds: Removed Terrain object " + mapINI.GetKey("Terrain", key, "") +
+ " (key: " + key + ") from cell " + x + "," + y + ".");
+ mapINI.RemoveKey("Terrain", key);
+ MapAltered = true;
+ }
+ }
+ }
+
+ ///
+ /// Deletes specific types of objects outside map bounds.
+ ///
+ ///
+ private void DeleteObjectsOutsideBoundsFromSection(string sectionName)
+ {
+ string[] keys = mapINI.GetKeys(sectionName);
+ if (keys == null) return;
+ foreach (string key in keys)
+ {
+ string[] tmp = mapINI.GetKey(sectionName, key, "").Split(',');
+ if (tmp.Length < 5)
+ continue;
+ int x = Conversion.GetIntFromString(tmp[3], -1);
+ int y = Conversion.GetIntFromString(tmp[4], -1);
+ if (x < 0 || y < 0)
+ continue;
+ if (!CoordinateExistsOnMap(x, y))
+ {
+ string[] values = mapINI.GetKey(sectionName, key, "").Split(',');
+ Logger.Debug("DeleteObjectsOutsideMapBounds: Removed " + sectionName + " object " + (values.Length > 1 ? values[1] : "???") +
+ " (key: " + key + ") from cell " + x + "," + y + ".");
+ mapINI.RemoveKey(sectionName, key);
+ MapAltered = true;
+ }
+ }
+ }
+
+ ///
+ /// Deletes overlays outside map bounds.
+ ///
+ private void DeleteOverlaysOutsideBounds()
+ {
+ if (overlayPack == null || overlayDataPack == null)
+ {
+ ParseOverlayPacks();
+ }
+
+ if (overlayPack == null || overlayDataPack == null)
+ return;
+
+ bool overlayDataAltered = false;
+
+ for (int i = 0; i < overlayPack.Length; i++)
+ {
+ if (overlayPack[i] == 255)
+ continue;
+ int x = i % 512;
+ int y = (i - x) / 512;
+
+ if (!CoordinateExistsOnMap(x, y))
+ {
+ Logger.Debug("DeleteObjectsOutsideMapBounds: Removed overlay (index: " + overlayPack[i] + ") from cell " + x + "," + y + ".");
+ overlayPack[i] = 255;
+ overlayDataPack[i] = 0;
+ overlayDataAltered = true;
+ }
+ }
+
+ if (overlayDataAltered)
+ SaveOverlayPacks();
+
+ MapAltered |= overlayDataAltered;
+ }
+
+ ///
+ /// Fixes tunnels.
+ /// Based on Rampastring's FinalSun Tunnel Fixer.
+ /// https://ppmforums.com/viewtopic.php?t=42008
+ ///
+ private void FixTubesSection()
+ {
+ string[] keys = mapINI.GetKeys("Tubes");
+
+ if (keys == null)
+ return;
+
+ int counter = 0;
+ foreach (string key in keys)
+ {
+ List values = mapINI.GetKey("Tubes", key, string.Empty).Split(',').ToList();
+
+ int index = values.FindIndex(str => str == "-1");
+
+ if (index < 1 || index > values.Count - 3)
+ continue;
+
+ if (counter % 2 == 0)
+ {
+ Logger.Debug("FixTunnels: Set -1 at index " + index + " in tube #" + counter + " to " + values[index - 1] + ".");
+ values[index] = values[index - 1];
+ values.RemoveRange(index + 2, values.Count - (index + 2));
+ }
+ else
+ values.RemoveRange(index + 1, values.Count - (index + 1));
+ mapINI.SetKey("Tubes", key, string.Join(",", values));
+ MapAltered = true;
+ counter++;
+ }
+ }
+
+ ///
+ /// Changes section data of current map based on conversion profile.
+ ///
+ public void ConvertSectionData()
+ {
+ if (!Initialized || sectionRules == null || sectionRules.Count < 1) return;
+ else if (!IsCurrentTheaterAllowed())
+ {
+ Logger.Warn("Skipping altering section data - ApplicableTheaters does not contain entry matching map theater.");
+ return;
+ }
+ Logger.Info("Attempting to modify section data of the map file.");
+ ApplySectionConversionRules();
+ }
+
+ ///
+ /// Processes section data conversion rules.
+ ///
+ private void ApplySectionConversionRules()
+ {
+ foreach (SectionConversionRule rule in sectionRules)
+ {
+ if (string.IsNullOrEmpty(rule.OriginalSection))
+ continue;
+
+ string currentSection = rule.OriginalSection;
+ if (rule.NewSection == null)
+ {
+ Logger.Debug("SectionRules: Removed section '" + rule.OriginalSection + "'.");
+ mapINI.RemoveSection(rule.OriginalSection);
+ MapAltered = true;
+ continue;
+ }
+ else if (rule.NewSection != "")
+ {
+ if (!mapINI.SectionExists(rule.OriginalSection))
+ {
+ Logger.Debug("SectionRules: Added new section '" + rule.NewSection + "'.");
+ mapINI.AddSection(rule.NewSection);
+ }
+ else
+ {
+ Logger.Debug("SectionRules: Renamed section '" + rule.OriginalSection + "' to '" + rule.NewSection + "'.");
+ mapINI.RenameSection(rule.OriginalSection, rule.NewSection);
+ }
+ MapAltered = true;
+ currentSection = rule.NewSection;
+ }
+
+ string currentKey = rule.OriginalKey;
+ if (rule.NewKey == null)
+ {
+ Logger.Debug("SectionRules: Removed key '" + rule.OriginalKey + "' from section '" + currentSection + "'.");
+ mapINI.RemoveKey(currentSection, rule.OriginalKey);
+ MapAltered = true;
+ continue;
+ }
+ else if (rule.NewKey != "")
+ {
+ if (mapINI.GetKey(currentSection, rule.OriginalKey, null) == null)
+ {
+ Logger.Debug("SectionRules: Added a new key '" + rule.NewKey + "' to section '" + currentSection + "'.");
+ mapINI.SetKey(currentSection, rule.NewKey, "");
+ }
+ else
+ {
+ Logger.Debug("SectionRules: Renamed key '" + rule.OriginalKey + "' in section '" + currentSection + "' to '" + rule.NewKey + "'.");
+ mapINI.RenameKey(currentSection, rule.OriginalKey, rule.NewKey);
+ }
+ MapAltered = true;
+ currentKey = rule.NewKey;
+ }
+
+ if (rule.NewValue != "")
+ {
+ Logger.Debug("SectionRules: Section '" + currentSection + "' key '" + currentKey + "' value changed to '" + rule.NewValue + "'.");
+ mapINI.SetKey(currentSection, currentKey, rule.NewValue);
+ MapAltered = true;
+ }
+ }
+ }
+
+ #endregion
+
+ #region helpers
+
+ ///
+ /// Checks if map object declaration matches with specific object ID.
+ ///
+ /// Object declaration from map file.
+ /// Object ID.
+ /// True if a match, otherwise false.
+ private bool CheckIfObjectIDMatches(string objectDeclaration, string objectID)
+ {
+ if (objectDeclaration.Equals(objectID))
+ return true;
+ string[] sp = objectDeclaration.Split(',');
+ if (sp.Length < 2)
+ return false;
+ if (sp[1].Equals(objectID))
+ return true;
+ return false;
+ }
+
+ ///
+ /// Gets coordinates of a map object.
+ ///
+ /// Object declaration key & value.
+ /// Map object section name.
+ /// Will be set to map tile x coordinate of the object.
+ /// Will be set to map tile y coordinate of the object.
+ private void GetObjectCoordinates(KeyValuePair kvp, string sectionName, out int x, out int y)
+ {
+ x = -1;
+ y = -1;
+
+ if (sectionName.Equals("Terrain"))
+ {
+ int coord = Conversion.GetIntFromString(kvp.Key, -1);
+ if (coord < 0)
+ return;
+ x = coord % 1000;
+ y = (coord - x) / 1000;
+ }
+ else
+ {
+ string[] sp = kvp.Value.Split(',');
+ if (sp.Length < 5)
+ return;
+ x = Conversion.GetIntFromString(sp[3], -1);
+ y = Conversion.GetIntFromString(sp[4], -1);
+ }
+ }
+
+ ///
+ /// Checks if location with given coordinates exists within map bounds.
+ ///
+ /// Location X coordinate.
+ /// Location Y coordinate.
+ /// True if location exists, false if not.
+ private bool CoordinateExistsOnMap(int x, int y)
+ {
+ if (!Initialized || coordinateValidityLUT == null ||
+ coordinateValidityLUT.GetLength(0) <= x || coordinateValidityLUT.GetLength(1) <= y)
+ return false;
+
+ return coordinateValidityLUT[x, y];
+ }
+
+ ///
+ /// Calculates valid map coordinates from map width & height and creates a look-up table from them.
+ ///
+ private void CalculateCoordinateValidity()
+ {
+ Logger.Debug("Calculating map coordinate look-up table.");
+
+ int size = Math.Max(mapWidth, mapHeight) * 2 + 1;
+ coordinateValidityLUT = new bool[size, size];
+
+ int yOffset = 0;
+ for (int col = 1; col <= mapWidth; col++)
+ {
+ int startY = mapWidth - yOffset;
+ for (int row = 0; row < mapHeight; row++)
+ {
+ int x = col + row;
+ int y = startY + row;
+ coordinateValidityLUT[x, y] = true;
+ if (col < mapWidth)
+ coordinateValidityLUT[x + 1, y] = true;
+ }
+ yOffset += 1;
+ }
+ }
+
+ ///
+ /// Merges array of string key-value pairs to a single string array containing strings of the keys and values separated by =.
+ ///
+ /// Array of string key-value pairs.
+ /// Array of strings made by merging the keys and values.
+ private string[] MergeKeyValuePairs(KeyValuePair[] keyValuePairs)
+ {
+ if (keyValuePairs == null)
+ return null;
+
+ string[] result = new string[keyValuePairs.Length];
+ for (int i = 0; i < keyValuePairs.Length; i++)
+ {
+ result[i] = keyValuePairs[i].Key + "=" + keyValuePairs[i].Value;
+ }
+ return result;
+ }
+
+ #endregion
+
+ ///
+ /// Lists theater config file data to a text file.
+ ///
+ public void ListTileSetData()
+ {
+ if (!Initialized || theaterConfigINI == null)
+ return;
+
+ List tilesets = ParseTilesetData();
+
+ if (tilesets == null || tilesets.Count < 1)
+ {
+ Logger.Error("Could not parse tileset data from theater configuration file '" +
+ theaterConfigINI.Filename + "'."); return;
+ };
+
+ Logger.Info("Attempting to list tileset data for a theater based on file: '" + theaterConfigINI.Filename + "'.");
+ List lines = new List();
+ int tilecounter = 0;
+ lines.Add("Theater tileset data gathered from file '" + theaterConfigINI.Filename + "'.");
+ lines.Add("");
+ lines.Add("");
+ foreach (Tileset tileset in tilesets)
+ {
+ if (tileset.TilesInSet < 1)
+ {
+ Logger.Debug("ListTileSetData: " + tileset.SetID + " (" + tileset.SetName + ")" + " skipped due to tile count of 0.");
+ continue;
+ }
+ lines.AddRange(tileset.GetPrintableData(tilecounter));
+ lines.Add("");
+ tilecounter += tileset.TilesInSet;
+ Logger.Debug("ListTileSetData: " + tileset.SetID + " (" + tileset.SetName + ")" + " added to the list.");
+ }
+ File.WriteAllLines(FilenameOutput, lines.ToArray());
+ }
+
+ ///
+ /// Parse tileset data from a theater configuration INI file.
+ ///
+ /// List of tilesets.
+ private List ParseTilesetData()
+ {
+ List tilesets = new List();
+
+ if (!Initialized || theaterConfigINI == null)
+ return tilesets;
+
+ string[] sections = theaterConfigINI.GetSections();
+ foreach (string section in sections)
+ {
+ if (!Regex.IsMatch(section, "^TileSet\\d{4}$"))
+ continue;
+
+ Tileset tileset = new Tileset
+ {
+ SetID = section,
+ SetNumber = Conversion.GetIntFromString(section.Substring(7, 4), -1),
+ SetName = theaterConfigINI.GetKey(section, "SetName", "N/A"),
+ FileName = theaterConfigINI.GetKey(section, "FileName", "N/A").ToLower(),
+ TilesInSet = Conversion.GetIntFromString(theaterConfigINI.GetKey(section, "TilesInSet", "0"), 0)
+ };
+
+ if (tileset.SetNumber == -1)
+ continue;
+
+ tilesets.Add(tileset);
+ }
+
+ return tilesets;
+ }
+ }
+}
diff --git a/MapTool/MapTool.csproj b/MapTool/MapTool.csproj
index e9bd118..b15c392 100644
--- a/MapTool/MapTool.csproj
+++ b/MapTool/MapTool.csproj
@@ -1,131 +1,131 @@
-
-
-
- Debug
- x86
- 8.0.30703
- 2.0
- {263073ED-EAD2-493C-BC60-705DB71E2624}
- Exe
- Properties
- MapTool
- MapTool
- v4.0
- 512
-
- publish\
- true
- Disk
- false
- Foreground
- 7
- Days
- false
- false
- true
- 0
- 1.0.0.%2a
- false
- false
- true
-
-
- x86
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
- true
-
-
- x86
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
- true
-
-
- true
- bin\Debug\
- DEBUG;TRACE
- true
- full
- AnyCPU
- prompt
- MinimumRecommendedRules.ruleset
-
-
- bin\Release\
- TRACE
- true
- true
- pdbonly
- AnyCPU
- prompt
- MinimumRecommendedRules.ruleset
-
-
-
- False
- ..\References\StarkkuUtils.dll
-
-
-
-
-
-
-
-
- Properties\SharedAssemblyInfo.cs
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- False
- .NET Framework 3.5 SP1 Client Profile
- false
-
-
- False
- .NET Framework 3.5 SP1
- true
-
-
- False
- Windows Installer 3.1
- true
-
-
-
-
-
-
-
-
+
+
+
+ Debug
+ x86
+ 8.0.30703
+ 2.0
+ {263073ED-EAD2-493C-BC60-705DB71E2624}
+ Exe
+ Properties
+ MapTool
+ MapTool
+ v4.0
+ 512
+
+ publish\
+ true
+ Disk
+ false
+ Foreground
+ 7
+ Days
+ false
+ false
+ true
+ 0
+ 1.0.0.%2a
+ false
+ false
+ true
+
+
+ x86
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ true
+
+
+ x86
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ true
+
+
+ true
+ bin\Debug\
+ DEBUG;TRACE
+ true
+ full
+ AnyCPU
+ prompt
+ MinimumRecommendedRules.ruleset
+
+
+ bin\Release\
+ TRACE
+ true
+ true
+ pdbonly
+ AnyCPU
+ prompt
+ MinimumRecommendedRules.ruleset
+
+
+
+ False
+ ..\References\StarkkuUtils.dll
+
+
+
+
+
+
+
+
+ Properties\SharedAssemblyInfo.cs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ False
+ .NET Framework 3.5 SP1 Client Profile
+ false
+
+
+ False
+ .NET Framework 3.5 SP1
+ true
+
+
+ False
+ Windows Installer 3.1
+ true
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MapTool/Program.cs b/MapTool/Program.cs
index 72587da..994b021 100644
--- a/MapTool/Program.cs
+++ b/MapTool/Program.cs
@@ -1,161 +1,161 @@
-/*
- * Copyright 2017-2020 by Starkku
- * This file is part of MapTool, which is free software. It is made
- * available to you under the terms of the GNU General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version. For more
- * information, see COPYING.
- */
-
-using System;
-using System.IO;
-using System.Reflection;
-using MapTool.Utility;
-using StarkkuUtils.Utilities;
-using NDesk.Options;
-
-namespace MapTool
-{
- class Program
- {
- private static OptionSet options;
- private static Settings settings = new Settings();
-
- static void Main(string[] args)
- {
- options = new OptionSet
- {
- { "h|?|help", "Show help", v => settings.ShowHelp = true},
- { "i|infile=", "Input file.", v => settings.FileInput = v},
- { "o|outfile=", "Output file.", v => settings.FileOutput = v},
- { "l|list", "List theater data based on input theater config file.", v => settings.List = true},
- { "p|profilefile=", "Conversion profile file. This also enables the conversion logic.", v => settings.FileConfig = v},
- { "g|log", "If set, writes a log to a file in program directory.", v => settings.WriteLogFile = true},
- { "d|debug", "If set, shows debug-level logging in console window.", v => settings.ShowDebugLogging = true}
- };
- try
- {
- options.Parse(args);
- }
- catch (Exception e)
- {
- ConsoleColor defcolor = Console.ForegroundColor;
- Console.ForegroundColor = ConsoleColor.Red;
- Console.WriteLine("Encountered an error while parsing command-line parameters. Message: " + e.Message);
- Console.ForegroundColor = defcolor;
- ShowHelp();
- return;
- }
- InitLogger();
-
-#if !DEBUG
- AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
-#endif
- bool error = false;
-
- if (settings.ShowHelp)
- {
- ShowHelp();
- return;
- }
- if (string.IsNullOrEmpty(settings.FileConfig) && !settings.List)
- {
- Logger.Error("Not enough parameters. Must provide either -l or -p.");
- ShowHelp();
- return;
- }
- else if (settings.List)
- {
- Logger.Info("Mode set (-l): List Tile Data.");
- }
- else
- {
- Logger.Info("Mode set (-p): Apply Conversion Profile.");
- }
- if (string.IsNullOrEmpty(settings.FileInput))
- {
- Logger.Error("No valid input file specified.");
- ShowHelp();
- error = true;
- }
- else if (!string.IsNullOrEmpty(settings.FileInput) && !File.Exists(settings.FileInput))
- {
- Logger.Error("Specified input file does not exist.");
- ShowHelp();
- error = true;
- }
- if (error) return;
- else Logger.Info("Input file path OK.");
- if (settings.List)
- {
- if (string.IsNullOrEmpty(settings.FileOutput))
- {
- Logger.Warn("No output file available. Using input as output.");
- settings.FileOutput = Path.ChangeExtension(settings.FileInput, ".txt");
- }
- }
- else
- Logger.Info("Output file path OK.");
-
- MapTool map = new MapTool(settings.FileInput, settings.FileOutput, settings.FileConfig, settings.List);
- if (map.Initialized)
- Logger.Info("MapTool initialized.");
- else
- {
- Logger.Error("MapTool could not be initialized.");
- return;
- }
-
- if (settings.List)
- {
- map.ListTileSetData();
- return;
- }
- else
- {
- map.ConvertTheaterData();
- map.ConvertTileData();
- map.ConvertOverlayData();
- map.ConvertObjectData();
- map.ConvertSectionData();
- }
-
- map.Save();
- }
-
- private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
- {
- Logger.Error("Error encountered. Message: " + (e.ExceptionObject as Exception).Message);
- Logger.Debug((e.ExceptionObject as Exception).StackTrace);
- Environment.Exit(1);
- }
-
- ///
- /// Initializes the logger.
- ///
- private static void InitLogger()
- {
- string filename = AppDomain.CurrentDomain.BaseDirectory + Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location) + ".log";
- bool enableDebugLogging = settings.ShowDebugLogging;
-#if DEBUG
- enableDebugLogging = true;
-#endif
- Logger.Initialize(filename, settings.WriteLogFile, enableDebugLogging);
- }
-
- ///
- /// Shows help for command line arguments.
- ///
- private static void ShowHelp()
- {
- Console.ForegroundColor = ConsoleColor.Gray;
- Console.Write("Usage: ");
- Console.WriteLine("");
- var sb = new System.Text.StringBuilder();
- var sw = new StringWriter(sb);
- options.WriteOptionDescriptions(sw);
- Console.WriteLine(sb.ToString());
- }
-
- }
-}
+/*
+ * Copyright 2017-2020 by Starkku
+ * This file is part of MapTool, which is free software. It is made
+ * available to you under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version. For more
+ * information, see COPYING.
+ */
+
+using System;
+using System.IO;
+using System.Reflection;
+using MapTool.Utility;
+using StarkkuUtils.Utilities;
+using NDesk.Options;
+
+namespace MapTool
+{
+ class Program
+ {
+ private static OptionSet options;
+ private static Settings settings = new Settings();
+
+ static void Main(string[] args)
+ {
+ options = new OptionSet
+ {
+ { "h|?|help", "Show help", v => settings.ShowHelp = true},
+ { "i|infile=", "Input file.", v => settings.FileInput = v},
+ { "o|outfile=", "Output file.", v => settings.FileOutput = v},
+ { "l|list", "List theater data based on input theater config file.", v => settings.List = true},
+ { "p|profilefile=", "Conversion profile file. This also enables the conversion logic.", v => settings.FileConfig = v},
+ { "g|log", "If set, writes a log to a file in program directory.", v => settings.WriteLogFile = true},
+ { "d|debug", "If set, shows debug-level logging in console window.", v => settings.ShowDebugLogging = true}
+ };
+ try
+ {
+ options.Parse(args);
+ }
+ catch (Exception e)
+ {
+ ConsoleColor defcolor = Console.ForegroundColor;
+ Console.ForegroundColor = ConsoleColor.Red;
+ Console.WriteLine("Encountered an error while parsing command-line parameters. Message: " + e.Message);
+ Console.ForegroundColor = defcolor;
+ ShowHelp();
+ return;
+ }
+ InitLogger();
+
+#if !DEBUG
+ AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
+#endif
+ bool error = false;
+
+ if (settings.ShowHelp)
+ {
+ ShowHelp();
+ return;
+ }
+ if (string.IsNullOrEmpty(settings.FileConfig) && !settings.List)
+ {
+ Logger.Error("Not enough parameters. Must provide either -l or -p.");
+ ShowHelp();
+ return;
+ }
+ else if (settings.List)
+ {
+ Logger.Info("Mode set (-l): List Tile Data.");
+ }
+ else
+ {
+ Logger.Info("Mode set (-p): Apply Conversion Profile.");
+ }
+ if (string.IsNullOrEmpty(settings.FileInput))
+ {
+ Logger.Error("No valid input file specified.");
+ ShowHelp();
+ error = true;
+ }
+ else if (!string.IsNullOrEmpty(settings.FileInput) && !File.Exists(settings.FileInput))
+ {
+ Logger.Error("Specified input file does not exist.");
+ ShowHelp();
+ error = true;
+ }
+ if (error) return;
+ else Logger.Info("Input file path OK.");
+ if (settings.List)
+ {
+ if (string.IsNullOrEmpty(settings.FileOutput))
+ {
+ Logger.Warn("No output file available. Using input as output.");
+ settings.FileOutput = Path.ChangeExtension(settings.FileInput, ".txt");
+ }
+ }
+ else
+ Logger.Info("Output file path OK.");
+
+ MapTool map = new MapTool(settings.FileInput, settings.FileOutput, settings.FileConfig, settings.List);
+ if (map.Initialized)
+ Logger.Info("MapTool initialized.");
+ else
+ {
+ Logger.Error("MapTool could not be initialized.");
+ return;
+ }
+
+ if (settings.List)
+ {
+ map.ListTileSetData();
+ return;
+ }
+ else
+ {
+ map.ConvertTheaterData();
+ map.ConvertTileData();
+ map.ConvertOverlayData();
+ map.ConvertObjectData();
+ map.ConvertSectionData();
+ }
+
+ map.Save();
+ }
+
+ private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
+ {
+ Logger.Error("Error encountered. Message: " + (e.ExceptionObject as Exception).Message);
+ Logger.Debug((e.ExceptionObject as Exception).StackTrace);
+ Environment.Exit(1);
+ }
+
+ ///
+ /// Initializes the logger.
+ ///
+ private static void InitLogger()
+ {
+ string filename = AppDomain.CurrentDomain.BaseDirectory + Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location) + ".log";
+ bool enableDebugLogging = settings.ShowDebugLogging;
+#if DEBUG
+ enableDebugLogging = true;
+#endif
+ Logger.Initialize(filename, settings.WriteLogFile, enableDebugLogging);
+ }
+
+ ///
+ /// Shows help for command line arguments.
+ ///
+ private static void ShowHelp()
+ {
+ Console.ForegroundColor = ConsoleColor.Gray;
+ Console.Write("Usage: ");
+ Console.WriteLine("");
+ var sb = new System.Text.StringBuilder();
+ var sw = new StringWriter(sb);
+ options.WriteOptionDescriptions(sw);
+ Console.WriteLine(sb.ToString());
+ }
+
+ }
+}
diff --git a/SharedAssemblyInfo.cs b/SharedAssemblyInfo.cs
index 8d79eac..dd9e767 100644
--- a/SharedAssemblyInfo.cs
+++ b/SharedAssemblyInfo.cs
@@ -1,8 +1,8 @@
-using System.Reflection;
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyCopyright("Copyright © Starkku 2017-2020")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-[assembly: AssemblyVersion("1.1.2.2")]
-[assembly: AssemblyFileVersion("1.1.2.2")]
-[assembly: AssemblyInformationalVersion("1.1.2.2")]
+using System.Reflection;
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyCopyright("Copyright © Starkku 2017-2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: AssemblyVersion("1.1.2.2")]
+[assembly: AssemblyFileVersion("1.1.2.2")]
+[assembly: AssemblyInformationalVersion("1.1.2.2")]