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")]