using Lidgren.Network; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Reflection; using System.Text; using Microsoft.Xna.Framework; namespace Barotrauma { public class Pair { public T1 First { get; set; } public T2 Second { get; set; } public Pair(T1 first, T2 second) { First = first; Second = second; } } public class Triplet { public T1 First { get; set; } public T2 Second { get; set; } public T3 Third { get; set; } public Triplet(T1 first, T2 second, T3 third) { First = first; Second = second; Third = third; } } public static partial class ToolBox { static internal class Epoch { private static readonly DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); /// /// Returns the current Unix Epoch /// public static int Now { get { return (int)(DateTime.UtcNow.Subtract(epoch).TotalSeconds); } } /// /// Convert an epoch to a datetime /// public static DateTime ToDateTime(decimal unixTime) { return epoch.AddSeconds((long)unixTime); } /// /// Convert a DateTime to a unix time /// public static uint FromDateTime(DateTime dt) { return (uint)(dt.Subtract(epoch).TotalSeconds); } } public static bool IsProperFilenameCase(string filename) { //File case only matters on Linux where the filesystem is case-sensitive, so we don't need these errors in release builds. //It also seems Path.GetFullPath may return a path with an incorrect case on Windows when the case of any of the game's //parent folders have been changed. #if !DEBUG && !LINUX return true; #endif char[] delimiters = { '/', '\\' }; string[] subDirs = filename.Split(delimiters); string originalFilename = filename; filename = ""; for (int i = 0; i < subDirs.Length - 1; i++) { filename += subDirs[i] + "/"; if (i == subDirs.Length - 2) { string[] filePaths = Directory.GetFiles(filename); if (filePaths.Any(s => s.Equals(filename + subDirs[i + 1], StringComparison.Ordinal))) { return true; } else if (filePaths.Any(s => s.Equals(filename + subDirs[i + 1], StringComparison.OrdinalIgnoreCase))) { DebugConsole.ThrowError(originalFilename + " has incorrect case!"); return false; } } string[] dirPaths = Directory.GetDirectories(filename); if (!dirPaths.Any(s => s.Equals(filename + subDirs[i + 1], StringComparison.Ordinal))) { if (dirPaths.Any(s => s.Equals(filename + subDirs[i + 1], StringComparison.OrdinalIgnoreCase))) { DebugConsole.ThrowError(originalFilename + " has incorrect case!"); } else { DebugConsole.ThrowError(originalFilename + " doesn't exist!"); } return false; } } return true; } public static string LimitString(string str, int maxCharacters) { if (str == null || maxCharacters < 0) return null; if (maxCharacters < 4 || str.Length <= maxCharacters) return str; return str.Substring(0, maxCharacters - 3) + "..."; } public static string RandomSeed(int length) { var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; return new string( Enumerable.Repeat(chars, length) .Select(s => s[Rand.Int(s.Length)]) .ToArray()); } public static int StringToInt(string str) { str = str.Substring(0, Math.Min(str.Length, 32)); str = str.PadLeft(4, 'a'); byte[] asciiBytes = Encoding.ASCII.GetBytes(str); for (int i = 4; i < asciiBytes.Length; i++) { asciiBytes[i % 4] ^= asciiBytes[i]; } return BitConverter.ToInt32(asciiBytes, 0); } /// /// a method for changing inputtypes with old names to the new ones to ensure backwards compatibility with older subs /// public static string ConvertInputType(string inputType) { if (inputType == "ActionHit" || inputType == "Action") return "Use"; if (inputType == "SecondaryHit" || inputType == "Secondary") return "Aim"; return inputType; } // Convert an RGB value into an HLS value. public static Vector3 RgbToHLS(Vector3 color) { double h, l, s; double double_r = color.X; double double_g = color.Y; double double_b = color.Z; // Get the maximum and minimum RGB components. double max = double_r; if (max < double_g) max = double_g; if (max < double_b) max = double_b; double min = double_r; if (min > double_g) min = double_g; if (min > double_b) min = double_b; double diff = max - min; l = (max + min) / 2; if (Math.Abs(diff) < 0.00001) { s = 0; h = 0; // H is really undefined. } else { if (l <= 0.5) s = diff / (max + min); else s = diff / (2 - max - min); double r_dist = (max - double_r) / diff; double g_dist = (max - double_g) / diff; double b_dist = (max - double_b) / diff; if (double_r == max) h = b_dist - g_dist; else if (double_g == max) h = 2 + r_dist - b_dist; else h = 4 + g_dist - r_dist; h = h * 60; if (h < 0) h += 360; } return new Vector3((float)h, (float)l, (float)s); } /// /// Calculates the minimum number of single-character edits (i.e. insertions, deletions or substitutions) required to change one string into the other /// public static int LevenshteinDistance(string s, string t) { int n = s.Length; int m = t.Length; int[,] d = new int[n + 1, m + 1]; if (n == 0 || m == 0) return 0; for (int i = 0; i <= n; d[i, 0] = i++) ; for (int j = 0; j <= m; d[0, j] = j++) ; for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { int cost = (t[j - 1] == s[i - 1]) ? 0 : 1; d[i, j] = Math.Min( Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), d[i - 1, j - 1] + cost); } } return d[n, m]; } public static string SecondsToReadableTime(float seconds) { if (seconds < 60.0f) { return (int)seconds + " s"; } else { int m = (int)(seconds / 60.0f); int s = (int)(seconds % 60.0f); return s == 0 ? m + " m" : m + " m " + s + " s"; } } private static Dictionary> cachedLines = new Dictionary>(); public static string GetRandomLine(string filePath) { List lines; if (cachedLines.ContainsKey(filePath)) { lines = cachedLines[filePath]; } else { try { using (StreamReader file = new StreamReader(filePath)) { lines = File.ReadLines(filePath).ToList(); cachedLines.Add(filePath, lines); if (lines.Count == 0) { DebugConsole.ThrowError("File \"" + filePath + "\" is empty!"); return ""; } } } catch (Exception e) { DebugConsole.ThrowError("Couldn't open file \"" + filePath + "\"!", e); return ""; } } if (lines.Count == 0) return ""; return lines[Rand.Range(0, lines.Count, Rand.RandSync.Server)]; } /// /// Reads a number of bits from the buffer and inserts them to a new NetBuffer instance /// public static NetBuffer ExtractBits(this NetBuffer originalBuffer, int numberOfBits) { var buffer = new NetBuffer(); byte[] data = new byte[(int)Math.Ceiling(numberOfBits / (double)8)]; originalBuffer.ReadBits(data, 0, numberOfBits); buffer.Write(data); return buffer; } public static T SelectWeightedRandom(IList objects, IList weights, Rand.RandSync randSync) { return SelectWeightedRandom(objects, weights, Rand.GetRNG(randSync)); } public static T SelectWeightedRandom(IList objects, IList weights, Random random) { if (objects.Count == 0) return default(T); if (objects.Count != weights.Count) { DebugConsole.ThrowError("Error in SelectWeightedRandom, number of objects does not match the number of weights.\n" + Environment.StackTrace); return objects[0]; } float totalWeight = weights.Sum(); float randomNum = (float)(random.NextDouble() * totalWeight); for (int i = 0; i < objects.Count; i++) { if (randomNum <= weights[i]) { return objects[i]; } randomNum -= weights[i]; } return default(T); } public static UInt32 StringToUInt32Hash(string str, MD5 md5) { //calculate key based on MD5 hash instead of string.GetHashCode //to ensure consistent results across platforms byte[] inputBytes = Encoding.ASCII.GetBytes(str); byte[] hash = md5.ComputeHash(inputBytes); UInt32 key = (UInt32)((str.Length & 0xff) << 24); //could use more of the hash here instead? key |= (UInt32)(hash[hash.Length - 3] << 16); key |= (UInt32)(hash[hash.Length - 2] << 8); key |= (UInt32)(hash[hash.Length - 1]); return key; } /// /// Returns a new instance of the class with all properties and fields copied. /// public static T CreateCopy(this T source, BindingFlags flags = BindingFlags.Instance | BindingFlags.Public) where T : new() => CopyValues(source, new T(), flags); public static T CopyValuesTo(this T source, T target, BindingFlags flags = BindingFlags.Instance | BindingFlags.Public) => CopyValues(source, target, flags); /// /// Copies the values of the source to the destination. May not work, if the source is of higher inheritance class than the destination. Does not work with virtual properties. /// public static T CopyValues(T source, T destination, BindingFlags flags = BindingFlags.Instance | BindingFlags.Public) { if (source == null) { throw new Exception("Failed to copy object. Source is null."); } if (destination == null) { throw new Exception("Failed to copy object. Destination is null."); } Type type = source.GetType(); var properties = type.GetProperties(flags); foreach (var property in properties) { if (property.CanWrite) { property.SetValue(destination, property.GetValue(source, null), null); } } var fields = type.GetFields(flags); foreach (var field in fields) { field.SetValue(destination, field.GetValue(source)); } // Check that the fields match.Uncomment to apply the test, if in doubt. //if (fields.Any(f => { var value = f.GetValue(destination); return value == null || !value.Equals(f.GetValue(source)); })) //{ // throw new Exception("Failed to copy some of the fields."); //} return destination; } } }