Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions FloatTool/Common/Calculations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,16 @@ namespace FloatTool.Common
{
static public class Calculations
{
public static double Craft(InputSkin[] ingridients, double minFloat, double floatRange)
public static double Craft(InputSkin[] ingredients, double minFloat, double maxFloat)
{
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;
double sumNormalized = 0.0;

for (int i = 0; i < 10; i++)
sumNormalized += ingredients[i].NormalizedWear;

double avgNormalized = sumNormalized / 10.0;

return minFloat + avgNormalized * (maxFloat - minFloat);
}

public static bool NextCombination(int[] num, int n)
Expand Down
17 changes: 14 additions & 3 deletions FloatTool/Common/Skin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,13 @@ 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;
}

Expand Down Expand Up @@ -142,15 +140,28 @@ public struct InputSkin
public float Price;
public Currency SkinCurrency;
public string ListingID;
public double MinFloat;
public double MaxFloat;
public double NormalizedWear;

public double GetWearValue => WearValue;

public InputSkin(double wear, float price, Currency currency, string listingId = "")
public InputSkin(double wear, float price, Currency currency, string listingId = "",
double minFloat = 0.0, double maxFloat = 1.0)
{
WearValue = wear;
Price = price;
SkinCurrency = currency;
ListingID = listingId;
MinFloat = minFloat;
MaxFloat = maxFloat;

// Pre-compute normalized wear value
double range = maxFloat - minFloat;
if (range < 0.0001) range = 0.0001;
NormalizedWear = (wear - minFloat) / range;
if (NormalizedWear < 0.0) NormalizedWear = 0.0;
if (NormalizedWear > 1.0) NormalizedWear = 1.0;
}

internal int CompareTo(InputSkin b)
Expand Down
46 changes: 39 additions & 7 deletions FloatTool/ViewModels/MainViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -381,19 +381,51 @@ public void UpdateFloatRange()
minCraftWear = (float)currentMinWear;
maxCraftWear = (float)currentMaxWear;
UpdateFloatError();
FloatRange = $"{minCraftWear.ToString("0.00", CultureInfo.InvariantCulture)} - {maxCraftWear.ToString("0.00", CultureInfo.InvariantCulture)}";
FloatRange = $"{minCraftWear.ToString("0.0000", CultureInfo.InvariantCulture)} - {maxCraftWear.ToString("0.0000", CultureInfo.InvariantCulture)}";
return;
}

var range = Skin.GetFloatRangeForQuality(SkinQuality);
// Find input skin in database to get its actual MinWear/MaxWear
string inputSkinName = $"{WeaponName} | {SkinName}";
SkinModel? inputSkinModel = null;

if (SkinsDatabase != null)
{
foreach (var collection in SkinsDatabase)
{
foreach (var skin in collection.Skins)
{
if (skin.Name == inputSkinName)
{
inputSkinModel = skin;
break;
}
}
if (inputSkinModel != null) break;
}
}

if (inputSkinModel == null)
{
FloatRange = "No data";
return;
}

var qualityRange = Skin.GetFloatRangeForQuality(SkinQuality);
double inputSkinMin = inputSkinModel.Value.MinWear;
double inputSkinMax = inputSkinModel.Value.MaxWear;

List<InputSkin> lowest = new();
for (int i = 0; i < 10; i++)
lowest.Add(new InputSkin(range.Min, 0, Currency.USD));
lowest.Add(new InputSkin(qualityRange.Min, 0, Currency.USD, "",
minFloat: inputSkinMin,
maxFloat: inputSkinMax));

List<InputSkin> highest = new();
for (int i = 0; i < 10; i++)
highest.Add(new InputSkin(range.Max, 0, Currency.USD));
highest.Add(new InputSkin(qualityRange.Max, 0, Currency.USD, "",
minFloat: inputSkinMin,
maxFloat: inputSkinMax));

int index = 0;
minCraftWear = 0;
Expand All @@ -407,15 +439,15 @@ public void UpdateFloatRange()
Calculations.Craft(
lowest.ToArray(),
currSkin.MinFloat,
currSkin.FloatRange
currSkin.MaxFloat
),
CultureInfo.InvariantCulture
);
maxCraftWear = Convert.ToSingle(
Calculations.Craft(
highest.ToArray(),
currSkin.MinFloat,
currSkin.FloatRange
currSkin.MaxFloat
),
CultureInfo.InvariantCulture
);
Expand All @@ -424,7 +456,7 @@ public void UpdateFloatRange()
}

UpdateFloatError();
FloatRange = $"{minCraftWear.ToString("0.00", CultureInfo.InvariantCulture)} - {maxCraftWear.ToString("0.00", CultureInfo.InvariantCulture)}";
FloatRange = $"{minCraftWear.ToString("0.0000", CultureInfo.InvariantCulture)} - {maxCraftWear.ToString("0.0000", CultureInfo.InvariantCulture)}";
}

public void UpdateFloatError()
Expand Down
5 changes: 3 additions & 2 deletions FloatTool/Views/BenchmarkWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ private static void FloatCraftWorkerThread(CraftSearchSetup options)
for (int i = 0; i < options.Outcomes.Length; ++i)
{
double resultFloat = Calculations.Craft(
resultList, options.Outcomes[i].MinFloat, options.Outcomes[i].FloatRange
resultList, options.Outcomes[i].MinFloat, options.Outcomes[i].MaxFloat
);

bool gotResult = false;
Expand Down Expand Up @@ -161,7 +161,8 @@ private void StartBenchmark_Click(object sender, RoutedEventArgs e)

List<InputSkin> inputSkinsList = new();
foreach (double f in pool)
inputSkinsList.Add(new InputSkin(f, 0.03f, Currency.USD));
inputSkinsList.Add(new InputSkin(f, 0.03f, Currency.USD, "",
minFloat: 0.06, maxFloat: 0.8));

InputSkin[] inputSkins = inputSkinsList.ToArray();

Expand Down
112 changes: 89 additions & 23 deletions FloatTool/Views/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ private void SetStatus(string stringCode)
Dispatcher.Invoke(new Action(() => { SetStatus(stringCode); }));
}

private const int MAX_FOUND_RESULTS = 1000;
private static ConcurrentBag<Combination> PendingResults;
private static long FoundResultCount;


private void FloatCraftWorkerThread(CraftSearchSetup options)
{
int size = options.SkinPool.Length - 10;
Expand All @@ -201,7 +206,7 @@ private void FloatCraftWorkerThread(CraftSearchSetup options)
for (int i = 0; i < options.Outcomes.Length; ++i)
{
double resultFloat = Calculations.Craft(
resultList, options.Outcomes[i].MinFloat, options.Outcomes[i].FloatRange
resultList, options.Outcomes[i].MinFloat, options.Outcomes[i].MaxFloat
);

bool gotResult = false;
Expand All @@ -226,31 +231,32 @@ private void FloatCraftWorkerThread(CraftSearchSetup options)
.StartsWith(options.SearchFilter, StringComparison.Ordinal)
|| options.SearchMode != SearchMode.Equal)
{
// Check limit before doing expensive work
if (Interlocked.Read(ref FoundResultCount) >= MAX_FOUND_RESULTS)
break;

InputSkin[] result = (InputSkin[])resultList.Clone();
float price = 0;
float ieeesum = 0;
float ieeeNormSum = 0;
foreach (var skin in result)
{
price += skin.Price;
ieeesum += (float)skin.WearValue;
ieeeNormSum += (float)skin.NormalizedWear;
}
ieeesum /= 10;
float ieee = ((float)options.Outcomes[i].MaxFloat - (float)options.Outcomes[i].MinFloat) * ieeesum + (float)options.Outcomes[i].MinFloat;
ieeeNormSum /= 10;
float ieee = ((float)options.Outcomes[i].MaxFloat - (float)options.Outcomes[i].MinFloat) * ieeeNormSum + (float)options.Outcomes[i].MinFloat;

Dispatcher.Invoke(new Action(() =>
// Add to pending results (non-blocking)
Interlocked.Increment(ref FoundResultCount);
PendingResults.Add(new Combination
{
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();
}));
Wear = resultFloat,
OutcomeName = options.Outcomes[i].Name,
Inputs = result,
Currency = result[0].SkinCurrency,
Price = price,
Wear32Bit = ((double)ieee).ToString("0.000000000000000", CultureInfo.InvariantCulture),
});
}
}
}
Expand All @@ -260,6 +266,10 @@ private void FloatCraftWorkerThread(CraftSearchSetup options)
if (CancellationToken.IsCancellationRequested)
break;

// Stop if we've found enough results
if (Interlocked.Read(ref FoundResultCount) >= MAX_FOUND_RESULTS)
break;

// Get next combination

running = Calculations.NextCombination(numbers, size, options.ThreadCount);
Expand Down Expand Up @@ -389,6 +399,29 @@ private void StartSearchButton_Click(object sender, RoutedEventArgs e)
}
}

// Look up input skin's own min/max float from database
string inputSkinFullName = $"{ViewModel.WeaponName} | {ViewModel.SkinName}";
double skinMinFloat = 0.0, skinMaxFloat = 1.0;
if (ViewModel.SkinsDatabase != null)
{
foreach (var collection in ViewModel.SkinsDatabase)
{
bool skinFound = false;
foreach (var skinModel in collection.Skins)
{
if (skinModel.Name == inputSkinFullName)
{
skinMinFloat = skinModel.MinWear;
skinMaxFloat = skinModel.MaxWear;
skinFound = true;
break;
}
}
if (skinFound) break;
}
}


foreach (var task in floatTasks.Keys)
{
try
Expand All @@ -397,7 +430,9 @@ private void StartSearchButton_Click(object sender, RoutedEventArgs e)
task.Result,
floatTasks[task].Item2,
AppHelpers.Settings.Currency,
floatTasks[task].Item1
floatTasks[task].Item1,
minFloat: skinMinFloat,
maxFloat: skinMaxFloat
));

ViewModel.ProgressPercentage = (float)inputSkinBag.Count * 100 / floatTasks.Count;
Expand Down Expand Up @@ -474,8 +509,8 @@ private void StartSearchButton_Click(object sender, RoutedEventArgs e)
InputSkin[] worst = sorted.TakeLast(10).ToArray();

// Get min and max floats by running the craft function
double minFloat = Calculations.Craft(best, outcomes[0].MinFloat, outcomes[0].FloatRange);
double maxFloat = Calculations.Craft(worst, outcomes[0].MinFloat, outcomes[0].FloatRange);
double minFloat = Calculations.Craft(best, outcomes[0].MinFloat, outcomes[0].MaxFloat);
double maxFloat = Calculations.Craft(worst, outcomes[0].MinFloat, outcomes[0].MaxFloat);

Dispatcher.Invoke(new Action(() =>
{
Expand All @@ -496,6 +531,8 @@ private void StartSearchButton_Click(object sender, RoutedEventArgs e)
double precission = Math.Pow(0.1, searchFilter.Length - 2);

// Create thread pool
PendingResults = new ConcurrentBag<Combination>();
FoundResultCount = 0;
ThreadPool = new();
int threads = ViewModel.ThreadCount;

Expand Down Expand Up @@ -593,18 +630,47 @@ private void StartSearchButton_Click(object sender, RoutedEventArgs e)
ViewModel.ParsedCombinations = PassedCombinations;
ViewModel.CombinationsLabel = string.Empty;

if (!isAnyRunning)
break;
// Batch flush pending results to UI (must be on UI thread)
if (!PendingResults.IsEmpty)
{
Dispatcher.Invoke(new Action(() =>
{
while (PendingResults.TryTake(out var combo))
{
ViewModel.FoundCombinations.Add(combo);
if (AppHelpers.Settings.Sound && ViewModel.FoundCombinations.Count == 1)
CombinationFoundSound.Play();
}
}));
}

if (stopAfterHit && ViewModel.FoundCombinations.Count >= 1)
{
TokenSource.Cancel();
break;
}

// Limit max results to prevent memory exhaustion
if (Interlocked.Read(ref FoundResultCount) >= MAX_FOUND_RESULTS)
{
Logger.Info($"Max results limit ({MAX_FOUND_RESULTS}) reached, stopping search");
TokenSource.Cancel();
break;
}

if (!isAnyRunning)
break;

Thread.Sleep(100);
}

// Final flush of any remaining pending results
Dispatcher.Invoke(new Action(() =>
{
while (PendingResults.TryTake(out var combo))
ViewModel.FoundCombinations.Add(combo);
}));

UpdateRichPresence(true);
Logger.Info("Finished searching");
Dispatcher.Invoke(
Expand Down