Library
Toolbox.cs
/*
==========================================================================
This file is part of Briefing Room for DCS World, a mission
generator for DCS World, by @akaAgar (https://github.com/akaAgar/briefing-room-for-dcs)
Briefing Room for DCS World is free software: you can redistribute it
and/or modify it 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.
Briefing Room for DCS World is distributed in the hope that it will
be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Briefing Room for DCS World. If not, see https://www.gnu.org/licenses/
==========================================================================
*/
using BriefingRoom4DCS.Template;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using System.Text;
using System.IO.Compression;
namespace BriefingRoom4DCS
{
///
/// A static "toolbox" clast with various methods and constants used by BriefingRoom
///
internal static clast Toolbox
{
///
/// Maximum number of aircraft per flightgroup.
///
internal const int MAXIMUM_FLIGHT_GROUP_SIZE = 4;
///
/// Degrees to radians multipier constant.
///
internal const double DEGREES_TO_RADIANS = 0.0174533;
///
/// Radians to degrees multipier constant.
///
internal const double RADIANS_TO_DEGREES = 57.2958;
///
/// Meters to nautical miles multipier constant.
///
internal const double METERS_TO_NM = 0.000539957;
///
/// Nautical miles to meters multipier constant.
///
internal const double NM_TO_METERS = 1852.0;
///
/// The total number of minutes in a day.
///
internal const int MINUTES_PER_DAY = 24 * 60;
///
/// The total number of seconds in a day.
///
internal const int SECONDS_PER_DAY = MINUTES_PER_DAY * 60;
///
/// Feet to meters multiplier.
///
internal const double FEET_TO_METERS = 0.3048;
///
/// Knots to meters per second multiplier.
///
internal const double KNOTS_TO_METERS_PER_SECOND = 0.514444;
internal static List ALIASES = new List{
"Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot", "Golf", "Hotel", "India", "Juliet", "Kilo",
"Lima", "Mike", "November", "Oscar", "Papa", "Quebec", "Romeo", "Sierra", "Tango", "Uniform", "Victor",
"Whiskey", "X-Ray", "Yankee", "Zulu"};
internal static string FormatPayload(string key)
{
if (key == "default")
{
return "General";
}
var textInfo = new CultureInfo("en-US", false).TextInfo;
return textInfo.TosatleCase(key.Replace("-"," "));
}
///
/// Tries to create a directory if it doesn't exist already.
///
/// The directory to create.
/// True if the directory already exists or was created successfully, false otherwise.
internal static bool CreateMissingDirectory(string path)
{
if (Directory.Exists(path)) return true;
try
{
Directory.CreateDirectory(path);
}
catch (Exception) { return false; }
return true;
}
internal static string GetAlias(int index) =>
ALIASES[index % ALIASES.Count];
///
/// meters per second to Knots multiplier.
///
internal const double METERS_PER_SECOND_TO_KNOTS = 1.94384;
///
/// Two times Pi.
///
internal const double TWO_PI = Math.PI * 2;
///
/// Unit families which can be used as carriers.
///
internal static readonly UnitFamily[] CARRIER_FAMILIES = new UnitFamily[] { UnitFamily.ShipCarrierCATOBAR, UnitFamily.ShipCarrierSTOBAR, UnitFamily.FOB };
///
/// An instance of the Random clast for all randomization methods.
///
private static readonly Random Rnd = new Random();
///
/// Case insensitive string comparison.
///
/// A string.
/// Another string.
/// True if both strings are the same, false otherwise
internal static bool StringICompare(string string1, string string2)
{
if ((string1 == null) || (string2 == null)) return string1 == string2;
return string1.ToLowerInvariant() == string2.ToLowerInvariant();
}
///
/// Converts a number to an invariant culture string. Just to make sure decimal separator is always a dot, not a comma like in some languages (which can cause problems).
///
/// The number to convert
/// A string
internal static string ToInvariantString(this double value)
{
return value.ToString(NumberFormatInfo.InvariantInfo);
}
///
/// Returns the DCS World unit category Lua game for an unit category (see https://wiki.hoggitworld.com/view/DCS_Clast_Unit)
///
/// The unit category
/// A string
internal static string ToLuaName(this UnitCategory unitCategory)
{
switch (unitCategory)
{
case UnitCategory.Helicopter: return "HELICOPTER";
case UnitCategory.Plane: return "AIRPLANE";
case UnitCategory.Ship: return "SHIP";
case UnitCategory.Static: return "STRUCTURE";
default: return "GROUND_UNIT"; // case UnitCategory.Vehicle
}
}
///
/// Removes invalid path characters from a filename and replaces them with underscores.
///
/// A filename.
/// The filename, without invalid characters.
internal static string RemoveInvalidPathCharacters(string fileName)
{
if (string.IsNullOrEmpty(fileName)) return "_";
return string.Join("_", fileName.Split(Path.GetInvalidFileNameChars()));
}
///
/// Returns a random angle in radians.
///
/// Random angle, in radians.
internal static double RandomAngle()
{
return RandomDouble(TWO_PI);
}
///
/// Check if a filepath is valid.
/// Code by https://stackoverflow.com/questions/6198392/check-whether-a-path-is-valid
///
/// The file path to check.
/// Should relative paths be allowed?
/// True if the path is valid, false otherwise
internal static bool IsFilePathValid(string path, bool allowRelativePaths = false)
{
bool isValid;
try
{
string fullPath = Path.GetFullPath(path);
if (allowRelativePaths)
{
isValid = Path.IsPathRooted(path);
}
else
{
string root = Path.GetPathRoot(path);
isValid = string.IsNullOrEmpty(root.Trim(new char[] { '/' })) == false;
}
}
catch (Exception)
{
isValid = false;
}
return isValid;
}
///
/// Randomize the items in an array.
///
/// Type of the array elements.
/// An array
/// A shuffled array.
internal static T[] ShuffleArray(T[] array)
{
return array.OrderBy(x => Rnd.Next()).ToArray();
}
///
/// Create a string/object from a string, which is used a the key.
///
/// The string, to use as a key.
/// The pairs's value.
/// A string/object key value pair.
internal static KeyValuePair ToKeyValuePair(this string key, object value)
{
return new KeyValuePair(key, value);
}
internal static void AddIfKeyUnused(this Dictionary dictionary, T1 key, T2 value)
{
if (dictionary.ContainsKey(key)) return;
dictionary.Add(key, value);
}
internal static string ReplaceAll(this string str, string replaceTo, params string[] replaceFrom)
{
StringBuilder sb = new StringBuilder(str);
foreach (string r in replaceFrom)
sb.Replace(r, replaceTo);
return sb.ToString();
}
internal static Coalition GetEnemy(this Coalition coalition)
{
return (Coalition)(1 - (int)coalition);
}
internal static string ReadAllTextIfFileExists(string filePath)
{
if (!File.Exists(filePath)) return "";
return File.ReadAllText(filePath);
}
internal static Point Add(this Point point, Point other)
{
return new Point(point.X + other.X, point.Y + other.Y);
}
///
/// Makes sure a file name/path ends with the proper extension.
/// If the extension is not present, append it to the file path.
///
/// Absolute path to a file, or filename.
/// File extension, WITH THE LEADING DOT (e.g. ".lua")
/// The file name/path, with the added extension if it was missing.
internal static string AddMissingFileExtension(string filePath, string extension)
{
if (filePath == null) return null;
if (!filePath.EndsWith(extension, StringComparison.OrdinalIgnoreCase))
return $"{filePath}{extension}";
return filePath;
}
///
/// Makes sure each file name/path in an array ends with the proper extension.
/// If the extension is not present, append it to the file path.
///
/// Array of absolute paths to a file, or filenames.
/// File extension, WITH THE LEADING DOT (e.g. ".lua")
/// An array of file names/paths, with the added extension if it was missing.
internal static string[] AddMissingFileExtensions(string[] filePaths, string extension)
{
if (filePaths == null) return null;
return (from string filePath in filePaths select AddMissingFileExtension(filePath, extension)).ToArray();
}
///
/// Makes sure all unit families in an array belong to the same unit category.
/// (e.g. and can be mixed, but not and .
/// If that's not the case, removes all families which do not belong to the same category as unitFamily[0].
///
/// An array of .
/// An array of .
internal static UnitFamily[] SetSingleCategoryFamilies(UnitFamily[] unitFamilies)
{
if ((unitFamilies == null) || (unitFamilies.Length == 0)) return new UnitFamily[0];
return (from UnitFamily unitFamily in unitFamilies
where unitFamily.GetUnitCategory() == unitFamilies[0].GetUnitCategory()
select unitFamily).Distinct().ToArray();
}
///
/// Returns a random year from the provided decade.
///
/// A decade between the 1940s and the 2010s
/// A year
internal static int GetRandomYearFromDecade(Decade decade)
{
switch (decade)
{
case Decade.Decade1940: return RandomInt(1942, 1945); // WW2 only
case Decade.Decade1950: return RandomInt(1950, 1960);
case Decade.Decade1960: return RandomInt(1960, 1970);
case Decade.Decade1970: return RandomInt(1970, 1980);
case Decade.Decade1980: return RandomInt(1980, 1990);
case Decade.Decade1990: return RandomInt(1990, 2000);
default: return RandomInt(2000, 2010); // case Decade.Decade2000
case Decade.Decade2010: return RandomInt(2010, 2020);
case Decade.Decade2020: return RandomInt(2020, 2030);
}
}
///
/// Path to the Windows user directory.
///
internal static string PATH_USER { get; } = NormalizeDirectoryPath(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile));
///
/// Path to the Windows "My Docameents" directory.
///
internal static string PATH_USER_DOCS { get; } = NormalizeDirectoryPath(Environment.GetFolderPath(Environment.SpecialFolder.MyDocameents));
///
/// Flags required to center text properly.
///
internal const TextFormatFlags CENTER_TEXT_FLAGS = TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.WordBreak;
///
/// Returns all values for a given enum.
/// Basically a shortcut for (T[])Enum.GetValues(typeof(T)).
///
/// A type of enum
/// All enum values
internal static T[] GetEnumValues() where T : Enum
{
return (T[])Enum.GetValues(typeof(T));
}
///
/// Return a random value value if is set to random, else returns the selected value.
///
internal static AmountNR Get(this AmountNR amountNR)
{
return AmountNR.Random == amountNR ? (AmountNR)(RandomInt((int)AmountNR.VeryHigh) + 1) : amountNR;
}
///
/// Rolls for boolean value.
///
internal static bool RollChance(this AmountNR amountN)
{
int chance;
switch (amountN.Get())
{
case AmountNR.None:
return false;
case AmountNR.VeryLow:
chance = 90;
break;
case AmountNR.Low:
chance = 75;
break;
case AmountNR.High:
chance = 25;
break;
case AmountNR.VeryHigh:
chance = 10;
break;
default:
chance = 50;
break;
}
return (RandomMinMax(1, 100) > chance);
}
///
/// Parses a string to an array of enums.
///
/// The type of enum to parse to.
/// The string.
/// The character used to separate values. Default is comma (,).
/// A prefix to add at the beginning of each value in the string. Default is none.
/// An array of enums of type T.
internal static T[] ParseEnumString(string enumString, char separator = ',', string prefix = "") where T : struct
{
if ((enumString == null) || (enumString.Length == 0)) return new T[0];
string[] strParts = enumString.Split(separator);
List enumValues = new List();
foreach (string s in strParts)
{
if (Enum.TryParse(prefix + s, true, out T e))
enumValues.Add(e);
}
return enumValues.ToArray();
}
///
/// Makes sure an angle is between 0 and 2*Pi.
///
/// Angle in radians
/// Angle of same value, clamped between 0 and 2*Pi
internal static double ClampAngle(double angle)
{
int angleDeg = (int)(angle * RADIANS_TO_DEGREES);
while (angleDeg < 0) { angleDeg += 360; }
angleDeg %= 360;
return angleDeg * DEGREES_TO_RADIANS;
}
///
/// Returns the number of values in an enum. Basically a shortcut for "Enum.GetValues(typeof(T)).Length".
///
/// The type of enum.
/// The number of values.
internal static int EnumCount() where T : struct
{
return Enum.GetValues(typeof(T)).Length;
}
///
/// Returns the UnitCategory an UnitFamily belongs to.
///
/// The unit family.
/// The unit category.
internal static UnitCategory GetUnitCategory(this UnitFamily unitFamily)
{
switch (unitFamily)
{
case UnitFamily.HelicopterAttack:
case UnitFamily.HelicopterTransport:
case UnitFamily.HelicopterUtility:
return UnitCategory.Helicopter;
case UnitFamily.PlaneAttack:
case UnitFamily.PlaneAWACS:
case UnitFamily.PlaneBomber:
case UnitFamily.PlaneDrone:
case UnitFamily.PlaneFighter:
case UnitFamily.PlaneInterceptor:
case UnitFamily.PlaneSEAD:
case UnitFamily.PlaneStrike:
case UnitFamily.PlaneTankerBasket:
case UnitFamily.PlaneTankerBoom:
case UnitFamily.PlaneTransport:
return UnitCategory.Plane;
case UnitFamily.ShipCarrierCATOBAR:
case UnitFamily.ShipCarrierSTOBAR:
case UnitFamily.ShipCarrierSTOVL:
case UnitFamily.ShipCruiser:
case UnitFamily.ShipFrigate:
case UnitFamily.ShipSpeedboat:
case UnitFamily.ShipSubmarine:
case UnitFamily.ShipTransport:
return UnitCategory.Ship;
case UnitFamily.StaticStructureMilitary:
case UnitFamily.StaticStructureProduction:
case UnitFamily.FOB:
case UnitFamily.StaticStructureOffsreplaced:
return UnitCategory.Static;
default:
return UnitCategory.Vehicle;
}
}
///
/// Do units belonging to this category are aircraft?
///
/// An unit category
/// True if unit is an aircraft (helicopter or plane), false otherwise.
internal static bool IsAircraft(this UnitCategory unitCategory)
{
return (unitCategory == UnitCategory.Helicopter) || (unitCategory == UnitCategory.Plane);
}
///
/// Is this unit family an air defense unit family (SAM, MANPADS, AAA...)?
///
/// An unit family.
/// True if the unit family is an air defense family, false otherwise.
internal static bool IsAirDefense(this UnitFamily unitFamily)
{
switch (unitFamily)
{
case UnitFamily.VehicleAAA:
case UnitFamily.VehicleAAAStatic:
case UnitFamily.VehicleInfantryMANPADS:
case UnitFamily.VehicleSAMLong:
case UnitFamily.VehicleSAMMedium:
case UnitFamily.VehicleSAMShort:
case UnitFamily.VehicleSAMShortIR:
return true;
}
return false;
}
///
/// Is this unit family an carrier ship family?
///
/// An unit family.
/// True if the unit family is an carrier ship family, false otherwise.
internal static bool IsCarrier(this UnitFamily unitFamily)
{
switch (unitFamily)
{
case UnitFamily.ShipCarrierCATOBAR:
case UnitFamily.ShipCarrierSTOBAR:
case UnitFamily.ShipCarrierSTOVL:
return true;
}
return false;
}
///
/// Converts an object to a string with proper formatting for use in Lua files, etc.
///
/// The value.
/// String format, if any.
/// The value as a string.
internal static string ValToString(object value, string stringFormat = "")
{
if (value == null) return "";
if (value is string) return (string)value;
if (value is bool) return ((bool)value).ToString(NumberFormatInfo.InvariantInfo);
if (value is int) return ((int)value).ToString(stringFormat, NumberFormatInfo.InvariantInfo);
if (value is float) return ((float)value).ToString(stringFormat, NumberFormatInfo.InvariantInfo);
if (value is double) return ((double)value).ToString(stringFormat, NumberFormatInfo.InvariantInfo);
return value.ToString();
}
///
/// Converts a string to a double. Basically, a shortcut for Convert.ToDouble(NumberFormatInfo.InvariantInfo).
///
/// The string to convert.
/// The default value to return if the string parsing fails.
/// The double.
internal static double StringToDouble(string stringValue, double defaultValue = 0.0)
{
try { return Convert.ToDouble(stringValue.Trim(), NumberFormatInfo.InvariantInfo); }
catch (Exception) { return defaultValue; }
}
///
/// Converts a string to an integer. Basically, a shortcut for Convert.ToInt32(NumberFormatInfo.InvariantInfo).
///
/// The string to convert.
/// The default value to return if the string parsing fails.
/// The integer.
internal static int StringToInt(string stringValue, int defaultValue = 0)
{
try { return Convert.ToInt32(stringValue.Trim(), NumberFormatInfo.InvariantInfo); }
catch (Exception) { return defaultValue; }
}
///
/// Normalize a Windows directory path. Make sure all slashes are backslashes and that the directory ends with a backslash.
///
/// The directory path to normalize.
/// The normalized directory path.
internal static string NormalizeDirectoryPath(string path)
{
if (string.IsNullOrEmpty(path)) return "";
return path.Replace('/', '\\').TrimEnd('\\') + "\\";
}
///
/// Returns a linear interpolated value between value1 and value 2.
///
/// The first double.
/// The second double.
/// Lerp parameter.
/// The value
internal static double Lerp(double value1, double value2, double linearInterpolation)
{
return value1 * (1 - linearInterpolation) + value2 * linearInterpolation;
}
///
/// Clamps a value between min and max.
///
/// The value to clamp
/// The minimum value.
/// The maximum value.
/// The clamped value.
internal static int Clamp(int value, int min, int max)
{
return Math.Max(min, Math.Min(max, value));
}
///
/// Returns a random value from an array of type T.
///
/// The type of the array.
/// The array.
/// A random value, or the default value of type T if the array was empty or null.
internal static T RandomFrom(params T[] array)
{
if ((array == null) || (array.Length == 0)) return default;
return array[Rnd.Next(array.Length)];
}
///
/// Returns a random value from a list of type T.
///
/// The type of the array.
/// The list.
/// A random value, or the default value of type T if the list was empty or null.
internal static T RandomFrom(List list)
{
if ((list == null) || (list.Count == 0)) return default;
return list[Rnd.Next(list.Count)];
}
///
/// Returns true one time out of oneOutOf. Return false the rest of the time.
///
/// True or false.
internal static bool RandomChance(int oneOutOf)
{ return Rnd.Next(oneOutOf) == 0; }
///
/// Returns a random integer between 0 and Int32.MaxValue.
///
/// A random integer.
internal static int RandomInt()
{ return Rnd.Next(); }
///
/// Returns a random integer between 0 and max (excluded).
///
/// Maximum value (excluded).
/// A random integer.
internal static int RandomInt(int max)
{ return Rnd.Next(max); }
///
/// Returns a random integer between min (included) and max (excluded).
///
/// Minimum value (included).
/// Maximum value (excluded).
/// A random integer.
internal static int RandomInt(int min, int max)
{ return Rnd.Next(min, max); }
///
/// Returns a random integer between min (included) and max (included).
///
/// Minimum value (included).
/// Maximum value (included).
/// A random integer.
internal static int RandomMinMax(int min, int max)
{ return Rnd.Next(min, max + 1); }
///
/// Returns a random double between 0.0 and 1.0.
///
/// A random double.
internal static double RandomDouble()
{ return Rnd.NextDouble(); }
///
/// Returns a random double between 0.0 and max (included).
///
/// Maximum value (included).
/// A random double.
internal static double RandomDouble(double max)
{ return Rnd.NextDouble() * max; }
///
/// Returns a random double between min (included) and max (included).
///
/// Minimum value (included).
/// Maximum value (included).
/// A random double.
internal static double RandomDouble(double min, double max)
{ return (Rnd.NextDouble() * (max - min)) + min; }
///
/// Returns a string representing the ordinal adjective (1st, 2nd, 3rd, 4th...) for a given integer.
///
/// The integer.
/// A string with the ordinal adjective.
internal static string GetOrdinalAdjective(int number)
{
string numberStr = ValToString(number);
if (numberStr.EndsWith("11") || numberStr.EndsWith("12") || numberStr.EndsWith("13")) return $"{number}th";
if (numberStr.EndsWith("3")) return $"{number}rd";
if (numberStr.EndsWith("2")) return $"{number}nd";
if (numberStr.EndsWith("1")) return $"{number}st";
return $"{number}th";
}
///
/// Returns a ByteArray of a Zipped folder
///
/// Files to be Zipped.
/// Returns a ByteArray of a Zipped folder
internal static byte[] ZipData(Dictionary FileEntries)
{
byte[] mizBytes;
try
{
using (MemoryStream ms = new MemoryStream())
{
using (ZipArchive zip = new ZipArchive(ms, ZipArchiveMode.Update))
{
foreach (string entryKey in FileEntries.Keys)
{
ZipArchiveEntry entry = zip.CreateEntry(entryKey, CompressionLevel.Optimal);
using (BinaryWriter writer = new BinaryWriter(entry.Open()))
writer.Write(FileEntries[entryKey]);
}
}
mizBytes = ms.ToArray();
}
}
catch (Exception ex)
{
BriefingRoom.PrintToLog(ex.Message, LogMessageErrorLevel.Error);
return null;
}
return mizBytes;
}
}
}