diff --git a/FloatTool/App.xaml.cs b/FloatTool/App.xaml.cs index bd1d346..e57d634 100644 --- a/FloatTool/App.xaml.cs +++ b/FloatTool/App.xaml.cs @@ -16,6 +16,7 @@ */ using DiscordRPC; +using FloatTool.Common; using System; using System.Collections.Generic; using System.Globalization; @@ -31,7 +32,7 @@ namespace FloatTool { - public partial class App : Application + public partial class App : Application { public static ResourceDictionary ThemeDictionary { get; set; } diff --git a/FloatTool/AppHelpers.cs b/FloatTool/AppHelpers.cs index 60a927a..1050353 100644 --- a/FloatTool/AppHelpers.cs +++ b/FloatTool/AppHelpers.cs @@ -16,12 +16,13 @@ */ using DiscordRPC; +using FloatTool.Common; using System.Collections.Generic; using System.IO; namespace FloatTool { - internal static class AppHelpers + internal static class AppHelpers { public static FileSystemWatcher Watcher; public static DiscordRpcClient DiscordClient; diff --git a/FloatTool/Common/Calculations.cs b/FloatTool/Common/Calculations.cs index 1296ca0..ceb5486 100644 --- a/FloatTool/Common/Calculations.cs +++ b/FloatTool/Common/Calculations.cs @@ -15,56 +15,81 @@ - along with this program. If not, see . */ -using System.Collections.Generic; using System.Numerics; -namespace FloatTool +namespace FloatTool.Common { - static public class Calculations - { - public static double Craft(InputSkin[] ingridients, double minFloat, double floatRange) - { - return floatRange * (ingridients[0].WearValue - + ingridients[1].WearValue - + ingridients[2].WearValue - + ingridients[3].WearValue - + ingridients[4].WearValue - + ingridients[5].WearValue - + ingridients[6].WearValue - + ingridients[7].WearValue - + ingridients[8].WearValue - + ingridients[9].WearValue) + minFloat; - } + static public class Calculations + { + public static double Craft(InputSkin[] ingridients, double minFloat, double floatRange) + { + return floatRange * (ingridients[0].WearValue + + ingridients[1].WearValue + + ingridients[2].WearValue + + ingridients[3].WearValue + + ingridients[4].WearValue + + ingridients[5].WearValue + + ingridients[6].WearValue + + ingridients[7].WearValue + + ingridients[8].WearValue + + ingridients[9].WearValue) + minFloat; + } - public static bool NextCombination(int[] num, int n) - { - bool finished = false; - for (int i = 9; !finished; --i) - { - if (num[i] < n + i) - { - ++num[i]; - if (i < 9) - for (int j = i + 1; j < 10; ++j) - num[j] = num[j - 1] + 1; - return true; - } - finished = i == 0; - } - return false; - } - - public static long GetCombinationsCount(int poolSize) - { - BigInteger fact1 = poolSize; - for (int i = poolSize - 1; i > 10; i--) - fact1 *= i; + public static bool NextCombination(int[] num, int n) + { + bool finished = false; + for (int i = 9; !finished; --i) + { + if (num[i] < n + i) + { + ++num[i]; + if (i < 9) + for (int j = i + 1; j < 10; ++j) + num[j] = num[j - 1] + 1; + return true; + } + finished = i == 0; + } + return false; + } - BigInteger fact2 = poolSize - 10; - for (int i = poolSize - 11; i > 1; i--) - fact2 *= i; + public static bool NextCombination(int[] arr, int n, int step) + { + arr[9] += step; + if (arr[9] < n + 10) return true; + else + { + fix_loop: + int overflow = arr[9] - (n + 10); + for (int i = 9; i >= 0; --i) + { + if (arr[i] < n + i) + { + ++arr[i]; + for (int j = i + 1; j < 10; ++j) + arr[j] = arr[j - 1] + 1; - return (long)(fact1 / fact2); - } - } + arr[9] += overflow; + + if (arr[9] < n + 10) return true; + else goto fix_loop; + } + } + return false; + } + } + + public static long GetCombinationsCount(int poolSize) + { + BigInteger fact1 = poolSize; + for (int i = poolSize - 1; i > 10; i--) + fact1 *= i; + + BigInteger fact2 = poolSize - 10; + for (int i = poolSize - 11; i > 1; i--) + fact2 *= i; + + return (long)(fact1 / fact2); + } + } } diff --git a/FloatTool/Common/Logger.cs b/FloatTool/Common/Logger.cs index 358bb45..edb57fd 100644 --- a/FloatTool/Common/Logger.cs +++ b/FloatTool/Common/Logger.cs @@ -22,47 +22,47 @@ using log4net.Repository.Hierarchy; using System.Reflection; -namespace FloatTool +namespace FloatTool.Common { - public sealed class Logger - { - public static ILog Log { get; } = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + public sealed class Logger + { + public static ILog Log { get; } = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - public static void Initialize() - { - Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository(); + public static void Initialize() + { + Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository(); - PatternLayout patternLayout = new() - { - ConversionPattern = "%date [%thread] %-5level - %message%newline" - }; - patternLayout.ActivateOptions(); + PatternLayout patternLayout = new() + { + ConversionPattern = "%date [%thread] %-5level - %message%newline" + }; + patternLayout.ActivateOptions(); - RollingFileAppender roller = new() - { - AppendToFile = false, - File = @"logs/log.txt", - Layout = patternLayout, - MaxSizeRollBackups = 5, - MaximumFileSize = "250KB", - RollingStyle = RollingFileAppender.RollingMode.Size, - StaticLogFileName = true - }; - roller.ActivateOptions(); - hierarchy.Root.AddAppender(roller); + RollingFileAppender roller = new() + { + AppendToFile = false, + File = @"logs/log.txt", + Layout = patternLayout, + MaxSizeRollBackups = 5, + MaximumFileSize = "250KB", + RollingStyle = RollingFileAppender.RollingMode.Size, + StaticLogFileName = true + }; + roller.ActivateOptions(); + hierarchy.Root.AddAppender(roller); - MemoryAppender memory = new(); - memory.ActivateOptions(); - hierarchy.Root.AddAppender(memory); + MemoryAppender memory = new(); + memory.ActivateOptions(); + hierarchy.Root.AddAppender(memory); - hierarchy.Root.Level = Level.Info; - hierarchy.Configured = true; - } + hierarchy.Root.Level = Level.Info; + hierarchy.Configured = true; + } - public static void Debug(object message) => Log.Debug(message); - public static void Info(object message) => Log.Info(message); - public static void Warn(object message) => Log.Warn(message); - public static void Error(object message) => Log.Error(message); - public static void Fatal(object message) => Log.Fatal(message); - } + public static void Debug(object message) => Log.Debug(message); + public static void Info(object message) => Log.Info(message); + public static void Warn(object message) => Log.Warn(message); + public static void Error(object message) => Log.Error(message); + public static void Fatal(object message) => Log.Fatal(message); + } } diff --git a/FloatTool/Common/RelayCommand.cs b/FloatTool/Common/RelayCommand.cs index d63e42d..00a5740 100644 --- a/FloatTool/Common/RelayCommand.cs +++ b/FloatTool/Common/RelayCommand.cs @@ -18,38 +18,38 @@ using System; using System.Windows.Input; -namespace FloatTool +namespace FloatTool.Common { - public sealed class RelayCommand : ICommand - { - readonly Action _execute; - readonly Predicate _canExecute; + public sealed class RelayCommand : ICommand + { + readonly Action _execute; + readonly Predicate _canExecute; - public RelayCommand(Action execute) - : this(execute, null) - { - } + public RelayCommand(Action execute) + : this(execute, null) + { + } - public RelayCommand(Action execute, Predicate canExecute) - { - _execute = execute ?? throw new ArgumentNullException(nameof(execute)); - _canExecute = canExecute; - } + public RelayCommand(Action execute, Predicate canExecute) + { + _execute = execute ?? throw new ArgumentNullException(nameof(execute)); + _canExecute = canExecute; + } - public bool CanExecute(object parameters) - { - return _canExecute == null || _canExecute(parameters); - } + public bool CanExecute(object parameters) + { + return _canExecute == null || _canExecute(parameters); + } - public event EventHandler CanExecuteChanged - { - add { CommandManager.RequerySuggested += value; } - remove { CommandManager.RequerySuggested -= value; } - } + public event EventHandler CanExecuteChanged + { + add { CommandManager.RequerySuggested += value; } + remove { CommandManager.RequerySuggested -= value; } + } - public void Execute(object parameters) - { - _execute(parameters); - } - } + public void Execute(object parameters) + { + _execute(parameters); + } + } } diff --git a/FloatTool/Common/Settings.cs b/FloatTool/Common/Settings.cs index 99a0968..ed49b15 100644 --- a/FloatTool/Common/Settings.cs +++ b/FloatTool/Common/Settings.cs @@ -22,191 +22,193 @@ using System.IO; using System.Threading; -namespace FloatTool +namespace FloatTool.Common { - public enum Currency - { - USD = 1, GBP = 2, - EUR = 3, CHF = 4, - RUB = 5, PLN = 6, - BRL = 7, JPY = 8, - NOK = 9, IDR = 10, - MYR = 11, PHP = 12, - SGD = 13, THB = 14, - VND = 15, KRW = 16, - TRY = 17, UAH = 18, - MXN = 19, CAD = 20, - AUD = 21, NZD = 22, - CNY = 23, INR = 24, - CLP = 25, PEN = 26, - COP = 27, ZAR = 28, - HKD = 29, TWD = 30, - SAR = 31, AED = 32, - ARS = 34, ILS = 35, - BYN = 36, KZT = 37, - KWD = 38, QAR = 39, - CRC = 40, UYU = 41, - RMB = 9000, NXP = 9001 - } - - // These are currently the same, but that can change later. - public enum FloatAPI - { - CSGOFloat, - SteamInventoryHelper - } - - public enum ExtensionType - { - CSGOFloat, - SteamInventoryHelper - } - - public static class CurrencyHelper - { - //Method to get the currency name from the enum - public static Currency GetCurrencyByIndex(int index) - { - return (Currency)(Enum.GetValues(typeof(Currency))).GetValue(index); - } - - //Method to get index of a currency - public static int GetIndexFromCurrency(Currency currency) - { - return Array.IndexOf(Enum.GetValues(typeof(Currency)), currency); - } - } - - public sealed class Settings - { - public string LanguageCode { get; set; } = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName; - public Currency Currency { get; set; } = Currency.USD; - public string ThemeURI { get; set; } = "/Theme/Schemes/Dark.xaml"; - public bool Sound { get; set; } = true; - public bool CheckForUpdates { get; set; } = true; - public bool DiscordRPC { get; set; } = true; - public int ThreadCount { get; set; } = Environment.ProcessorCount; - public FloatAPI FloatAPI { get; set; } = FloatAPI.CSGOFloat; - public ExtensionType ExtensionType { get; set; } = ExtensionType.CSGOFloat; - - public void Load() - { - try - { - string settingsPath = Path.Join(AppHelpers.AppDirectory, "settings.json"); - if (File.Exists(settingsPath)) - { - var settings = File.ReadAllText(settingsPath); - var tmpSettings = JsonConvert.DeserializeObject(settings); - LanguageCode = tmpSettings.LanguageCode; - Currency = tmpSettings.Currency; - ThemeURI = tmpSettings.ThemeURI; - Sound = tmpSettings.Sound; - CheckForUpdates = tmpSettings.CheckForUpdates; - DiscordRPC = tmpSettings.DiscordRPC; - ThreadCount = tmpSettings.ThreadCount; - FloatAPI = tmpSettings.FloatAPI; - ExtensionType = tmpSettings.ExtensionType; - } - else - { - LoadOld(); - Save(); - } - } - catch (Exception ex) - { - Logger.Log.Error("Error loading settings", ex); - } - } - - public void LoadOld() - { - // Load settings from registry - const string userRoot = "HKEY_CURRENT_USER"; - const string subkey = "Software\\FloatTool"; - const string keyName = userRoot + "\\" + subkey; - - try - { - using var hkcu = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64); - var key = hkcu.OpenSubKey(@"SOFTWARE\FloatTool"); - - if (key == null) return; - - LanguageCode = (string)Registry.GetValue(keyName, "languageCode", LanguageCode); - Currency = (Currency)Registry.GetValue(keyName, "currency", Currency); - ThemeURI = (string)Registry.GetValue(keyName, "themeURI", ThemeURI); - ThreadCount = (int)Registry.GetValue(keyName, "lastThreads", ThreadCount); - Sound = (string)Registry.GetValue(keyName, "sound", Sound ? "True" : "False") == "True"; - CheckForUpdates = (string)Registry.GetValue(keyName, "updateCheck", CheckForUpdates ? "True" : "False") == "True"; - DiscordRPC = (string)Registry.GetValue(keyName, "discordRPC", DiscordRPC ? "True" : "False") == "True"; - - key.Close(); - Logger.Log.Info("Loaded settings from registry"); - - MigrateFromOldVersion(); - } - catch (Exception ex) - { - Logger.Log.Error("Error loading settings from registry", ex); - } - } - - public void Save() - { - try - { - if (!Directory.Exists(AppHelpers.AppDirectory)) - Directory.CreateDirectory(AppHelpers.AppDirectory); - - var settings = JsonConvert.SerializeObject(this, Formatting.Indented); - File.WriteAllText(Path.Join(AppHelpers.AppDirectory, "settings.json"), settings); - } - catch (Exception ex) - { - Logger.Log.Error("Error saving settings", ex); - } - } - - public void MigrateFromOldVersion() - { - // This method cleans up old settings and data - try - { - RegistryKey regkeySoftware = Registry.CurrentUser.OpenSubKey("SOFTWARE", true); - regkeySoftware.DeleteSubKeyTree("FloatTool"); - regkeySoftware.Close(); - } - catch (Exception ex) - { - Logger.Log.Error("Error saving settings", ex); - } - - List oldFiles = new() - { - "debug.log", - "FloatCore.dll", - "FloatTool.exe.config", - "FloatTool.pdb", - "itemData.json", - "theme.json", - "Updater.exe" - }; - - foreach (var file in oldFiles) - { - if (File.Exists(file)) - File.Delete(file); - } - - App.CleanOldFiles(); - Save(); - } - - public override string ToString() - { - return JsonConvert.SerializeObject(this, Formatting.Indented); - } - } + public enum Currency + { + USD = 1, GBP = 2, + EUR = 3, CHF = 4, + RUB = 5, PLN = 6, + BRL = 7, JPY = 8, + NOK = 9, IDR = 10, + MYR = 11, PHP = 12, + SGD = 13, THB = 14, + VND = 15, KRW = 16, + TRY = 17, UAH = 18, + MXN = 19, CAD = 20, + AUD = 21, NZD = 22, + CNY = 23, INR = 24, + CLP = 25, PEN = 26, + COP = 27, ZAR = 28, + HKD = 29, TWD = 30, + SAR = 31, AED = 32, + ARS = 34, ILS = 35, + BYN = 36, KZT = 37, + KWD = 38, QAR = 39, + CRC = 40, UYU = 41, + RMB = 9000, NXP = 9001 + } + + // These are currently the same, but that can change later. + public enum FloatAPI + { + CSGOFloat, + SteamInventoryHelper + } + + public enum ExtensionType + { + CSGOFloat, + SteamInventoryHelper + } + + public static class CurrencyHelper + { + //Method to get the currency name from the enum + public static Currency GetCurrencyByIndex(int index) + { + return (Currency)Enum.GetValues(typeof(Currency)).GetValue(index); + } + + //Method to get index of a currency + public static int GetIndexFromCurrency(Currency currency) + { + return Array.IndexOf(Enum.GetValues(typeof(Currency)), currency); + } + } + + public sealed class Settings + { + public string LanguageCode { get; set; } = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName; + public Currency Currency { get; set; } = Currency.USD; + public string ThemeURI { get; set; } = "/Theme/Schemes/Dark.xaml"; + public bool Sound { get; set; } = true; + public bool CheckForUpdates { get; set; } = true; + public bool DiscordRPC { get; set; } = true; + public bool UseParallel { get; set; } = false; + public int ThreadCount { get; set; } = Environment.ProcessorCount; + public FloatAPI FloatAPI { get; set; } = FloatAPI.CSGOFloat; + public ExtensionType ExtensionType { get; set; } = ExtensionType.CSGOFloat; + + public void Load() + { + try + { + string settingsPath = Path.Join(AppHelpers.AppDirectory, "settings.json"); + if (File.Exists(settingsPath)) + { + var settings = File.ReadAllText(settingsPath); + var tmpSettings = JsonConvert.DeserializeObject(settings); + LanguageCode = tmpSettings.LanguageCode; + Currency = tmpSettings.Currency; + ThemeURI = tmpSettings.ThemeURI; + Sound = tmpSettings.Sound; + CheckForUpdates = tmpSettings.CheckForUpdates; + DiscordRPC = tmpSettings.DiscordRPC; + UseParallel = tmpSettings.UseParallel; + ThreadCount = tmpSettings.ThreadCount; + FloatAPI = tmpSettings.FloatAPI; + ExtensionType = tmpSettings.ExtensionType; + } + else + { + LoadOld(); + Save(); + } + } + catch (Exception ex) + { + Logger.Log.Error("Error loading settings", ex); + } + } + + public void LoadOld() + { + // Load settings from registry + const string userRoot = "HKEY_CURRENT_USER"; + const string subkey = "Software\\FloatTool"; + const string keyName = userRoot + "\\" + subkey; + + try + { + using var hkcu = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64); + var key = hkcu.OpenSubKey(@"SOFTWARE\FloatTool"); + + if (key == null) return; + + LanguageCode = (string)Registry.GetValue(keyName, "languageCode", LanguageCode); + Currency = (Currency)Registry.GetValue(keyName, "currency", Currency); + ThemeURI = (string)Registry.GetValue(keyName, "themeURI", ThemeURI); + ThreadCount = (int)Registry.GetValue(keyName, "lastThreads", ThreadCount); + Sound = (string)Registry.GetValue(keyName, "sound", Sound ? "True" : "False") == "True"; + CheckForUpdates = (string)Registry.GetValue(keyName, "updateCheck", CheckForUpdates ? "True" : "False") == "True"; + DiscordRPC = (string)Registry.GetValue(keyName, "discordRPC", DiscordRPC ? "True" : "False") == "True"; + + key.Close(); + Logger.Log.Info("Loaded settings from registry"); + + MigrateFromOldVersion(); + } + catch (Exception ex) + { + Logger.Log.Error("Error loading settings from registry", ex); + } + } + + public void Save() + { + try + { + if (!Directory.Exists(AppHelpers.AppDirectory)) + Directory.CreateDirectory(AppHelpers.AppDirectory); + + var settings = JsonConvert.SerializeObject(this, Formatting.Indented); + File.WriteAllText(Path.Join(AppHelpers.AppDirectory, "settings.json"), settings); + } + catch (Exception ex) + { + Logger.Log.Error("Error saving settings", ex); + } + } + + public void MigrateFromOldVersion() + { + // This method cleans up old settings and data + try + { + RegistryKey regkeySoftware = Registry.CurrentUser.OpenSubKey("SOFTWARE", true); + regkeySoftware.DeleteSubKeyTree("FloatTool"); + regkeySoftware.Close(); + } + catch (Exception ex) + { + Logger.Log.Error("Error saving settings", ex); + } + + List oldFiles = new() + { + "debug.log", + "FloatCore.dll", + "FloatTool.exe.config", + "FloatTool.pdb", + "itemData.json", + "theme.json", + "Updater.exe" + }; + + foreach (var file in oldFiles) + { + if (File.Exists(file)) + File.Delete(file); + } + + App.CleanOldFiles(); + Save(); + } + + public override string ToString() + { + return JsonConvert.SerializeObject(this, Formatting.Indented); + } + } } diff --git a/FloatTool/Common/Skin.cs b/FloatTool/Common/Skin.cs index 951e7d5..a9639ac 100644 --- a/FloatTool/Common/Skin.cs +++ b/FloatTool/Common/Skin.cs @@ -17,145 +17,145 @@ using System.Collections.Generic; -namespace FloatTool +namespace FloatTool.Common { - public sealed class Collection - { - public string Name; - public bool CanBeStattrak; - public string LowestRarity; - public string HighestRarity; - public string Link; - public List Skins; - } - - public struct SkinModel - { - public string Name; - public string Rarity; - public double MinWear; - public double MaxWear; - - public bool IsQualityInRange(string quality) - { - var range = Skin.GetFloatRangeForQuality(quality); - return new FloatRange(MinWear, MaxWear).IsOverlapped(range); - } - } - - public struct Skin - { - public enum Quality - { - Consumer, - Industrial, - MilSpec, - Restricted, - Classified, - Covert - } - - public string Name; - public double MinFloat; - public double MaxFloat; - public double FloatRange; - public Quality Rarity; - - public Skin(string name, double minWear, double maxWear, Quality rarity) - { - Name = name; - MinFloat = minWear; - MaxFloat = maxWear; - FloatRange = (MaxFloat - MinFloat) / 10; - Rarity = rarity; - } - - public Skin(SkinModel model) : this(model.Name, model.MinWear, model.MaxWear, FromString(model.Rarity)) { } - - public static string NextRarity(string rarity) - { - return rarity switch - { - "Consumer" => "Industrial", - "Industrial" => "Mil-Spec", - "Mil-Spec" => "Restricted", - "Restricted" => "Classified", - "Classified" => "Covert", - _ => "Nothing", - }; - } - - public static Quality FromString(string value) - { - return value switch - { - "Consumer" => Quality.Consumer, - "Industrial" => Quality.Industrial, - "Mil-Spec" => Quality.MilSpec, - "Restricted" => Quality.Restricted, - "Classified" => Quality.Classified, - _ => Quality.Covert, - }; - } - - public static FloatRange GetFloatRangeForQuality(string quality) - { - float lowestWear; - float highestWear; - - switch (quality) - { - case "Factory New": - lowestWear = 0f; - highestWear = 0.07f; - break; - case "Minimal Wear": - lowestWear = 0.07f; - highestWear = 0.15f; - break; - case "Field-Tested": - lowestWear = 0.15f; - highestWear = 0.38f; - break; - case "Well-Worn": - lowestWear = 0.38f; - highestWear = 0.45f; - break; - case "Battle-Scarred": - lowestWear = 0.45f; - highestWear = 1f; - break; - default: - lowestWear = 0f; - highestWear = 1f; - break; - } - - return new FloatRange(lowestWear, highestWear); - } - - } - - public sealed class InputSkin - { - public double WearValue; - public float Price; - public Currency SkinCurrency; - public string ListingID; - - public double GetWearValue => WearValue; - - public InputSkin(double wear, float price, Currency currency, string listingId = "") - { - WearValue = wear; - Price = price; - SkinCurrency = currency; - ListingID = listingId; - } - - internal int CompareTo(InputSkin b) - { - return WearValue > b.WearValue ? 1 : (WearValue < b.WearValue ? -1 : 0); - } - } + public sealed class Collection + { + public string Name; + public bool CanBeStattrak; + public string LowestRarity; + public string HighestRarity; + public string Link; + public List Skins; + } + + public struct SkinModel + { + public string Name; + public string Rarity; + public double MinWear; + public double MaxWear; + + public bool IsQualityInRange(string quality) + { + var range = Skin.GetFloatRangeForQuality(quality); + return new FloatRange(MinWear, MaxWear).IsOverlapped(range); + } + } + + public enum Quality + { + Consumer, + Industrial, + MilSpec, + Restricted, + Classified, + Covert + } + + public struct Skin + { + public string Name; + public double MinFloat; + public double MaxFloat; + public double FloatRange; + public Quality Rarity; + + public Skin(string name, double minWear, double maxWear, Quality rarity) + { + Name = name; + MinFloat = minWear; + MaxFloat = maxWear; + FloatRange = (MaxFloat - MinFloat) / 10; + Rarity = rarity; + } + + public Skin(SkinModel model) : this(model.Name, model.MinWear, model.MaxWear, FromString(model.Rarity)) { } + + public static string NextRarity(string rarity) + { + return rarity switch + { + "Consumer" => "Industrial", + "Industrial" => "Mil-Spec", + "Mil-Spec" => "Restricted", + "Restricted" => "Classified", + "Classified" => "Covert", + _ => "Nothing", + }; + } + + public static Quality FromString(string value) + { + return value switch + { + "Consumer" => Quality.Consumer, + "Industrial" => Quality.Industrial, + "Mil-Spec" => Quality.MilSpec, + "Restricted" => Quality.Restricted, + "Classified" => Quality.Classified, + _ => Quality.Covert, + }; + } + + public static FloatRange GetFloatRangeForQuality(string quality) + { + float lowestWear; + float highestWear; + + switch (quality) + { + case "Factory New": + lowestWear = 0f; + highestWear = 0.07f; + break; + case "Minimal Wear": + lowestWear = 0.07f; + highestWear = 0.15f; + break; + case "Field-Tested": + lowestWear = 0.15f; + highestWear = 0.38f; + break; + case "Well-Worn": + lowestWear = 0.38f; + highestWear = 0.45f; + break; + case "Battle-Scarred": + lowestWear = 0.45f; + highestWear = 1f; + break; + default: + lowestWear = 0f; + highestWear = 1f; + break; + } + + return new FloatRange(lowestWear, highestWear); + } + + } + + public struct InputSkin + { + public double WearValue; + public float Price; + public Currency SkinCurrency; + public string ListingID; + + public double GetWearValue => WearValue; + + public InputSkin(double wear, float price, Currency currency, string listingId = "") + { + WearValue = wear; + Price = price; + SkinCurrency = currency; + ListingID = listingId; + } + + internal int CompareTo(InputSkin b) + { + return WearValue.CompareTo(b.WearValue); + } + } } diff --git a/FloatTool/Common/Steam/SteamApi.cs b/FloatTool/Common/Steam/SteamApi.cs deleted file mode 100644 index b42e760..0000000 --- a/FloatTool/Common/Steam/SteamApi.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Runtime.Serialization.Formatters.Binary; -using System.Text; -using System.Threading.Tasks; - -namespace FloatTool.Steam -{ - /*public static class SteamApi - { - public readonly static HttpClient httpClient = new(); - - public struct RequestDefinition - { - public string ApiInterface; - public string ApiMethod; - public string ApiVersion; - public dynamic Data; - public string? AccessToken; - } - - public static async Task SendRequest(RequestDefinition request) - { - string url = $"https://api.steampowered.com/{request.ApiInterface}/{request.ApiMethod}/v{request.ApiVersion}/"; - var content = new FormUrlEncodedContent(request.Data); - var result = await httpClient.PostAsync(url, content); - - return null; - } - }*/ -} \ No newline at end of file diff --git a/FloatTool/Common/Utils.cs b/FloatTool/Common/Utils.cs index 2957fd8..fa73af0 100644 --- a/FloatTool/Common/Utils.cs +++ b/FloatTool/Common/Utils.cs @@ -15,6 +15,7 @@ - along with this program. If not, see . */ +using FloatTool.ViewModels; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -28,272 +29,272 @@ using System.Threading.Tasks; using System.Windows; -namespace FloatTool +namespace FloatTool.Common { - // Taken from here: - // https://stackoverflow.com/a/46497896/16349466 - public static class StreamExtensions - { - public static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress progress = null, CancellationToken cancellationToken = default) - { - if (source == null) - throw new ArgumentNullException(nameof(source)); - if (!source.CanRead) - throw new ArgumentException("Has to be readable", nameof(source)); - if (destination == null) - throw new ArgumentNullException(nameof(destination)); - if (!destination.CanWrite) - throw new ArgumentException("Has to be writable", nameof(destination)); - if (bufferSize < 0) - throw new ArgumentOutOfRangeException(nameof(bufferSize)); - - var buffer = new byte[bufferSize]; - long totalBytesRead = 0; - int bytesRead; - while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0) - { - await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false); - totalBytesRead += bytesRead; - progress?.Report(totalBytesRead); - } - } - } - - public static class HttpClientExtensions - { - public static async Task DownloadAsync(this HttpClient client, string requestUri, Stream destination, IProgress progress = null, CancellationToken cancellationToken = default) - { - // Get the http headers first to examine the content length - using var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken); - var contentLength = response.Content.Headers.ContentLength; - - using var download = await response.Content.ReadAsStreamAsync(cancellationToken); - - // Ignore progress reporting when no progress reporter was - // passed or when the content length is unknown - if (progress == null || !contentLength.HasValue) - { - await download.CopyToAsync(destination, cancellationToken); - return; - } - - // Convert absolute progress (bytes downloaded) into relative progress (0% - 100%) - var relativeProgress = new Progress(totalBytes => progress.Report((float)totalBytes / contentLength.Value)); - // Use extension method to report progress while downloading - await download.CopyToAsync(destination, 81920, relativeProgress, cancellationToken); - progress.Report(1); - } - } - - public static class StringExtensions - { - public static string FirstCharToUpper(this string input) => - input switch - { - null => throw new ArgumentNullException(nameof(input)), - "" => throw new ArgumentException($"{nameof(input)} cannot be empty", nameof(input)), - _ => string.Concat(input[0].ToString().ToUpper(), input.AsSpan(1)) - }; - } - - public static class Utils - { - public const string API_URL = "https://prevter.ml/api/floattool"; - private static readonly HttpClient Client = new(); - - public static async Task GetWearFromInspectURL(string inspect_url) - { - string url = AppHelpers.Settings.FloatAPI switch - { - FloatAPI.SteamInventoryHelper => "https://floats.steaminventoryhelper.com/?url=", - _ => "https://api.csgofloat.com/?url=", - }; - - var result = await Client.GetAsync(url + inspect_url); - result.EnsureSuccessStatusCode(); - string response = await result.Content.ReadAsStringAsync(); - dynamic json = JsonConvert.DeserializeObject(response); - return Convert.ToDouble(json["iteminfo"]["floatvalue"]); - } - - public static async Task CheckForUpdates() - { - try - { - HttpClient client = new(); - client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36"); - var result = await client.GetAsync("https://api.github.com/repos/prevter/floattool/releases/latest"); - result.EnsureSuccessStatusCode(); - string response = await result.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject(response); - } - catch (Exception ex) - { - Logger.Log.Error("Failed to get latest version", ex); - return null; - } - } - - public static string ShortCpuName(string cpu) - { - cpu = cpu.Replace("(R)", ""); - cpu = cpu.Replace("(TM)", ""); - cpu = cpu.Replace("(tm)", ""); - cpu = cpu.Replace(" with Radeon Graphics", ""); - cpu = cpu.Replace(" with Radeon Vega Mobile Gfx", ""); - cpu = cpu.Replace(" CPU", ""); - cpu = cpu.Replace(" Processor", ""); - - Regex regex = new(@"( \S{1,}-Core)"); - MatchCollection matches = regex.Matches(cpu); - if (matches.Count > 0) - foreach (Match match in matches.Cast()) - cpu = cpu.Replace(match.Value, ""); - - var index = cpu.IndexOf('@'); - if (index != -1) - cpu = cpu[..index]; - - index = cpu.IndexOf('('); - if (index != -1) - cpu = cpu[..index]; - - return cpu.Trim(); - } - - public static string EscapeLocalization(string input) - { - string regex = @"%(m_[^%]{1,})%"; - var matches = Regex.Matches(input, regex); - - foreach (Match m in matches.Cast()) - { - string key = m.Groups[1].Value; - string localization = Application.Current.Resources[key] as string; - input = input.Replace(m.Value, localization); - } - - return input; - } - - public static void Sort(this ObservableCollection collection, Comparison comparison) - { - var sortableList = new List(collection); - sortableList.Sort(comparison); - - for (int i = 0; i < sortableList.Count; i++) - { - collection.Move(collection.IndexOf(sortableList[i]), i); - } - } - - private const long TicksPerMillisecond = 10000; - private const long TicksPerSecond = TicksPerMillisecond * 1000; - private static readonly double s_tickFrequency = (double)TicksPerSecond / Stopwatch.Frequency; - - public static TimeSpan GetTimePassed(long starttime) - { - long endtime = Stopwatch.GetTimestamp(); - return GetTimePassed(starttime, endtime); - } - - public static TimeSpan GetTimePassed(long starttime, long endtime) - { - return new TimeSpan((long)((endtime - starttime) * s_tickFrequency)); - } - - } - - public sealed class UpdateResult - { - public class Asset - { - [JsonRequired] - [JsonProperty("browser_download_url")] - public string BrowserDownloadUrl { get; set; } - } - - [JsonRequired] - [JsonProperty("tag_name")] - public string TagName { get; set; } - [JsonRequired] - [JsonProperty("name")] - public string Name { get; set; } - [JsonRequired] - [JsonProperty("assets")] - public List Assets { get; set; } - [JsonRequired] - [JsonProperty("body")] - public string Body { get; set; } - } - - public struct CraftSearchSetup - { - public double SearchTarget; - public double TargetPrecision; - public string SearchFilter; - - public Skin[] Outcomes; - public InputSkin[] SkinPool; - - public SearchMode SearchMode; - - public int ThreadID; - public int ThreadCount; - } - - public struct FloatRange - { - readonly double min; - readonly double max; - - public FloatRange(double min, double max) - { - this.min = min; - this.max = max; - } - - public bool IsOverlapped(FloatRange other) - { - return Min <= other.Max && other.Min <= Max; - } - - public double Min { get { return min; } } - public double Max { get { return max; } } - } - - /// - /// Used to store current Discord Presense and update language if needed - /// - public sealed class RPCSettingsPersist - { - public string Details { get; set; } - public string State { get; set; } - - public DiscordRPC.Timestamps Timestamp { get; set; } - public bool ShowTime { get; set; } - - public DiscordRPC.RichPresence GetPresense() - { - string details = Utils.EscapeLocalization(Details); - string state = Utils.EscapeLocalization(State); - - var rpc = new DiscordRPC.RichPresence() - { - Details = details, - State = state, - Assets = new DiscordRPC.Assets() - { - LargeImageKey = "icon_new", - LargeImageText = $"FloatTool {AppHelpers.VersionCode}", - }, - }; - - if (ShowTime) - rpc.Timestamps = Timestamp; - - return rpc; - } - } + // Taken from here: + // https://stackoverflow.com/a/46497896/16349466 + public static class StreamExtensions + { + public static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress progress = null, CancellationToken cancellationToken = default) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (!source.CanRead) + throw new ArgumentException("Has to be readable", nameof(source)); + if (destination == null) + throw new ArgumentNullException(nameof(destination)); + if (!destination.CanWrite) + throw new ArgumentException("Has to be writable", nameof(destination)); + if (bufferSize < 0) + throw new ArgumentOutOfRangeException(nameof(bufferSize)); + + var buffer = new byte[bufferSize]; + long totalBytesRead = 0; + int bytesRead; + while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0) + { + await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false); + totalBytesRead += bytesRead; + progress?.Report(totalBytesRead); + } + } + } + + public static class HttpClientExtensions + { + public static async Task DownloadAsync(this HttpClient client, string requestUri, Stream destination, IProgress progress = null, CancellationToken cancellationToken = default) + { + // Get the http headers first to examine the content length + using var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + var contentLength = response.Content.Headers.ContentLength; + + using var download = await response.Content.ReadAsStreamAsync(cancellationToken); + + // Ignore progress reporting when no progress reporter was + // passed or when the content length is unknown + if (progress == null || !contentLength.HasValue) + { + await download.CopyToAsync(destination, cancellationToken); + return; + } + + // Convert absolute progress (bytes downloaded) into relative progress (0% - 100%) + var relativeProgress = new Progress(totalBytes => progress.Report((float)totalBytes / contentLength.Value)); + // Use extension method to report progress while downloading + await download.CopyToAsync(destination, 81920, relativeProgress, cancellationToken); + progress.Report(1); + } + } + + public static class StringExtensions + { + public static string FirstCharToUpper(this string input) => + input switch + { + null => throw new ArgumentNullException(nameof(input)), + "" => throw new ArgumentException($"{nameof(input)} cannot be empty", nameof(input)), + _ => string.Concat(input[0].ToString().ToUpper(), input.AsSpan(1)) + }; + } + + public static class Utils + { + public const string API_URL = "https://prevter.ml/api/floattool"; + private static readonly HttpClient Client = new(); + + public static async Task GetWearFromInspectURL(string inspect_url) + { + string url = AppHelpers.Settings.FloatAPI switch + { + FloatAPI.SteamInventoryHelper => "https://floats.steaminventoryhelper.com/?url=", + _ => "https://api.csgofloat.com/?url=", + }; + + var result = await Client.GetAsync(url + inspect_url); + result.EnsureSuccessStatusCode(); + string response = await result.Content.ReadAsStringAsync(); + dynamic json = JsonConvert.DeserializeObject(response); + return Convert.ToDouble(json["iteminfo"]["floatvalue"]); + } + + public static async Task CheckForUpdates() + { + try + { + HttpClient client = new(); + client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36"); + var result = await client.GetAsync("https://api.github.com/repos/prevter/floattool/releases/latest"); + result.EnsureSuccessStatusCode(); + string response = await result.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject(response); + } + catch (Exception ex) + { + Logger.Log.Error("Failed to get latest version", ex); + return null; + } + } + + public static string ShortCpuName(string cpu) + { + cpu = cpu.Replace("(R)", ""); + cpu = cpu.Replace("(TM)", ""); + cpu = cpu.Replace("(tm)", ""); + cpu = cpu.Replace(" with Radeon Graphics", ""); + cpu = cpu.Replace(" with Radeon Vega Mobile Gfx", ""); + cpu = cpu.Replace(" CPU", ""); + cpu = cpu.Replace(" Processor", ""); + + Regex regex = new(@"( \S{1,}-Core)"); + MatchCollection matches = regex.Matches(cpu); + if (matches.Count > 0) + foreach (Match match in matches.Cast()) + cpu = cpu.Replace(match.Value, ""); + + var index = cpu.IndexOf('@'); + if (index != -1) + cpu = cpu[..index]; + + index = cpu.IndexOf('('); + if (index != -1) + cpu = cpu[..index]; + + return cpu.Trim(); + } + + public static string EscapeLocalization(string input) + { + string regex = @"%(m_[^%]{1,})%"; + var matches = Regex.Matches(input, regex); + + foreach (Match m in matches.Cast()) + { + string key = m.Groups[1].Value; + string localization = Application.Current.Resources[key] as string; + input = input.Replace(m.Value, localization); + } + + return input; + } + + public static void Sort(this ObservableCollection collection, Comparison comparison) + { + var sortableList = new List(collection); + sortableList.Sort(comparison); + + for (int i = 0; i < sortableList.Count; i++) + { + collection.Move(collection.IndexOf(sortableList[i]), i); + } + } + + private const long TicksPerMillisecond = 10000; + private const long TicksPerSecond = TicksPerMillisecond * 1000; + private static readonly double s_tickFrequency = (double)TicksPerSecond / Stopwatch.Frequency; + + public static TimeSpan GetTimePassed(long starttime) + { + long endtime = Stopwatch.GetTimestamp(); + return GetTimePassed(starttime, endtime); + } + + public static TimeSpan GetTimePassed(long starttime, long endtime) + { + return new TimeSpan((long)((endtime - starttime) * s_tickFrequency)); + } + + } + + public sealed class UpdateResult + { + public class Asset + { + [JsonRequired] + [JsonProperty("browser_download_url")] + public string BrowserDownloadUrl { get; set; } + } + + [JsonRequired] + [JsonProperty("tag_name")] + public string TagName { get; set; } + [JsonRequired] + [JsonProperty("name")] + public string Name { get; set; } + [JsonRequired] + [JsonProperty("assets")] + public List Assets { get; set; } + [JsonRequired] + [JsonProperty("body")] + public string Body { get; set; } + } + + public struct CraftSearchSetup + { + public double SearchTarget; + public double TargetPrecision; + public string SearchFilter; + + public Skin[] Outcomes; + public InputSkin[] SkinPool; + + public SearchMode SearchMode; + + public int ThreadID; + public int ThreadCount; + } + + public readonly struct FloatRange + { + readonly double min; + readonly double max; + + public FloatRange(double min, double max) + { + this.min = min; + this.max = max; + } + + public bool IsOverlapped(FloatRange other) + { + return Min <= other.Max && other.Min <= Max; + } + + public double Min { get { return min; } } + public double Max { get { return max; } } + } + + /// + /// Used to store current Discord Presense and update language if needed + /// + public sealed class RPCSettingsPersist + { + public string Details { get; set; } + public string State { get; set; } + + public DiscordRPC.Timestamps Timestamp { get; set; } + public bool ShowTime { get; set; } + + public DiscordRPC.RichPresence GetPresense() + { + string details = Utils.EscapeLocalization(Details); + string state = Utils.EscapeLocalization(State); + + var rpc = new DiscordRPC.RichPresence() + { + Details = details, + State = state, + Assets = new DiscordRPC.Assets() + { + LargeImageKey = "icon_new", + LargeImageText = $"FloatTool {AppHelpers.VersionCode}", + }, + }; + + if (ShowTime) + rpc.Timestamps = Timestamp; + + return rpc; + } + } } diff --git a/FloatTool/Languages/Lang.ru.xaml b/FloatTool/Languages/Lang.ru.xaml index d578672..c4bb6d0 100644 --- a/FloatTool/Languages/Lang.ru.xaml +++ b/FloatTool/Languages/Lang.ru.xaml @@ -73,7 +73,7 @@ Поиск - Ищю скин на торговой площадке + Ищу скин на торговой площадке Получаю флоаты з торговой площадки Ошибка! Не удалось получить флоаты с торговой площадки Ошибка! Не удалось получить больше 10-ти флоатов diff --git a/FloatTool/Languages/Lang.xaml b/FloatTool/Languages/Lang.xaml index 11e181a..cb9ff5f 100644 --- a/FloatTool/Languages/Lang.xaml +++ b/FloatTool/Languages/Lang.xaml @@ -53,6 +53,7 @@ Show advanced Float fetcher API: Format for: + Use "Parallel.For" Benchmark diff --git a/FloatTool/ViewModels/BenchmarkViewModel.cs b/FloatTool/ViewModels/BenchmarkViewModel.cs index 1d57235..a3b771b 100644 --- a/FloatTool/ViewModels/BenchmarkViewModel.cs +++ b/FloatTool/ViewModels/BenchmarkViewModel.cs @@ -15,6 +15,7 @@ - along with this program. If not, see . */ +using FloatTool.Common; using Microsoft.Win32; using Newtonsoft.Json; using System; @@ -26,232 +27,232 @@ using System.Windows; using System.Windows.Media; -namespace FloatTool +namespace FloatTool.ViewModels { - public sealed class BenchmarkViewModel : INotifyPropertyChanged - { - public sealed class BenchmarkResult - { - public string CpuName { get; set; } - public string ThreadCount { get; set; } - public string MultithreadedScore { get; set; } - public string SinglethreadedScore { get; set; } - public LinearGradientBrush FillBrush { get; set; } - public GridLength FillSize { get; set; } - public GridLength EmptySize { get; set; } - } - public long TotalCombinations { get; set; } + public sealed class BenchmarkViewModel : INotifyPropertyChanged + { + public sealed class BenchmarkResult + { + public string CpuName { get; set; } + public string ThreadCount { get; set; } + public string MultithreadedScore { get; set; } + public string SinglethreadedScore { get; set; } + public LinearGradientBrush FillBrush { get; set; } + public GridLength FillSize { get; set; } + public GridLength EmptySize { get; set; } + } + public long TotalCombinations { get; set; } - private int progressPercentage; - private bool buttonsEnabled = true; - private bool canPublish = false; - private bool showOnlyCurrent = false; - private int multithreadedSpeed = 0; - private int threadCount = Environment.ProcessorCount; - public int ThreadCountTested = 0; - private int singlethreadedSpeed = 0; - private bool isUpdatingEnabled; + private int progressPercentage; + private bool buttonsEnabled = true; + private bool canPublish = false; + private bool showOnlyCurrent = false; + private int multithreadedSpeed = 0; + private int threadCount = Environment.ProcessorCount; + public int ThreadCountTested = 0; + private int singlethreadedSpeed = 0; + private bool isUpdatingEnabled; - private static readonly LinearGradientBrush AMDBrush = Application.Current.Resources["AmdBenchmarkFill"] as LinearGradientBrush; - private static readonly LinearGradientBrush IntelBrush = Application.Current.Resources["IntelBenchmarkFill"] as LinearGradientBrush; - private static readonly LinearGradientBrush CurrentBrush = Application.Current.Resources["CurrentBenchmarkFill"] as LinearGradientBrush; + private static readonly LinearGradientBrush AMDBrush = Application.Current.Resources["AmdBenchmarkFill"] as LinearGradientBrush; + private static readonly LinearGradientBrush IntelBrush = Application.Current.Resources["IntelBenchmarkFill"] as LinearGradientBrush; + private static readonly LinearGradientBrush CurrentBrush = Application.Current.Resources["CurrentBenchmarkFill"] as LinearGradientBrush; - public int ProgressPercentage - { - get { return progressPercentage; } - set - { - progressPercentage = value; - OnPropertyChanged(); - } - } + public int ProgressPercentage + { + get { return progressPercentage; } + set + { + progressPercentage = value; + OnPropertyChanged(); + } + } - public bool ButtonsEnabled - { - get { return buttonsEnabled; } - set - { - buttonsEnabled = value; - OnPropertyChanged(); - } - } - public bool CanPublish - { - get { return canPublish; } - set - { - canPublish = value; - OnPropertyChanged(); - } - } + public bool ButtonsEnabled + { + get { return buttonsEnabled; } + set + { + buttonsEnabled = value; + OnPropertyChanged(); + } + } + public bool CanPublish + { + get { return canPublish; } + set + { + canPublish = value; + OnPropertyChanged(); + } + } - public int ThreadCount - { - get - { - return threadCount; - } - set - { - threadCount = value; - OnPropertyChanged(); - } - } - public bool ShowOnlyCurrent - { - get - { - return showOnlyCurrent; - } - set - { - showOnlyCurrent = value; - PollBenchmarkResults(); - OnPropertyChanged(); - } - } + public int ThreadCount + { + get + { + return threadCount; + } + set + { + threadCount = value; + OnPropertyChanged(); + } + } + public bool ShowOnlyCurrent + { + get + { + return showOnlyCurrent; + } + set + { + showOnlyCurrent = value; + PollBenchmarkResults(); + OnPropertyChanged(); + } + } - public int MultithreadedSpeed - { - get { return multithreadedSpeed; } - set - { - multithreadedSpeed = value; - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MultithreadedSpeedText))); - } - } - public string MultithreadedSpeedText { get { return $"{multithreadedSpeed:n0}"; } } + public int MultithreadedSpeed + { + get { return multithreadedSpeed; } + set + { + multithreadedSpeed = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MultithreadedSpeedText))); + } + } + public string MultithreadedSpeedText { get { return $"{multithreadedSpeed:n0}"; } } - public int SinglethreadedSpeed - { - get { return singlethreadedSpeed; } - set - { - singlethreadedSpeed = value; - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SinglethreadedSpeedText))); - } - } - public string SinglethreadedSpeedText { get { return $"{singlethreadedSpeed:n0}"; } } + public int SinglethreadedSpeed + { + get { return singlethreadedSpeed; } + set + { + singlethreadedSpeed = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SinglethreadedSpeedText))); + } + } + public string SinglethreadedSpeedText { get { return $"{singlethreadedSpeed:n0}"; } } - public ObservableCollection BenchmarkResults { get; set; } - public string CurrentCpuName { get; set; } - public static string CurrentCpuThreads { get { return $"{Environment.ProcessorCount}"; } } + public ObservableCollection BenchmarkResults { get; set; } + public string CurrentCpuName { get; set; } + public static string CurrentCpuThreads { get { return $"{Environment.ProcessorCount}"; } } - public bool IsUpdatingEnabled - { - get { return isUpdatingEnabled; } - set { isUpdatingEnabled = value; OnPropertyChanged(); } - } + public bool IsUpdatingEnabled + { + get { return isUpdatingEnabled; } + set { isUpdatingEnabled = value; OnPropertyChanged(); } + } - public async void PollBenchmarkResults() - { - var url = Utils.API_URL + "/load"; - if (ShowOnlyCurrent) url += $"?version={AppHelpers.VersionCode}"; - Logger.Log.Info($"Getting benchmark results from {url}"); - IsUpdatingEnabled = false; - try - { - BenchmarkResults.Clear(); - using var client = new HttpClient(); + public async void PollBenchmarkResults() + { + var url = Utils.API_URL + "/load"; + if (ShowOnlyCurrent) url += $"?version={AppHelpers.VersionCode}"; + Logger.Log.Info($"Getting benchmark results from {url}"); + IsUpdatingEnabled = false; + try + { + BenchmarkResults.Clear(); + using var client = new HttpClient(); - HttpResponseMessage response = await client.GetAsync(url); - response.EnsureSuccessStatusCode(); - string responseBody = await response.Content.ReadAsStringAsync(); - dynamic result = JsonConvert.DeserializeObject(responseBody); + HttpResponseMessage response = await client.GetAsync(url); + response.EnsureSuccessStatusCode(); + string responseBody = await response.Content.ReadAsStringAsync(); + dynamic result = JsonConvert.DeserializeObject(responseBody); - if (result["status"] == 200 && result["count"] > 0) - { - float maxspeed = result.items[0].multithread; + if (result["status"] == 200 && result["count"] > 0) + { + float maxspeed = result.items[0].multithread; - foreach (var benchmark in result.items) - { - float percentage = (float)benchmark.multithread / maxspeed; - float reverse = 1 - percentage; + foreach (var benchmark in result.items) + { + float percentage = (float)benchmark.multithread / maxspeed; + float reverse = 1 - percentage; - string cpuName = Utils.ShortCpuName((string)benchmark.name); - var currentFill = AMDBrush; - if (cpuName == CurrentCpuName) - { - currentFill = CurrentBrush; - MultithreadedSpeed = Math.Max((int)benchmark.multithread, MultithreadedSpeed); - SinglethreadedSpeed = Math.Max((int)benchmark.singlethread, SinglethreadedSpeed); - } - else if (cpuName.StartsWith("Intel")) - currentFill = IntelBrush; + string cpuName = Utils.ShortCpuName((string)benchmark.name); + var currentFill = AMDBrush; + if (cpuName == CurrentCpuName) + { + currentFill = CurrentBrush; + MultithreadedSpeed = Math.Max((int)benchmark.multithread, MultithreadedSpeed); + SinglethreadedSpeed = Math.Max((int)benchmark.singlethread, SinglethreadedSpeed); + } + else if (cpuName.StartsWith("Intel")) + currentFill = IntelBrush; - BenchmarkResults.Add(new BenchmarkResult - { - CpuName = cpuName, - ThreadCount = Utils.EscapeLocalization($"{benchmark.threads} {((int)benchmark.threads == 1 ? "%m_Thread%" : "%m_Threads%")} [{benchmark.version}]"), - MultithreadedScore = $"{(int)benchmark.multithread:n0}", - SinglethreadedScore = $"{(int)benchmark.singlethread:n0}", - FillSize = new GridLength(percentage, GridUnitType.Star), - EmptySize = new GridLength(reverse, GridUnitType.Star), - FillBrush = currentFill - }); - } - } + BenchmarkResults.Add(new BenchmarkResult + { + CpuName = cpuName, + ThreadCount = Utils.EscapeLocalization($"{benchmark.threads} {((int)benchmark.threads == 1 ? "%m_Thread%" : "%m_Threads%")} [{benchmark.version}]"), + MultithreadedScore = $"{(int)benchmark.multithread:n0}", + SinglethreadedScore = $"{(int)benchmark.singlethread:n0}", + FillSize = new GridLength(percentage, GridUnitType.Star), + EmptySize = new GridLength(reverse, GridUnitType.Star), + FillBrush = currentFill + }); + } + } - Logger.Log.Info($"Benchmark results loaded: {BenchmarkResults.Count} items"); + Logger.Log.Info($"Benchmark results loaded: {BenchmarkResults.Count} items"); - if (BenchmarkResults.Count == 0) - throw new Exception("0 results"); - } - catch (Exception ex) - { - BenchmarkResults.Add(new BenchmarkResult - { - CpuName = "Error loading benchmark table: " + ex.Message, - FillSize = new GridLength(0, GridUnitType.Star), - EmptySize = new GridLength(1, GridUnitType.Star), - }); - Logger.Log.Error("Error getting benchmark results", ex); - } - IsUpdatingEnabled = true; - } + if (BenchmarkResults.Count == 0) + throw new Exception("0 results"); + } + catch (Exception ex) + { + BenchmarkResults.Add(new BenchmarkResult + { + CpuName = "Error loading benchmark table: " + ex.Message, + FillSize = new GridLength(0, GridUnitType.Star), + EmptySize = new GridLength(1, GridUnitType.Star), + }); + Logger.Log.Error("Error getting benchmark results", ex); + } + IsUpdatingEnabled = true; + } - public async void PushBenchmarkResults() - { - Logger.Log.Info("Sending benchmark result"); - try - { - BenchmarkResults.Clear(); - using var client = new HttpClient(); - var version = Assembly.GetExecutingAssembly().GetName().Version; - client.DefaultRequestHeaders.Add("User-Agent", $"FloatTool/{AppHelpers.VersionCode}"); - string paramedURL = $"/submit?cpu={CurrentCpuName}&threads={ThreadCountTested}&multicore={MultithreadedSpeed}&singlecore={SinglethreadedSpeed}"; - HttpResponseMessage response = await client.GetAsync(Utils.API_URL + paramedURL); - response.EnsureSuccessStatusCode(); - string responseBody = await response.Content.ReadAsStringAsync(); + public async void PushBenchmarkResults() + { + Logger.Log.Info("Sending benchmark result"); + try + { + BenchmarkResults.Clear(); + using var client = new HttpClient(); + var version = Assembly.GetExecutingAssembly().GetName().Version; + client.DefaultRequestHeaders.Add("User-Agent", $"FloatTool/{AppHelpers.VersionCode}"); + string paramedURL = $"/submit?cpu={CurrentCpuName}&threads={ThreadCountTested}&multicore={MultithreadedSpeed}&singlecore={SinglethreadedSpeed}"; + HttpResponseMessage response = await client.GetAsync(Utils.API_URL + paramedURL); + response.EnsureSuccessStatusCode(); + string responseBody = await response.Content.ReadAsStringAsync(); - Logger.Log.Info("Sended benchmark result"); - } - catch (Exception ex) - { - Logger.Log.Error("Error sending benchmark result", ex); - } - CanPublish = false; - PollBenchmarkResults(); - } + Logger.Log.Info("Sended benchmark result"); + } + catch (Exception ex) + { + Logger.Log.Error("Error sending benchmark result", ex); + } + CanPublish = false; + PollBenchmarkResults(); + } - public BenchmarkViewModel() - { - ThreadCount = AppHelpers.Settings.ThreadCount; - string path = @"HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor\0"; - string cpu = (string)Registry.GetValue(path, "ProcessorNameString", "Unknown"); - CurrentCpuName = Utils.ShortCpuName(cpu); + public BenchmarkViewModel() + { + ThreadCount = AppHelpers.Settings.ThreadCount; + string path = @"HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor\0"; + string cpu = (string)Registry.GetValue(path, "ProcessorNameString", "Unknown"); + CurrentCpuName = Utils.ShortCpuName(cpu); - Logger.Log.Info($"CPU: {CurrentCpuName}"); - Logger.Log.Info($"Threads: {CurrentCpuThreads}"); + Logger.Log.Info($"CPU: {CurrentCpuName}"); + Logger.Log.Info($"Threads: {CurrentCpuThreads}"); - BenchmarkResults = new ObservableCollection(); - PollBenchmarkResults(); - } + BenchmarkResults = new ObservableCollection(); + PollBenchmarkResults(); + } - public event PropertyChangedEventHandler PropertyChanged; - private void OnPropertyChanged([CallerMemberName] string name = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); - } - } + public event PropertyChangedEventHandler PropertyChanged; + private void OnPropertyChanged([CallerMemberName] string name = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + } + } } diff --git a/FloatTool/ViewModels/MainViewModel.cs b/FloatTool/ViewModels/MainViewModel.cs index 4e154d2..3a03ae2 100644 --- a/FloatTool/ViewModels/MainViewModel.cs +++ b/FloatTool/ViewModels/MainViewModel.cs @@ -15,6 +15,7 @@ - along with this program. If not, see . */ +using FloatTool.Common; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -27,546 +28,544 @@ using System.Windows; using System.Windows.Controls; -namespace FloatTool +namespace FloatTool.ViewModels { - public enum SearchMode - { - Less, Equal, Greater - } - - public sealed class Combination - { - public double Wear { get; set; } - public string Wear32Bit { get; set; } - public string Wear128Bit { get; set; } - public InputSkin[] Inputs { get; set; } - public string OutcomeName { get; set; } - public Currency Currency { get; set; } - public float Price { get; set; } - public DateTime Time { get; set; } = DateTime.Now; - } - - public sealed class MainViewModel : INotifyPropertyChanged - { - private bool isSearching; - - private bool isStatTrak; - private string weaponName; - private string skinName; - private string skinQuality; - private string fullSkinName; - private int outcomeIndex; - private string floatRange; - - private SearchMode searchMode; - private string searchFilter; - private int skinCount; - private int skinSkipCount; - private Visibility isError = Visibility.Hidden; - private Visibility isErrorFloat = Visibility.Hidden; - private readonly Viewbox errorMessage; - private readonly Viewbox errorMessageFloat; - private bool sort; - private bool sortDescending; - private float progressPercentage; - private string currentSpeed = "0"; - - private readonly List qualityList = new() - { - "Factory New", - "Minimal Wear", - "Field-Tested", - "Well-Worn", - "Battle-Scarred" - }; - - private readonly List weaponList = new() - { - "AK-47", - "AUG", - "AWP", - "CZ75-Auto", - "Desert Eagle", - "Dual Berettas", - "FAMAS", - "Five-SeveN", - "G3SG1", - "Galil AR", - "Glock-18", - "M249", - "M4A1-S", - "M4A4", - "MAC-10", - "MAG-7", - "MP5-SD", - "MP7", - "MP9", - "Negev", - "Nova", - "P2000", - "P250", - "P90", - "PP-Bizon", - "R8 Revolver", - "SCAR-20", - "SG 553", - "SSG 08", - "Sawed-Off", - "Tec-9", - "UMP-45", - "USP-S", - "XM1014" - }; - - private List skinList = new(); - private List outcomeList = new(); - public Dictionary, List> Outcomes = new(); - - public List SkinsDatabase; - - #region Properties - - public ObservableCollection FoundCombinations { get; set; } - - public static string CurrentVersionSubtitle - { - get { return $"{AppHelpers.VersionCode} by Prevter"; } - } - - public int ThreadCount - { - get { return AppHelpers.Settings.ThreadCount; } - set { AppHelpers.Settings.ThreadCount = value; OnPropertyChanged(); } - } - - public string CombinationsLabel - { - get { return $"{ParsedCombinations:n0}/{TotalCombinations}"; } - set { OnPropertyChanged(); } - } - - public string CurrentSpeedLabel - { - get { return currentSpeed; } - set { currentSpeed = value; OnPropertyChanged(); } - } - - public string SearchFilter - { - get { return searchFilter; } - set - { - searchFilter = value; - UpdateFloatError(); - OnPropertyChanged(); - } - } - - public int SkinCount - { - get { return skinCount; } - set - { - skinCount = value; - OnPropertyChanged(); - } - } - - public int SkinSkipCount - { - get { return skinSkipCount; } - set - { - skinSkipCount = value; - OnPropertyChanged(); - } - } - - public bool Sort - { - get { return sort; } - set { sort = value; OnPropertyChanged(); } - } - - public bool SortDescending - { - get { return sortDescending; } - set { sortDescending = value; OnPropertyChanged(); } - } - - public SearchMode SearchModeSelected - { - get { return searchMode; } - set { searchMode = value; OnPropertyChanged(); } - } - - public string FloatRange - { - get { return floatRange; } - set { floatRange = value; OnPropertyChanged(); } - } - - public int OutcomeIndex - { - get { return outcomeIndex; } - set - { - outcomeIndex = value; - UpdateFloatRange(); - OnPropertyChanged(); - } - } - - public bool CanEditSettings - { - get { return !isSearching; } - set { isSearching = !value; OnPropertyChanged(); } - } - - public string WeaponName - { - get { return weaponName; } - set - { - weaponName = value; - UpdateFullSkinName(); - UpdateOutcomes(); - UpdateSkinList(); - OnPropertyChanged(); - } - } - - public string SkinName - { - get { return skinName; } - set - { - skinName = value; - UpdateFullSkinName(); - UpdateOutcomes(); - OnPropertyChanged(); - } - } - - public string SkinQuality - { - get { return skinQuality; } - set - { - skinQuality = value; - UpdateFullSkinName(); - UpdateFloatRange(); - OnPropertyChanged(); - } - } - - public bool IsStatTrak - { - get { return isStatTrak; } - set - { - isStatTrak = value; - UpdateFullSkinName(); - OnPropertyChanged(); - } - } - - public string FullSkinName - { - get { return fullSkinName; } - set { fullSkinName = value; OnPropertyChanged(); } - } - - public List WeaponList { get { return weaponList; } } - - public List SkinList - { - get { return skinList; } - set { skinList = value; OnPropertyChanged(); } - } - - public List QualityList { get { return qualityList; } } - - public List OutcomeList - { - get { return outcomeList; } - set { outcomeList = value; OnPropertyChanged(); } - } - - public Visibility IsError - { - get { return isError; } - set { isError = value; OnPropertyChanged(); } - } - - public Visibility IsErrorFloat - { - get { return isErrorFloat; } - set { isErrorFloat = value; OnPropertyChanged(); } - } - - public float ProgressPercentage - { - get { return progressPercentage; } - set { progressPercentage = value; OnPropertyChanged(); } - } - - public long ParsedCombinations { get; internal set; } - public long TotalCombinations { get; internal set; } - - private RelayCommand copyCommand; - public RelayCommand CopyCommand - { - get - { - return copyCommand ??= new RelayCommand(field => - { - var textbox = field as TextBox; - Clipboard.SetText(textbox.Text); - textbox?.Focus(); - textbox?.SelectAll(); - }); - } - } - - #endregion - - private void UpdateFullSkinName() - { - FullSkinName = $"{(IsStatTrak ? "StatTrak™ " : "")}{WeaponName} | {SkinName} ({SkinQuality})"; - - if (SkinsDatabase is null) - return; - - foreach (var collection in SkinsDatabase) - { - foreach (var skin in collection.Skins) - { - if (skin.Name == $"{WeaponName} | {SkinName}") - { - if (!skin.IsQualityInRange(SkinQuality)) - { - errorMessage.SetResourceReference(Viewbox.ToolTipProperty, "m_SkinNotFound"); - IsError = Visibility.Visible; - } - else if (IsStatTrak && !collection.CanBeStattrak) - { - errorMessage.SetResourceReference(Viewbox.ToolTipProperty, "m_CantBeStattrak"); - IsError = Visibility.Visible; - } - else - { - IsError = Visibility.Hidden; - } - } - } - } - } - - private float minCraftWear, maxCraftWear; - - private void UpdateFloatRange() - { - if (OutcomeIndex > Outcomes.Count - 1) - { - FloatRange = "No data"; - return; - } - - var range = Skin.GetFloatRangeForQuality(SkinQuality); - - List lowest = new(); - for (int i = 0; i < 10; i++) - lowest.Add(new InputSkin(range.Min, 0, Currency.USD)); - - List highest = new(); - for (int i = 0; i < 10; i++) - highest.Add(new InputSkin(range.Max, 0, Currency.USD)); - - int index = 0; - minCraftWear = 0; - maxCraftWear = 0; - foreach (var outcome in Outcomes.Values) - { - if (index++ == OutcomeIndex) - { - var currSkin = outcome[0]; - minCraftWear = Convert.ToSingle( - Calculations.Craft( - lowest.ToArray(), - currSkin.MinFloat, - currSkin.FloatRange - ), - CultureInfo.InvariantCulture - ); - maxCraftWear = Convert.ToSingle( - Calculations.Craft( - highest.ToArray(), - currSkin.MinFloat, - currSkin.FloatRange - ), - CultureInfo.InvariantCulture - ); - break; - } - } - - UpdateFloatError(); - FloatRange = $"{minCraftWear.ToString("0.00", CultureInfo.InvariantCulture)} - {maxCraftWear.ToString("0.00", CultureInfo.InvariantCulture)}"; - } - - public void UpdateFloatError() - { - try - { - decimal searchFilterDecimal = decimal.Parse(searchFilter, CultureInfo.InvariantCulture); - if (searchFilterDecimal < (decimal)minCraftWear || searchFilterDecimal > (decimal)maxCraftWear) - { - IsErrorFloat = Visibility.Visible; - errorMessageFloat.SetResourceReference(Viewbox.ToolTipProperty, "m_OutOfBounds"); - } - else - { - IsErrorFloat = Visibility.Hidden; - } - } - catch (FormatException) - { - IsErrorFloat = Visibility.Visible; - errorMessageFloat.SetResourceReference(Viewbox.ToolTipProperty, "m_CantParse"); - } - } - - - public Collection FindSkinCollection(string skin) - { - if (SkinsDatabase is null) - return null; - - foreach (var collection in SkinsDatabase) - { - foreach (var skinObj in collection.Skins) - { - if (skinObj.Name == skin) - { - return collection; - } - - } - } - - return null; - } - - public void UpdateOutcomes() - { - if (SkinsDatabase is null) - return; - - string skin = $"{WeaponName} | {SkinName}"; - var skinlist = new List(); - - var collection = FindSkinCollection(skin); - if (collection is null) - return; - - foreach (var skinObj in collection.Skins) - { - if (skinObj.Name == skin) - { - foreach (var otherSkinModel in collection.Skins) - { - if (Skin.NextRarity(skinObj.Rarity) == otherSkinModel.Rarity) - { - skinlist.Add(otherSkinModel); - } - } - break; - } - } - Outcomes.Clear(); - - for (int i = 0; i < skinlist.Count; i++) - { - var range = new Tuple(skinlist[i].MinWear, skinlist[i].MaxWear); - if (Outcomes.ContainsKey(range)) - Outcomes[range].Add(new Skin(skinlist[i])); - else - Outcomes[range] = new List { new Skin(skinlist[i]) }; - } - - int totalSkins = 0; - foreach (var skinRange in Outcomes.Values) - totalSkins += skinRange.Count; - - var list = new List(); - - foreach (var skinRange in Outcomes.Values) - { - string tmp = (skinRange.Count > 1) ? $" + {(skinRange.Count - 1)}" : ""; - list.Add($"{((float)skinRange.Count) / totalSkins * 100}% ({skinRange[0].Name}{tmp})"); - } - - list.Add("* Search all *"); - OutcomeIndex = 0; - OutcomeList = list; - } - - public void UpdateSkinList() - { - if (SkinsDatabase is null) - return; - - var list = new List(); - - foreach (var collection in SkinsDatabase) - { - foreach (var skin in collection.Skins) - { - if (skin.Name.Contains(WeaponName) && collection.HighestRarity != skin.Rarity) - { - list.Add(skin.Name.Split(" | ")[1]); - } - } - } - - list.Sort(); - SkinList = list; - } - - public MainViewModel(string weapon, string skin, string quality, string filter, int count, int skip, Viewbox errorTooltip, Viewbox errorTooltipFloat) - { - errorMessage = errorTooltip; - errorMessageFloat = errorTooltipFloat; - WeaponName = weapon; - SkinName = skin; - SkinQuality = quality; - - SearchModeSelected = SearchMode.Equal; - SearchFilter = filter; - SkinCount = count; - SkinSkipCount = skip; - - FoundCombinations = new(); - - Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("FloatTool.Assets.SkinList.json")!; - if (stream is null) - throw new NullReferenceException("Could not find SkinList.json"); - - using (StreamReader reader = new(stream)) - { - var jsonFileContent = reader.ReadToEnd(); - SkinsDatabase = JsonConvert.DeserializeObject>(jsonFileContent)!; - } - - UpdateSkinList(); - UpdateOutcomes(); - } - - public event PropertyChangedEventHandler PropertyChanged; - private void OnPropertyChanged([CallerMemberName] string name = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); - } - } + public enum SearchMode + { + Less, Equal, Greater + } + + public sealed class Combination + { + public double Wear { get; set; } + public string Wear32Bit { get; set; } + public string Wear128Bit { get; set; } + public InputSkin[] Inputs { get; set; } + public string OutcomeName { get; set; } + public Currency Currency { get; set; } + public float Price { get; set; } + public DateTime Time { get; set; } = DateTime.Now; + } + + public sealed class MainViewModel : INotifyPropertyChanged + { + private bool isSearching; + + private bool isStatTrak; + private string weaponName; + private string skinName; + private string skinQuality; + private string fullSkinName; + private int outcomeIndex; + private string floatRange; + + private SearchMode searchMode; + private string searchFilter; + private int skinCount; + private int skinSkipCount; + private Visibility isError = Visibility.Hidden; + private Visibility isErrorFloat = Visibility.Hidden; + private readonly Viewbox errorMessage; + private readonly Viewbox errorMessageFloat; + private bool sort; + private bool sortDescending; + private float progressPercentage; + private string currentSpeed = "0"; + + private readonly List qualityList = new() + { + "Factory New", + "Minimal Wear", + "Field-Tested", + "Well-Worn", + "Battle-Scarred" + }; + + private readonly List weaponList = new() + { + "AK-47", + "AUG", + "AWP", + "CZ75-Auto", + "Desert Eagle", + "Dual Berettas", + "FAMAS", + "Five-SeveN", + "G3SG1", + "Galil AR", + "Glock-18", + "M249", + "M4A1-S", + "M4A4", + "MAC-10", + "MAG-7", + "MP5-SD", + "MP7", + "MP9", + "Negev", + "Nova", + "P2000", + "P250", + "P90", + "PP-Bizon", + "R8 Revolver", + "SCAR-20", + "SG 553", + "SSG 08", + "Sawed-Off", + "Tec-9", + "UMP-45", + "USP-S", + "XM1014" + }; + + private List skinList = new(); + private List outcomeList = new(); + public Dictionary, List> Outcomes = new(); + + public List SkinsDatabase; + + #region Properties + + public ObservableCollection FoundCombinations { get; set; } + + public static string CurrentVersionSubtitle + { + get { return $"{AppHelpers.VersionCode} by Prevter"; } + } + + public int ThreadCount + { + get { return AppHelpers.Settings.ThreadCount; } + set { AppHelpers.Settings.ThreadCount = value; OnPropertyChanged(); } + } + + public string CombinationsLabel + { + get { return $"{ParsedCombinations:n0}/{TotalCombinations}"; } + set { OnPropertyChanged(); } + } + + public string CurrentSpeedLabel + { + get { return currentSpeed; } + set { currentSpeed = value; OnPropertyChanged(); } + } + + public string SearchFilter + { + get { return searchFilter; } + set + { + searchFilter = value; + UpdateFloatError(); + OnPropertyChanged(); + } + } + + public int SkinCount + { + get { return skinCount; } + set + { + skinCount = value; + OnPropertyChanged(); + } + } + + public int SkinSkipCount + { + get { return skinSkipCount; } + set + { + skinSkipCount = value; + OnPropertyChanged(); + } + } + + public bool Sort + { + get { return sort; } + set { sort = value; OnPropertyChanged(); } + } + + public bool SortDescending + { + get { return sortDescending; } + set { sortDescending = value; OnPropertyChanged(); } + } + + public SearchMode SearchModeSelected + { + get { return searchMode; } + set { searchMode = value; OnPropertyChanged(); } + } + + public string FloatRange + { + get { return floatRange; } + set { floatRange = value; OnPropertyChanged(); } + } + + public int OutcomeIndex + { + get { return outcomeIndex; } + set + { + outcomeIndex = value; + UpdateFloatRange(); + OnPropertyChanged(); + } + } + + public bool CanEditSettings + { + get { return !isSearching; } + set { isSearching = !value; OnPropertyChanged(); } + } + + public string WeaponName + { + get { return weaponName; } + set + { + weaponName = value; + UpdateFullSkinName(); + UpdateOutcomes(); + UpdateSkinList(); + OnPropertyChanged(); + } + } + + public string SkinName + { + get { return skinName; } + set + { + skinName = value; + UpdateFullSkinName(); + UpdateOutcomes(); + OnPropertyChanged(); + } + } + + public string SkinQuality + { + get { return skinQuality; } + set + { + skinQuality = value; + UpdateFullSkinName(); + UpdateFloatRange(); + OnPropertyChanged(); + } + } + + public bool IsStatTrak + { + get { return isStatTrak; } + set + { + isStatTrak = value; + UpdateFullSkinName(); + OnPropertyChanged(); + } + } + + public string FullSkinName + { + get { return fullSkinName; } + set { fullSkinName = value; OnPropertyChanged(); } + } + + public List WeaponList { get { return weaponList; } } + + public List SkinList + { + get { return skinList; } + set { skinList = value; OnPropertyChanged(); } + } + + public List QualityList { get { return qualityList; } } + + public List OutcomeList + { + get { return outcomeList; } + set { outcomeList = value; OnPropertyChanged(); } + } + + public Visibility IsError + { + get { return isError; } + set { isError = value; OnPropertyChanged(); } + } + + public Visibility IsErrorFloat + { + get { return isErrorFloat; } + set { isErrorFloat = value; OnPropertyChanged(); } + } + + public float ProgressPercentage + { + get { return progressPercentage; } + set { progressPercentage = value; OnPropertyChanged(); } + } + + public long ParsedCombinations { get; internal set; } + public long TotalCombinations { get; internal set; } + + private RelayCommand copyCommand; + public RelayCommand CopyCommand + { + get + { + return copyCommand ??= new RelayCommand(field => + { + var textbox = field as TextBox; + Clipboard.SetText(textbox.Text); + textbox?.Focus(); + textbox?.SelectAll(); + }); + } + } + + #endregion + + private void UpdateFullSkinName() + { + FullSkinName = $"{(IsStatTrak ? "StatTrak™ " : "")}{WeaponName} | {SkinName} ({SkinQuality})"; + + if (SkinsDatabase is null) + return; + + foreach (var collection in SkinsDatabase) + { + foreach (var skin in collection.Skins) + { + if (skin.Name == $"{WeaponName} | {SkinName}") + { + if (!skin.IsQualityInRange(SkinQuality)) + { + errorMessage.SetResourceReference(FrameworkElement.ToolTipProperty, "m_SkinNotFound"); + IsError = Visibility.Visible; + } + else if (IsStatTrak && !collection.CanBeStattrak) + { + errorMessage.SetResourceReference(FrameworkElement.ToolTipProperty, "m_CantBeStattrak"); + IsError = Visibility.Visible; + } + else + { + IsError = Visibility.Hidden; + } + } + } + } + } + + private float minCraftWear, maxCraftWear; + + private void UpdateFloatRange() + { + if (OutcomeIndex > Outcomes.Count - 1) + { + FloatRange = "No data"; + return; + } + + var range = Skin.GetFloatRangeForQuality(SkinQuality); + + List lowest = new(); + for (int i = 0; i < 10; i++) + lowest.Add(new InputSkin(range.Min, 0, Currency.USD)); + + List highest = new(); + for (int i = 0; i < 10; i++) + highest.Add(new InputSkin(range.Max, 0, Currency.USD)); + + int index = 0; + minCraftWear = 0; + maxCraftWear = 0; + foreach (var outcome in Outcomes.Values) + { + if (index++ == OutcomeIndex) + { + var currSkin = outcome[0]; + minCraftWear = Convert.ToSingle( + Calculations.Craft( + lowest.ToArray(), + currSkin.MinFloat, + currSkin.FloatRange + ), + CultureInfo.InvariantCulture + ); + maxCraftWear = Convert.ToSingle( + Calculations.Craft( + highest.ToArray(), + currSkin.MinFloat, + currSkin.FloatRange + ), + CultureInfo.InvariantCulture + ); + break; + } + } + + UpdateFloatError(); + FloatRange = $"{minCraftWear.ToString("0.00", CultureInfo.InvariantCulture)} - {maxCraftWear.ToString("0.00", CultureInfo.InvariantCulture)}"; + } + + public void UpdateFloatError() + { + try + { + decimal searchFilterDecimal = decimal.Parse(searchFilter, CultureInfo.InvariantCulture); + if (searchFilterDecimal < (decimal)minCraftWear || searchFilterDecimal > (decimal)maxCraftWear) + { + IsErrorFloat = Visibility.Visible; + errorMessageFloat.SetResourceReference(FrameworkElement.ToolTipProperty, "m_OutOfBounds"); + } + else + { + IsErrorFloat = Visibility.Hidden; + } + } + catch (FormatException) + { + IsErrorFloat = Visibility.Visible; + errorMessageFloat.SetResourceReference(FrameworkElement.ToolTipProperty, "m_CantParse"); + } + } + + + public Collection FindSkinCollection(string skin) + { + if (SkinsDatabase is null) + return null; + + foreach (var collection in SkinsDatabase) + { + foreach (var skinObj in collection.Skins) + { + if (skinObj.Name == skin) + { + return collection; + } + + } + } + + return null; + } + + public void UpdateOutcomes() + { + if (SkinsDatabase is null) + return; + + string skin = $"{WeaponName} | {SkinName}"; + var skinlist = new List(); + + var collection = FindSkinCollection(skin); + if (collection is null) + return; + + foreach (var skinObj in collection.Skins) + { + if (skinObj.Name == skin) + { + foreach (var otherSkinModel in collection.Skins) + { + if (Skin.NextRarity(skinObj.Rarity) == otherSkinModel.Rarity) + { + skinlist.Add(otherSkinModel); + } + } + break; + } + } + Outcomes.Clear(); + + for (int i = 0; i < skinlist.Count; i++) + { + var range = new Tuple(skinlist[i].MinWear, skinlist[i].MaxWear); + if (Outcomes.TryGetValue(range, out List value)) + value.Add(new Skin(skinlist[i])); + else + Outcomes[range] = new List { new Skin(skinlist[i]) }; + } + + int totalSkins = 0; + foreach (var skinRange in Outcomes.Values) + totalSkins += skinRange.Count; + + var list = new List(); + + foreach (var skinRange in Outcomes.Values) + { + string tmp = skinRange.Count > 1 ? $" + {skinRange.Count - 1}" : ""; + list.Add($"{(float)skinRange.Count / totalSkins * 100}% ({skinRange[0].Name}{tmp})"); + } + + list.Add("* Search all *"); + OutcomeIndex = 0; + OutcomeList = list; + } + + public void UpdateSkinList() + { + if (SkinsDatabase is null) + return; + + var list = new List(); + + foreach (var collection in SkinsDatabase) + { + foreach (var skin in collection.Skins) + { + if (skin.Name.Contains(WeaponName) && collection.HighestRarity != skin.Rarity) + { + list.Add(skin.Name.Split(" | ")[1]); + } + } + } + + list.Sort(); + SkinList = list; + } + + public MainViewModel(string weapon, string skin, string quality, string filter, int count, int skip, Viewbox errorTooltip, Viewbox errorTooltipFloat) + { + errorMessage = errorTooltip; + errorMessageFloat = errorTooltipFloat; + WeaponName = weapon; + SkinName = skin; + SkinQuality = quality; + + SearchModeSelected = SearchMode.Equal; + SearchFilter = filter; + SkinCount = count; + SkinSkipCount = skip; + + FoundCombinations = new(); + + Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("FloatTool.Assets.SkinList.json")! + ?? throw new NullReferenceException("Could not find SkinList.json"); + using (StreamReader reader = new(stream)) + { + var jsonFileContent = reader.ReadToEnd(); + SkinsDatabase = JsonConvert.DeserializeObject>(jsonFileContent)!; + } + + UpdateSkinList(); + UpdateOutcomes(); + } + + public event PropertyChangedEventHandler PropertyChanged; + private void OnPropertyChanged([CallerMemberName] string name = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + } + } } diff --git a/FloatTool/ViewModels/SettingsViewModel.cs b/FloatTool/ViewModels/SettingsViewModel.cs index 1eefa90..b21e637 100644 --- a/FloatTool/ViewModels/SettingsViewModel.cs +++ b/FloatTool/ViewModels/SettingsViewModel.cs @@ -15,233 +15,240 @@ - along with this program. If not, see . */ +using FloatTool.Common; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Runtime.CompilerServices; -namespace FloatTool +namespace FloatTool.ViewModels { - public sealed class SettingsViewModel : INotifyPropertyChanged - { - public static List ThemesList - { - get - { - var tmpList = new List(); - foreach (var theme in AppHelpers.ThemesFound) - tmpList.Add(Path.GetFileNameWithoutExtension(theme)); - return tmpList; - } - } - - public int ThemeIndex - { - get { return AppHelpers.ThemesFound.IndexOf(AppHelpers.Settings.ThemeURI); } - set - { - AppHelpers.Settings.ThemeURI = AppHelpers.ThemesFound[value]; - App.SelectTheme(AppHelpers.Settings.ThemeURI); - OnPropertyChanged(); - } - } - - public bool EnableSound - { - get { return AppHelpers.Settings.Sound; } - set { AppHelpers.Settings.Sound = value; OnPropertyChanged(); } - } - - public bool CheckUpdates - { - get { return AppHelpers.Settings.CheckForUpdates; } - set { AppHelpers.Settings.CheckForUpdates = value; OnPropertyChanged(); } - } - - public bool DiscordRPC - { - get { return AppHelpers.Settings.DiscordRPC; } - set - { - AppHelpers.Settings.DiscordRPC = value; - OnPropertyChanged(); - - // Re-enabling does not work. Probably bug in the library - if (!value) - AppHelpers.DiscordClient.Deinitialize(); - else - AppHelpers.DiscordClient.Initialize(); - } - } - - public int CurrentCurrencyIndex - { - get - { - return CurrencyHelper.GetIndexFromCurrency(AppHelpers.Settings.Currency); - } - set - { - AppHelpers.Settings.Currency = CurrencyHelper.GetCurrencyByIndex(value); - OnPropertyChanged(); - } - } - - public List CurrencyNames { get; private set; } = new List - { - "USD / United States Dollar", - "GBP / United Kingdom Pound", - "EUR / European Union Euro", - "CHF / Swiss Francs", - "RUB / Russian Rouble", - "PLN / Polish Złoty", - "BRL / Brazilian Reals", - "JPY / Japanese Yen", - "NOK / Norwegian Krone", - "IDR / Indonesian Rupiah", - "MYR / Malaysian Ringgit", - "PHP / Philippine Peso", - "SGD / Singapore Dollar", - "THB / Thai Baht", - "VND / Vietnamese Dong", - "KRW / South Korean Won", - "TRY / Turkish Lira", - "UAH / Ukrainian Hryvnia", - "MXN / Mexican Peso", - "CAD / Canadian Dollar", - "AUD / Australian Dollar", - "NZD / New Zealand Dollar", - "CNY / Chinese Yuan", - "INR / Indian Rupee", - "CLP / Chilean Peso", - "PEN / Peruvian Nuevo Sol", - "COP / Colombian Peso", - "ZAR / South African Rand", - "HKD / Hong Kong Dollar", - "TWD / Taiwan Dollar", - "SAR / Saudi Riyal", - "AED / United Arab Emirates Dirham", - "ARS / Argentine Peso", - "ILS / Israeli New Shekel", - "BYN / Belarusian Ruble", - "KZT / Kazakhstani Tenge", - "KWD / Kuwaiti Dinar", - "QAR / Qatari Riyal", - "CRC / Costa Rican Colón", - "UYU / Uruguayan Peso", - "RMB / Chinese Yuan", - "NXP / NXP", - }; - - public int CurrentLanguage - { - get - { - return LanguageCodes.IndexOf(AppHelpers.Settings.LanguageCode); - } - set - { - AppHelpers.Settings.LanguageCode = LanguageCodes[value]; - App.SelectCulture(AppHelpers.Settings.LanguageCode); - OnPropertyChanged(); - } - } - - public static List Languages { get; private set; } - public readonly static List LanguageCodes = new() - { - "cs", - "da", - "de", - "en", - "es", - "fi", - "fr", - "ga", - "he", - "hr", - "it", - "ja", - "ka", - "lt", - "lv", - "pl", - "pt", - "uk", - "tr", - "ru", - "zh", - }; - - public List FloatAPIList { get; private set; } = new() - { - "CSGOFloat (api.csgofloat.com)", - "SIH (floats.steaminventoryhelper.com)" - }; - - public List ExtensionNames { get; private set; } = new() - { - "CSGOFloat Market Checker", - "Steam Inventory Helper" - }; - - public int SelectedFloatAPI - { - get { return (int)AppHelpers.Settings.FloatAPI; } - set - { - AppHelpers.Settings.FloatAPI = (FloatAPI)value; - OnPropertyChanged(); - } - } - - public int SelectedExtension - { - get { return (int)AppHelpers.Settings.ExtensionType; } - set - { - AppHelpers.Settings.ExtensionType = (ExtensionType)value; - OnPropertyChanged(); - } - } - - private bool isShowingAdvanced = false; - private RelayCommand showAdvanced; - public RelayCommand ShowAdvancedCommand - { - get - { - return showAdvanced ??= new RelayCommand(obj => - { - isShowingAdvanced = !isShowingAdvanced; - - if (isShowingAdvanced) - window.Height = 370; - else - window.Height = 310; - - }); - } - } - - private readonly SettingsWindow window; - - public SettingsViewModel(SettingsWindow window) - { - Languages = new List(); - this.window = window; - - foreach (var lang in LanguageCodes) - { - var locale = new System.Globalization.CultureInfo(lang); - Languages.Add($"{locale.NativeName.FirstCharToUpper()} ({locale.EnglishName})"); - } - } - - public event PropertyChangedEventHandler PropertyChanged; - private void OnPropertyChanged([CallerMemberName] string name = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); - } - } + public sealed class SettingsViewModel : INotifyPropertyChanged + { + public static List ThemesList + { + get + { + var tmpList = new List(); + foreach (var theme in AppHelpers.ThemesFound) + tmpList.Add(Path.GetFileNameWithoutExtension(theme)); + return tmpList; + } + } + + public int ThemeIndex + { + get { return AppHelpers.ThemesFound.IndexOf(AppHelpers.Settings.ThemeURI); } + set + { + AppHelpers.Settings.ThemeURI = AppHelpers.ThemesFound[value]; + App.SelectTheme(AppHelpers.Settings.ThemeURI); + OnPropertyChanged(); + } + } + + public bool EnableSound + { + get { return AppHelpers.Settings.Sound; } + set { AppHelpers.Settings.Sound = value; OnPropertyChanged(); } + } + + public bool CheckUpdates + { + get { return AppHelpers.Settings.CheckForUpdates; } + set { AppHelpers.Settings.CheckForUpdates = value; OnPropertyChanged(); } + } + + public bool UseParallel + { + get { return AppHelpers.Settings.UseParallel; } + set { AppHelpers.Settings.UseParallel = value; OnPropertyChanged(); } + } + + public bool DiscordRPC + { + get { return AppHelpers.Settings.DiscordRPC; } + set + { + AppHelpers.Settings.DiscordRPC = value; + OnPropertyChanged(); + + // Re-enabling does not work. Probably bug in the library + if (!value) + AppHelpers.DiscordClient.Deinitialize(); + else + AppHelpers.DiscordClient.Initialize(); + } + } + + public int CurrentCurrencyIndex + { + get + { + return CurrencyHelper.GetIndexFromCurrency(AppHelpers.Settings.Currency); + } + set + { + AppHelpers.Settings.Currency = CurrencyHelper.GetCurrencyByIndex(value); + OnPropertyChanged(); + } + } + + public List CurrencyNames { get; private set; } = new List + { + "USD / United States Dollar", + "GBP / United Kingdom Pound", + "EUR / European Union Euro", + "CHF / Swiss Francs", + "RUB / Russian Rouble", + "PLN / Polish Złoty", + "BRL / Brazilian Reals", + "JPY / Japanese Yen", + "NOK / Norwegian Krone", + "IDR / Indonesian Rupiah", + "MYR / Malaysian Ringgit", + "PHP / Philippine Peso", + "SGD / Singapore Dollar", + "THB / Thai Baht", + "VND / Vietnamese Dong", + "KRW / South Korean Won", + "TRY / Turkish Lira", + "UAH / Ukrainian Hryvnia", + "MXN / Mexican Peso", + "CAD / Canadian Dollar", + "AUD / Australian Dollar", + "NZD / New Zealand Dollar", + "CNY / Chinese Yuan", + "INR / Indian Rupee", + "CLP / Chilean Peso", + "PEN / Peruvian Nuevo Sol", + "COP / Colombian Peso", + "ZAR / South African Rand", + "HKD / Hong Kong Dollar", + "TWD / Taiwan Dollar", + "SAR / Saudi Riyal", + "AED / United Arab Emirates Dirham", + "ARS / Argentine Peso", + "ILS / Israeli New Shekel", + "BYN / Belarusian Ruble", + "KZT / Kazakhstani Tenge", + "KWD / Kuwaiti Dinar", + "QAR / Qatari Riyal", + "CRC / Costa Rican Colón", + "UYU / Uruguayan Peso", + "RMB / Chinese Yuan", + "NXP / NXP", + }; + + public int CurrentLanguage + { + get + { + return LanguageCodes.IndexOf(AppHelpers.Settings.LanguageCode); + } + set + { + AppHelpers.Settings.LanguageCode = LanguageCodes[value]; + App.SelectCulture(AppHelpers.Settings.LanguageCode); + OnPropertyChanged(); + } + } + + public static List Languages { get; private set; } + public readonly static List LanguageCodes = new() + { + "cs", + "da", + "de", + "en", + "es", + "fi", + "fr", + "ga", + "he", + "hr", + "it", + "ja", + "ka", + "lt", + "lv", + "pl", + "pt", + "uk", + "tr", + "ru", + "zh", + }; + + public List FloatAPIList { get; private set; } = new() + { + "CSGOFloat (api.csgofloat.com)", + "SIH (floats.steaminventoryhelper.com)" + }; + + public List ExtensionNames { get; private set; } = new() + { + "CSGOFloat Market Checker", + "Steam Inventory Helper" + }; + + public int SelectedFloatAPI + { + get { return (int)AppHelpers.Settings.FloatAPI; } + set + { + AppHelpers.Settings.FloatAPI = (FloatAPI)value; + OnPropertyChanged(); + } + } + + public int SelectedExtension + { + get { return (int)AppHelpers.Settings.ExtensionType; } + set + { + AppHelpers.Settings.ExtensionType = (ExtensionType)value; + OnPropertyChanged(); + } + } + + private bool isShowingAdvanced = false; + private RelayCommand showAdvanced; + public RelayCommand ShowAdvancedCommand + { + get + { + return showAdvanced ??= new RelayCommand(obj => + { + isShowingAdvanced = !isShowingAdvanced; + + if (isShowingAdvanced) + window.Height = 400; + else + window.Height = 310; + + }); + } + } + + private readonly SettingsWindow window; + + public SettingsViewModel(SettingsWindow window) + { + Languages = new List(); + this.window = window; + + foreach (var lang in LanguageCodes) + { + var locale = new System.Globalization.CultureInfo(lang); + Languages.Add($"{locale.NativeName.FirstCharToUpper()} ({locale.EnglishName})"); + } + } + + public event PropertyChangedEventHandler PropertyChanged; + private void OnPropertyChanged([CallerMemberName] string name = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + } + } } diff --git a/FloatTool/Views/BenchmarkWindow.xaml.cs b/FloatTool/Views/BenchmarkWindow.xaml.cs index 7afdbaa..b5beab3 100644 --- a/FloatTool/Views/BenchmarkWindow.xaml.cs +++ b/FloatTool/Views/BenchmarkWindow.xaml.cs @@ -15,6 +15,8 @@ - along with this program. If not, see . */ +using FloatTool.Common; +using FloatTool.ViewModels; using System; using System.Collections.Generic; using System.Diagnostics; @@ -27,7 +29,7 @@ namespace FloatTool { - public sealed partial class BenchmarkWindow : Window + public sealed partial class BenchmarkWindow : Window { public BenchmarkViewModel Context; private static long PassedCombinations; @@ -83,8 +85,7 @@ private static void FloatCraftWorkerThread(CraftSearchSetup options) InputSkin[] resultList = new InputSkin[10]; bool running = true; - for (int i = 0; i < options.ThreadID; i++) - running = Calculations.NextCombination(numbers, size); + running = Calculations.NextCombination(numbers, size, options.ThreadID); while (running) { @@ -129,8 +130,7 @@ private static void FloatCraftWorkerThread(CraftSearchSetup options) Interlocked.Increment(ref PassedCombinations); // Get next combination - for (int i = 0; i < options.ThreadCount; i++) - running = Calculations.NextCombination(numbers, size); + running = Calculations.NextCombination(numbers, size, options.ThreadCount); } } @@ -142,7 +142,7 @@ private void StartBenchmark_Click(object sender, RoutedEventArgs e) new Thread(() => { Skin[] outcomes = new Skin[] { - new Skin("AK-47 | Safari Mesh", 0.06, 0.8, Skin.Quality.Industrial) + new Skin("AK-47 | Safari Mesh", 0.06, 0.8, Quality.Industrial) }; double[] pool = { @@ -155,8 +155,9 @@ private void StartBenchmark_Click(object sender, RoutedEventArgs e) 0.157685652375221, 0.217334255576134, 0.217334255576134, 0.157685652375221, 0.217334255576134, 0.217334255576134, 0.157685652375221, 0.217334255576134, 0.217334255576134, - 0.157685652375221, 0.217334255576134, 0.217334255576134 - }; + 0.157685652375221, 0.217334255576134, 0.217334255576134, + 0.157685652375221, 0.217334255576134, 0.217334255576134, + }; List inputSkinsList = new(); foreach (double f in pool) @@ -182,27 +183,51 @@ private void StartBenchmark_Click(object sender, RoutedEventArgs e) int threads = Context.ThreadCount; long startTime = Stopwatch.GetTimestamp(); + ParallelLoopResult? parallel = null; - try - { - for (int i = 0; i < threads; i++) + try + { + if (AppHelpers.Settings.UseParallel) { - int startIndex = i; - Task newThread = Task.Factory.StartNew(() => FloatCraftWorkerThread( - new CraftSearchSetup + Task.Run(() => + { + parallel = Parallel.For(0, threads, i => { - SkinPool = inputSkins, - Outcomes = outcomes, - SearchTarget = searched, - TargetPrecision = precission, - SearchFilter = searchFilter, - SearchMode = SearchMode.Equal, - ThreadID = startIndex, - ThreadCount = threads, - } - )); - threadPool.Add(newThread); + FloatCraftWorkerThread(new CraftSearchSetup + { + SkinPool = inputSkins, + Outcomes = outcomes, + SearchTarget = searched, + TargetPrecision = precission, + SearchFilter = searchFilter, + SearchMode = SearchMode.Equal, + ThreadID = i, + ThreadCount = threads, + }); + }); + }); } + else + { + for (int i = 0; i < threads; i++) + { + int startIndex = i; + Task newThread = Task.Factory.StartNew(() => FloatCraftWorkerThread( + new CraftSearchSetup + { + SkinPool = inputSkins, + Outcomes = outcomes, + SearchTarget = searched, + TargetPrecision = precission, + SearchFilter = searchFilter, + SearchMode = SearchMode.Equal, + ThreadID = startIndex, + ThreadCount = threads, + } + )); + threadPool.Add(newThread); + } + } } catch (Exception ex) { @@ -211,15 +236,27 @@ private void StartBenchmark_Click(object sender, RoutedEventArgs e) while (true) { - bool isAnyRunning = false; - foreach (Task t in CollectionsMarshal.AsSpan(threadPool)) + bool isAnyRunning; + if (AppHelpers.Settings.UseParallel) { - if (t.Status != TaskStatus.RanToCompletion) - { - isAnyRunning = true; - break; - } - } + isAnyRunning = true; + if (parallel.HasValue) + { + isAnyRunning = !parallel.Value.IsCompleted; + } + } + else + { + isAnyRunning = false; + foreach (Task t in CollectionsMarshal.AsSpan(threadPool)) + { + if (t.Status != TaskStatus.RanToCompletion) + { + isAnyRunning = true; + break; + } + } + } Context.ProgressPercentage = (int)(PassedCombinations * 100 / Context.TotalCombinations); diff --git a/FloatTool/Views/MainWindow.xaml b/FloatTool/Views/MainWindow.xaml index d76e80f..8abaef3 100644 --- a/FloatTool/Views/MainWindow.xaml +++ b/FloatTool/Views/MainWindow.xaml @@ -21,6 +21,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:FloatTool" + xmlns:models="clr-namespace:FloatTool.ViewModels" xmlns:theme="clr-namespace:FloatTool.Theme" mc:Ignorable="d" KeyUp="Window_KeyUp" MinWidth="875" MinHeight="420" @@ -168,9 +169,9 @@ - - - + + + diff --git a/FloatTool/Views/MainWindow.xaml.cs b/FloatTool/Views/MainWindow.xaml.cs index b978038..e5aefc1 100644 --- a/FloatTool/Views/MainWindow.xaml.cs +++ b/FloatTool/Views/MainWindow.xaml.cs @@ -15,6 +15,8 @@ - along with this program. If not, see . */ +using FloatTool.Common; +using FloatTool.ViewModels; using Newtonsoft.Json; using System; using System.Collections.Concurrent; @@ -37,490 +39,523 @@ namespace FloatTool { - public sealed partial class MainWindow : Window - { - public MainViewModel ViewModel; - private readonly RPCSettingsPersist RPCSettings = new(); - - private static long PassedCombinations; - private static List ThreadPool; - private static CancellationTokenSource TokenSource = new(); - public CancellationToken CancellationToken; - private static SoundPlayer CombinationFoundSound; - - public void UpdateRichPresence(bool clear = false) - { - if (clear) - { - RPCSettings.Details = "%m_SettingUpSearch%"; - RPCSettings.State = ""; - RPCSettings.ShowTime = false; - } - - if (AppHelpers.Settings.DiscordRPC) - { - AppHelpers.DiscordClient.SetPresence(RPCSettings.GetPresense()); - } - } - - public MainWindow() - { - InitializeComponent(); - using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("FloatTool.Assets.Found.wav")) - { - CombinationFoundSound = new SoundPlayer(stream); - CombinationFoundSound.Load(); - } - - App.SelectCulture(AppHelpers.Settings.LanguageCode); - App.SelectTheme(AppHelpers.Settings.ThemeURI); - - ViewModel = new MainViewModel("Nova", "Predator", "Field-Tested", "0.250000000", 100, 20, ErrorTooltip, ErrorTooltipFloat); - - MaxHeight = SystemParameters.WorkArea.Height + 12; - MaxWidth = SystemParameters.WorkArea.Width + 12; - - UpdateRichPresence(true); - DataContext = ViewModel; - - Logger.Log.Info("Main window started"); - - if (AppHelpers.Settings.CheckForUpdates) - { - Task.Factory.StartNew(() => - { - var update = Utils.CheckForUpdates().Result; - if (update != null && update.TagName != AppHelpers.VersionCode) - { - Dispatcher.Invoke(new Action(() => - { - Logger.Log.Info("New version available"); - var updateWindow = new UpdateWindow(update) - { - Owner = this - }; - updateWindow.ShowDialog(); - })); - } - }); - } - } - - protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) - { - base.OnMouseLeftButtonDown(e); - if (e.GetPosition(this).Y < 40) DragMove(); - } - - private async void Window_KeyUp(object sender, KeyEventArgs e) - { - switch (e.Key) - { - case Key.F1: - Process.Start(new ProcessStartInfo { FileName = "https://prevter.ml/floattool/table", UseShellExecute = true }); - break; - case Key.F2: - Process.Start(new ProcessStartInfo { FileName = "https://prevter.ml/floattool/tools", UseShellExecute = true }); - break; - case Key.F3: - string skin = $"{ViewModel.WeaponName} | {ViewModel.SkinName}"; - var collection = ViewModel.FindSkinCollection(skin); - Process.Start(new ProcessStartInfo { FileName = collection.Link, UseShellExecute = true }); - break; - case Key.F4: - var link = $"https://steamcommunity.com/market/listings/730/{ViewModel.FullSkinName}"; - Process.Start(new ProcessStartInfo { FileName = link, UseShellExecute = true }); - break; - case Key.F5: - ViewModel.FoundCombinations.Sort((a, b) => a.Price.CompareTo(b.Price)); - break; - case Key.F12: - break; - } - } - - private void WindowButton_Click(object sender, RoutedEventArgs e) - { - switch (((Button)sender).Name) - { - case "CloseButton": - Logger.Log.Info("Closing"); - Environment.Exit(Environment.ExitCode); - break; - case "MaximizeButton": - WindowState = (WindowState == WindowState.Maximized) ? WindowState.Normal : WindowState.Maximized; - break; - case "MinimizeButton": - WindowState = WindowState.Minimized; - break; - case "DiscordButton": - Process.Start(new ProcessStartInfo { FileName = "https://discord.gg/RM9VrzMfhP", UseShellExecute = true }); - break; - case "BenchmarkButton": - new BenchmarkWindow().ShowDialog(); - break; - case "SettingsButton": - new SettingsWindow().ShowDialog(); - break; - } - - // This will return rich presense to last state and update the language - UpdateRichPresence(); - } - - private void SetStatus(string stringCode) - { - if (Thread.CurrentThread == Application.Current.Dispatcher.Thread) - StatusBar.SetResourceReference(TextBlock.TextProperty, stringCode); - else - Dispatcher.Invoke(new Action(() => { SetStatus(stringCode); })); - } - - private void FloatCraftWorkerThread(CraftSearchSetup options) - { - int size = options.SkinPool.Length - 10; - int[] numbers = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - InputSkin[] resultList = new InputSkin[10]; - bool running = true; - - for (int i = 0; i < options.ThreadID; i++) - running = Calculations.NextCombination(numbers, size); - - while (running) - { - for (int i = 0; i < 10; ++i) - resultList[i] = options.SkinPool[numbers[i]]; - - // Check if the combination is valid - - for (int i = 0; i < options.Outcomes.Length; ++i) - { - double resultFloat = Calculations.Craft( - resultList, options.Outcomes[i].MinFloat, options.Outcomes[i].FloatRange - ); - - bool gotResult = false; - - switch (options.SearchMode) - { - case SearchMode.Equal: - gotResult = Math.Abs(resultFloat - options.SearchTarget) < options.TargetPrecision; - break; - case SearchMode.Less: - gotResult = resultFloat < options.SearchTarget; - break; - case SearchMode.Greater: - gotResult = resultFloat > options.SearchTarget; - break; - } - - if (gotResult) - { - if (Math.Round(resultFloat, 14, MidpointRounding.AwayFromZero) - .ToString(CultureInfo.InvariantCulture) - .StartsWith(options.SearchFilter, StringComparison.Ordinal) - || options.SearchMode != SearchMode.Equal) - { - InputSkin[] result = (InputSkin[])resultList.Clone(); - float price = 0; - float ieeesum = 0; - foreach (var skin in result) - { - price += skin.Price; - ieeesum += (float)skin.WearValue; - } - ieeesum /= 10; - float ieee = ((float)options.Outcomes[i].MaxFloat - (float)options.Outcomes[i].MinFloat) * ieeesum + (float)options.Outcomes[i].MinFloat; - - Dispatcher.Invoke(new Action(() => - { - ViewModel.FoundCombinations.Add(new Combination - { - Wear = resultFloat, - OutcomeName = options.Outcomes[i].Name, - Inputs = result, - Currency = result[0].SkinCurrency, - Price = price, - Wear32Bit = ((double)ieee).ToString("0.000000000000000", CultureInfo.InvariantCulture), - }); - if (AppHelpers.Settings.Sound) - CombinationFoundSound.Play(); - })); - } - } - } - - Interlocked.Increment(ref PassedCombinations); - - if (CancellationToken.IsCancellationRequested) - break; - - // Get next combination - - for (int i = 0; i < options.ThreadCount; i++) - running = Calculations.NextCombination(numbers, size); - } - } - - private void StartSearchButton_Click(object sender, RoutedEventArgs e) - { - bool stopAfterHit = (sender as Button).Name == "FindOneButton"; - - if (ViewModel.CanEditSettings) - { - ViewModel.FoundCombinations.Clear(); - StartButton.SetResourceReference(ContentProperty, "m_Stop"); - - TokenSource.Dispose(); - TokenSource = new CancellationTokenSource(); - CancellationToken = TokenSource.Token; - - if (AppHelpers.Settings.DiscordRPC) - { - RPCSettings.Details = $"%m_Searching% {ViewModel.FullSkinName}"; - RPCSettings.State = $"%m_DesiredFloat% {ViewModel.SearchFilter}"; - RPCSettings.Timestamp = DiscordRPC.Timestamps.Now; - RPCSettings.ShowTime = true; - - UpdateRichPresence(); - } - - new Thread(() => - { - Logger.Log.Info("Starting up search"); - SetStatus("m_SearchingSkin"); - PassedCombinations = 0; - ViewModel.CanEditSettings = false; - ViewModel.ProgressPercentage = 0; - ViewModel.TotalCombinations = Calculations.GetCombinationsCount(ViewModel.SkinCount); - - int index = 0; - Skin[] outcomes = Array.Empty(); - bool found = false; - foreach (var outcome in ViewModel.Outcomes.Values) - { - if (index++ == ViewModel.OutcomeIndex) - { - outcomes = new Skin[] { outcome[0] }; - found = true; - break; - } - } - if (!found) - { - List everything = new(); - foreach (var outcome in ViewModel.Outcomes.Values) - everything.Add(outcome[0]); - outcomes = everything.ToArray(); - } - - ConcurrentBag inputSkinBag = new(); - - string url = $"https://steamcommunity.com/market/listings/730/{ViewModel.FullSkinName}/render/?count={ViewModel.SkinCount}&start={ViewModel.SkinSkipCount}¤cy={(int)AppHelpers.Settings.Currency}"; - try - { - using var client = new HttpClient(); - HttpResponseMessage response = client.GetAsync(url).Result; - response.EnsureSuccessStatusCode(); - string responseBody = response.Content.ReadAsStringAsync().Result; - dynamic r = JsonConvert.DeserializeObject(responseBody); - - if (r["success"] == false) - { - Logger.Log.Error($"Steam haven't returned a success code\n{r.ToString()}"); - throw new ValueUnavailableException("Steam server returned error."); - } - - SetStatus("m_GettingFloats"); - - Dictionary, (string, float)> floatTasks = new(); - foreach (var skin in r["listinginfo"]) - { - string lid = r["listinginfo"][skin.Name]["listingid"].ToString(); - string aid = r["listinginfo"][skin.Name]["asset"]["id"].ToString(); - string link = r["listinginfo"][skin.Name]["asset"]["market_actions"][0]["link"].ToString(); - link = link.Replace("%assetid%", aid).Replace("%listingid%", lid); - try - { - floatTasks.Add( - Utils.GetWearFromInspectURL(link), - (lid, (float.Parse(r["listinginfo"][skin.Name]["converted_price"].ToString()) + - float.Parse(r["listinginfo"][skin.Name]["converted_fee"].ToString())) / 100) - ); - } - catch (Exception ex) - { - Logger.Log.Error($"Error getting float from link {link}", ex); - } - } - - foreach (var task in floatTasks.Keys) - { - try - { - inputSkinBag.Add(new InputSkin( - task.Result, - floatTasks[task].Item2, - AppHelpers.Settings.Currency, - floatTasks[task].Item1 - )); - - ViewModel.ProgressPercentage = (float)inputSkinBag.Count * 100 / floatTasks.Count; - } - catch (Exception ex) - { - Logger.Log.Error($"Error getting float from task {task.Id}", ex); - } - } - } - catch (ValueUnavailableException) - { - SetStatus("m_ErrorCouldntGetFloats"); - Dispatcher.Invoke( - new Action(() => - { - StartButton.SetResourceReference(ContentProperty, "m_Start"); - ViewModel.CanEditSettings = true; - }) - ); - return; - } - catch (Exception ex) - { - Logger.Log.Error("Error getting floats from marketplace", ex); - SetStatus("m_ErrorCouldntGetFloats"); - } - - if (inputSkinBag.Count < 10) - { - Logger.Log.Error("Couldn't get more than 10 floats from marketplace"); - SetStatus("m_ErrorLessThan10"); - Dispatcher.Invoke( - new Action(() => - { - StartButton.SetResourceReference(ContentProperty, "m_Start"); - ViewModel.CanEditSettings = true; - }) - ); - return; - } - - List inputSkinList = inputSkinBag.ToList(); - - // sort skins by price in ascending order - inputSkinList.Sort((a, b) => a.Price.CompareTo(b.Price)); - - if (ViewModel.Sort) - { - if (ViewModel.SortDescending) - inputSkinList.Sort((a, b) => b.CompareTo(a)); - else - inputSkinList.Sort((a, b) => a.CompareTo(b)); - } - InputSkin[] inputSkins = inputSkinList.ToArray(); - ViewModel.TotalCombinations = Calculations.GetCombinationsCount(inputSkinBag.Count); - - SetStatus("m_StartingUpSearch"); - - string searchFilter = ViewModel.SearchFilter; - - double searched = double.Parse(searchFilter, CultureInfo.InvariantCulture); - double precission = Math.Pow(0.1, searchFilter.Length - 2); - - // Create thread pool - ThreadPool = new(); - int threads = ViewModel.ThreadCount; - - try - { - for (int i = 0; i < threads; i++) - { - var startIndex = i; - var newThread = Task.Factory.StartNew(() => FloatCraftWorkerThread( - new CraftSearchSetup - { - SkinPool = inputSkins, - Outcomes = outcomes, - SearchTarget = searched, - TargetPrecision = precission, - SearchFilter = searchFilter, - SearchMode = ViewModel.SearchModeSelected, - ThreadID = startIndex, - ThreadCount = threads, - } - )); - ThreadPool.Add(newThread); - } - } - catch (Exception ex) - { - Logger.Log.Error("Error starting up thread pool", ex); - } - - SetStatus("m_Searching"); - var startTime = Stopwatch.GetTimestamp(); - long LastCombinationCount = 0; - - while (true) - { - long newTime = Stopwatch.GetTimestamp(); - double timeSinceLast = Utils.GetTimePassed(startTime, newTime).TotalMilliseconds; - startTime = newTime; - - bool isAnyRunning = false; - foreach (Task t in CollectionsMarshal.AsSpan(ThreadPool)) - { - if (t.Status != TaskStatus.RanToCompletion) - { - isAnyRunning = true; - break; - } - } - - if (timeSinceLast > 0) - ViewModel.CurrentSpeedLabel = - ( - (PassedCombinations - LastCombinationCount) - * 1000 - / timeSinceLast - ).ToString("n0"); - - LastCombinationCount = PassedCombinations; - ViewModel.ProgressPercentage = (float)PassedCombinations * 100 / ViewModel.TotalCombinations; - ViewModel.ParsedCombinations = PassedCombinations; - ViewModel.CombinationsLabel = string.Empty; - - if (!isAnyRunning) - break; - - if (stopAfterHit && ViewModel.FoundCombinations.Count >= 1) - { - TokenSource.Cancel(); - break; - } - - Thread.Sleep(100); - } - - UpdateRichPresence(true); - Logger.Log.Info("Finished searching"); - Dispatcher.Invoke( - new Action(() => - { - StartButton.SetResourceReference(ContentProperty, "m_Start"); - ViewModel.CanEditSettings = true; - SetStatus("m_FinishedSearching"); - }) - ); - - }).Start(); - } - else - { - Logger.Log.Info("Canceling task"); - TokenSource.Cancel(); - UpdateRichPresence(true); - } - } - } + public sealed partial class MainWindow : Window + { + public MainViewModel ViewModel; + private readonly RPCSettingsPersist RPCSettings = new(); + + private static long PassedCombinations; + private static List ThreadPool; + private static CancellationTokenSource TokenSource = new(); + public CancellationToken CancellationToken; + private static SoundPlayer CombinationFoundSound; + + public void UpdateRichPresence(bool clear = false) + { + if (clear) + { + RPCSettings.Details = "%m_SettingUpSearch%"; + RPCSettings.State = ""; + RPCSettings.ShowTime = false; + } + + if (AppHelpers.Settings.DiscordRPC) + { + AppHelpers.DiscordClient.SetPresence(RPCSettings.GetPresense()); + } + } + + public MainWindow() + { + InitializeComponent(); + using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("FloatTool.Assets.Found.wav")) + { + CombinationFoundSound = new SoundPlayer(stream); + CombinationFoundSound.Load(); + } + + App.SelectCulture(AppHelpers.Settings.LanguageCode); + App.SelectTheme(AppHelpers.Settings.ThemeURI); + + ViewModel = new MainViewModel("Nova", "Predator", "Field-Tested", "0.250000000", 100, 20, ErrorTooltip, ErrorTooltipFloat); + + MaxHeight = SystemParameters.WorkArea.Height + 12; + MaxWidth = SystemParameters.WorkArea.Width + 12; + + UpdateRichPresence(true); + DataContext = ViewModel; + + Logger.Info("Main window started"); + + if (AppHelpers.Settings.CheckForUpdates) + { + Task.Factory.StartNew(() => + { + var update = Utils.CheckForUpdates().Result; + if (update != null && update.TagName != AppHelpers.VersionCode) + { + Dispatcher.Invoke(new Action(() => + { + Logger.Info("New version available"); + var updateWindow = new UpdateWindow(update) + { + Owner = this + }; + updateWindow.ShowDialog(); + })); + } + }); + } + } + + protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) + { + base.OnMouseLeftButtonDown(e); + if (e.GetPosition(this).Y < 40) DragMove(); + } + + private void Window_KeyUp(object sender, KeyEventArgs e) + { + switch (e.Key) + { + case Key.F1: + Process.Start(new ProcessStartInfo { FileName = "https://prevter.ml/floattool/table", UseShellExecute = true }); + break; + case Key.F2: + Process.Start(new ProcessStartInfo { FileName = "https://prevter.ml/floattool/tools", UseShellExecute = true }); + break; + case Key.F3: + string skin = $"{ViewModel.WeaponName} | {ViewModel.SkinName}"; + var collection = ViewModel.FindSkinCollection(skin); + Process.Start(new ProcessStartInfo { FileName = collection.Link, UseShellExecute = true }); + break; + case Key.F4: + var link = $"https://steamcommunity.com/market/listings/730/{ViewModel.FullSkinName}"; + Process.Start(new ProcessStartInfo { FileName = link, UseShellExecute = true }); + break; + case Key.F5: + ViewModel.FoundCombinations.Sort((a, b) => a.Price.CompareTo(b.Price)); + break; + case Key.F12: + break; + } + } + + private void WindowButton_Click(object sender, RoutedEventArgs e) + { + switch (((Button)sender).Name) + { + case "CloseButton": + Logger.Info("Closing"); + Environment.Exit(Environment.ExitCode); + break; + case "MaximizeButton": + WindowState = (WindowState == WindowState.Maximized) ? WindowState.Normal : WindowState.Maximized; + break; + case "MinimizeButton": + WindowState = WindowState.Minimized; + break; + case "DiscordButton": + Process.Start(new ProcessStartInfo { FileName = "https://discord.gg/RM9VrzMfhP", UseShellExecute = true }); + break; + case "BenchmarkButton": + new BenchmarkWindow().ShowDialog(); + break; + case "SettingsButton": + new SettingsWindow().ShowDialog(); + break; + } + + // This will return rich presense to last state and update the language + UpdateRichPresence(); + } + + private void SetStatus(string stringCode) + { + if (Thread.CurrentThread == Application.Current.Dispatcher.Thread) + StatusBar.SetResourceReference(TextBlock.TextProperty, stringCode); + else + Dispatcher.Invoke(new Action(() => { SetStatus(stringCode); })); + } + + private void FloatCraftWorkerThread(CraftSearchSetup options) + { + int size = options.SkinPool.Length - 10; + int[] numbers = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + InputSkin[] resultList = new InputSkin[10]; + bool running = true; + + running = Calculations.NextCombination(numbers, size, options.ThreadID); + + while (running) + { + for (int i = 0; i < 10; ++i) + resultList[i] = options.SkinPool[numbers[i]]; + + // Check if the combination is valid + + for (int i = 0; i < options.Outcomes.Length; ++i) + { + double resultFloat = Calculations.Craft( + resultList, options.Outcomes[i].MinFloat, options.Outcomes[i].FloatRange + ); + + bool gotResult = false; + + switch (options.SearchMode) + { + case SearchMode.Equal: + gotResult = Math.Abs(resultFloat - options.SearchTarget) < options.TargetPrecision; + break; + case SearchMode.Less: + gotResult = resultFloat < options.SearchTarget; + break; + case SearchMode.Greater: + gotResult = resultFloat > options.SearchTarget; + break; + } + + if (gotResult) + { + if (Math.Round(resultFloat, 14, MidpointRounding.AwayFromZero) + .ToString(CultureInfo.InvariantCulture) + .StartsWith(options.SearchFilter, StringComparison.Ordinal) + || options.SearchMode != SearchMode.Equal) + { + InputSkin[] result = (InputSkin[])resultList.Clone(); + float price = 0; + float ieeesum = 0; + foreach (var skin in result) + { + price += skin.Price; + ieeesum += (float)skin.WearValue; + } + ieeesum /= 10; + float ieee = ((float)options.Outcomes[i].MaxFloat - (float)options.Outcomes[i].MinFloat) * ieeesum + (float)options.Outcomes[i].MinFloat; + + Dispatcher.Invoke(new Action(() => + { + ViewModel.FoundCombinations.Add(new Combination + { + Wear = resultFloat, + OutcomeName = options.Outcomes[i].Name, + Inputs = result, + Currency = result[0].SkinCurrency, + Price = price, + Wear32Bit = ((double)ieee).ToString("0.000000000000000", CultureInfo.InvariantCulture), + }); + if (AppHelpers.Settings.Sound) + CombinationFoundSound.Play(); + })); + } + } + } + + Interlocked.Increment(ref PassedCombinations); + + if (CancellationToken.IsCancellationRequested) + break; + + // Get next combination + + running = Calculations.NextCombination(numbers, size, options.ThreadCount); + } + } + + private void StartSearchButton_Click(object sender, RoutedEventArgs e) + { + bool stopAfterHit = (sender as Button).Name == "FindOneButton"; + + if (ViewModel.CanEditSettings) + { + ViewModel.FoundCombinations.Clear(); + StartButton.SetResourceReference(ContentProperty, "m_Stop"); + + TokenSource.Dispose(); + TokenSource = new CancellationTokenSource(); + CancellationToken = TokenSource.Token; + + if (AppHelpers.Settings.DiscordRPC) + { + RPCSettings.Details = $"%m_Searching% {ViewModel.FullSkinName}"; + RPCSettings.State = $"%m_DesiredFloat% {ViewModel.SearchFilter}"; + RPCSettings.Timestamp = DiscordRPC.Timestamps.Now; + RPCSettings.ShowTime = true; + + UpdateRichPresence(); + } + + new Thread(() => + { + Logger.Info("Starting up search"); + SetStatus("m_SearchingSkin"); + PassedCombinations = 0; + ViewModel.CanEditSettings = false; + ViewModel.ProgressPercentage = 0; + ViewModel.TotalCombinations = Calculations.GetCombinationsCount(ViewModel.SkinCount); + + int index = 0; + Skin[] outcomes = Array.Empty(); + bool found = false; + foreach (var outcome in ViewModel.Outcomes.Values) + { + if (index++ == ViewModel.OutcomeIndex) + { + outcomes = new Skin[] { outcome[0] }; + found = true; + break; + } + } + if (!found) + { + List everything = new(); + foreach (var outcome in ViewModel.Outcomes.Values) + everything.Add(outcome[0]); + outcomes = everything.ToArray(); + } + + ConcurrentBag inputSkinBag = new(); + + string url = $"https://steamcommunity.com/market/listings/730/{ViewModel.FullSkinName}/render/?count={ViewModel.SkinCount}&start={ViewModel.SkinSkipCount}¤cy={(int)AppHelpers.Settings.Currency}"; + try + { + using var client = new HttpClient(); + HttpResponseMessage response = client.GetAsync(url).Result; + response.EnsureSuccessStatusCode(); + string responseBody = response.Content.ReadAsStringAsync().Result; + dynamic r = JsonConvert.DeserializeObject(responseBody); + + if (r["success"] == false) + { + Logger.Error($"Steam haven't returned a success code\n{r.ToString()}"); + throw new ValueUnavailableException("Steam server returned error."); + } + + SetStatus("m_GettingFloats"); + + Dictionary, (string, float)> floatTasks = new(); + foreach (var skin in r["listinginfo"]) + { + string lid = r["listinginfo"][skin.Name]["listingid"].ToString(); + string aid = r["listinginfo"][skin.Name]["asset"]["id"].ToString(); + string link = r["listinginfo"][skin.Name]["asset"]["market_actions"][0]["link"].ToString(); + link = link.Replace("%assetid%", aid).Replace("%listingid%", lid); + try + { + floatTasks.Add( + Utils.GetWearFromInspectURL(link), + (lid, (float.Parse(r["listinginfo"][skin.Name]["converted_price"].ToString()) + + float.Parse(r["listinginfo"][skin.Name]["converted_fee"].ToString())) / 100) + ); + } + catch (Exception ex) + { + Logger.Log.Error($"Error getting float from link {link}", ex); + } + } + + foreach (var task in floatTasks.Keys) + { + try + { + inputSkinBag.Add(new InputSkin( + task.Result, + floatTasks[task].Item2, + AppHelpers.Settings.Currency, + floatTasks[task].Item1 + )); + + ViewModel.ProgressPercentage = (float)inputSkinBag.Count * 100 / floatTasks.Count; + } + catch (Exception ex) + { + Logger.Log.Error($"Error getting float from task {task.Id}", ex); + } + } + } + catch (ValueUnavailableException) + { + SetStatus("m_ErrorCouldntGetFloats"); + Dispatcher.Invoke( + new Action(() => + { + StartButton.SetResourceReference(ContentProperty, "m_Start"); + ViewModel.CanEditSettings = true; + }) + ); + return; + } + catch (Exception ex) + { + Logger.Log.Error("Error getting floats from marketplace", ex); + SetStatus("m_ErrorCouldntGetFloats"); + } + + if (inputSkinBag.Count < 10) + { + Logger.Error("Couldn't get more than 10 floats from marketplace"); + SetStatus("m_ErrorLessThan10"); + Dispatcher.Invoke( + new Action(() => + { + StartButton.SetResourceReference(ContentProperty, "m_Start"); + ViewModel.CanEditSettings = true; + }) + ); + return; + } + + List inputSkinList = inputSkinBag.ToList(); + + // sort skins by price in ascending order + inputSkinList.Sort((a, b) => a.Price.CompareTo(b.Price)); + + if (ViewModel.Sort) + { + if (ViewModel.SortDescending) + inputSkinList.Sort((a, b) => b.CompareTo(a)); + else + inputSkinList.Sort((a, b) => a.CompareTo(b)); + } + InputSkin[] inputSkins = inputSkinList.ToArray(); + ViewModel.TotalCombinations = Calculations.GetCombinationsCount(inputSkinBag.Count); + + SetStatus("m_StartingUpSearch"); + + string searchFilter = ViewModel.SearchFilter; + + double searched = double.Parse(searchFilter, CultureInfo.InvariantCulture); + double precission = Math.Pow(0.1, searchFilter.Length - 2); + + // Create thread pool + ThreadPool = new(); + int threads = ViewModel.ThreadCount; + + ParallelLoopResult? parallel = null; + + try + { + if (AppHelpers.Settings.UseParallel) + { + Task.Run(() => + { + parallel = Parallel.For(0, threads, i => + { + FloatCraftWorkerThread(new CraftSearchSetup + { + SkinPool = inputSkins, + Outcomes = outcomes, + SearchTarget = searched, + TargetPrecision = precission, + SearchFilter = searchFilter, + SearchMode = ViewModel.SearchModeSelected, + ThreadID = i, + ThreadCount = threads, + }); + }); + }); + } + else + { + for (int i = 0; i < threads; i++) + { + var startIndex = i; + var newThread = Task.Factory.StartNew(() => FloatCraftWorkerThread( + new CraftSearchSetup + { + SkinPool = inputSkins, + Outcomes = outcomes, + SearchTarget = searched, + TargetPrecision = precission, + SearchFilter = searchFilter, + SearchMode = ViewModel.SearchModeSelected, + ThreadID = startIndex, + ThreadCount = threads, + } + )); + ThreadPool.Add(newThread); + } + } + } + catch (Exception ex) + { + Logger.Log.Error("Error starting up thread pool", ex); + } + + SetStatus("m_Searching"); + var startTime = Stopwatch.GetTimestamp(); + long LastCombinationCount = 0; + + while (true) + { + long newTime = Stopwatch.GetTimestamp(); + double timeSinceLast = Utils.GetTimePassed(startTime, newTime).TotalMilliseconds; + startTime = newTime; + + bool isAnyRunning; + if (AppHelpers.Settings.UseParallel) + { + isAnyRunning = true; + if (parallel.HasValue) + isAnyRunning = !parallel.Value.IsCompleted; + } + else + { + isAnyRunning = false; + foreach (Task t in CollectionsMarshal.AsSpan(ThreadPool)) + { + if (t.Status != TaskStatus.RanToCompletion) + { + isAnyRunning = true; + break; + } + } + } + + if (timeSinceLast > 0) + ViewModel.CurrentSpeedLabel = + ( + (PassedCombinations - LastCombinationCount) + * 1000 + / timeSinceLast + ).ToString("n0"); + + LastCombinationCount = PassedCombinations; + ViewModel.ProgressPercentage = (float)PassedCombinations * 100 / ViewModel.TotalCombinations; + ViewModel.ParsedCombinations = PassedCombinations; + ViewModel.CombinationsLabel = string.Empty; + + if (!isAnyRunning) + break; + + if (stopAfterHit && ViewModel.FoundCombinations.Count >= 1) + { + TokenSource.Cancel(); + break; + } + + Thread.Sleep(100); + } + + UpdateRichPresence(true); + Logger.Info("Finished searching"); + Dispatcher.Invoke( + new Action(() => + { + StartButton.SetResourceReference(ContentProperty, "m_Start"); + ViewModel.CanEditSettings = true; + SetStatus("m_FinishedSearching"); + }) + ); + + }).Start(); + } + else + { + Logger.Info("Canceling task"); + TokenSource.Cancel(); + UpdateRichPresence(true); + } + } + } } diff --git a/FloatTool/Views/SettingsWindow.xaml b/FloatTool/Views/SettingsWindow.xaml index 36f64aa..19dcc0f 100644 --- a/FloatTool/Views/SettingsWindow.xaml +++ b/FloatTool/Views/SettingsWindow.xaml @@ -25,7 +25,7 @@ WindowStartupLocation="CenterScreen" WindowStyle="None" AllowsTransparency="True" ResizeMode="NoResize" - Background="Transparent" d:DesignHeight="370"> + Background="Transparent" d:DesignHeight="400"> @@ -149,6 +149,10 @@ Grid.Column="1" Grid.Row="1" Height="24" ItemsSource="{Binding ExtensionNames}" SelectedIndex="{Binding SelectedExtension}"/> + + + + diff --git a/FloatTool/Views/SettingsWindow.xaml.cs b/FloatTool/Views/SettingsWindow.xaml.cs index bd10716..231151c 100644 --- a/FloatTool/Views/SettingsWindow.xaml.cs +++ b/FloatTool/Views/SettingsWindow.xaml.cs @@ -15,6 +15,8 @@ - along with this program. If not, see . */ +using FloatTool.Common; +using FloatTool.ViewModels; using System; using System.Diagnostics; using System.IO; @@ -23,7 +25,7 @@ namespace FloatTool { - public sealed partial class SettingsWindow : Window + public sealed partial class SettingsWindow : Window { public SettingsWindow() { diff --git a/FloatTool/Views/UpdateWindow.xaml.cs b/FloatTool/Views/UpdateWindow.xaml.cs index a27f363..7cc5c6d 100644 --- a/FloatTool/Views/UpdateWindow.xaml.cs +++ b/FloatTool/Views/UpdateWindow.xaml.cs @@ -15,6 +15,7 @@ - along with this program. If not, see . */ +using FloatTool.Common; using System; using System.Diagnostics; using System.IO; @@ -27,7 +28,7 @@ namespace FloatTool { - public sealed partial class UpdateWindow : Window + public sealed partial class UpdateWindow : Window { public UpdateResult UpdateResult;