private void SetupDebugWindow() { Task.Run(() => { DebugMenu = new DebugMenu(this); DebugMenu.Load += DebugMenu_Load; DebugMenu.ShowDialog(); }); SpinWait.SpinUntil(() => { return(DebugStarted); }); }
/// <summary> /// Gets the name of the player when already in the career profile. /// </summary> /// <returns></returns> internal string GetPlayerName( #if DEBUG #pragma warning disable 1573 string debugLetters = "" #pragma warning restore 1573 #endif ) { #if DEBUG if (debugLetters == null) { debugLetters = ""; } #endif // Generate and cache the alphabet. if (EnglishAlphabet == null) { EnglishAlphabet = GenerateAlphabet(); } int lineScanFade = 80; // The fade to detect a letter. int letterCheckFade = 80; // The fade to scan each pixel in the letter with. float cutoff = .92f; // The percent of pixels scanned a letter needs to be confirmed in order to be considered as the correct letter. int cy = 131; // The Y coordinate to scan at. int xStart = 131; // The X coordinate to start scanning at. int xEnd = 270; // The X coordinate to stop scanning at. int maxSinceLastLetter = 10; // The max number of pixels that need to past since the last detected letter to stop scanning. Tuple <char, char>[] prioritizeLetter = new Tuple <char, char>[] // Some characters look very similiar to eachother, this improves those conflicts. { new Tuple <char, char>('O', 'D'), new Tuple <char, char>('R', 'A'), }; using (LockHandler.Interactive) { UpdateScreen(); string playerName = string.Empty; // The name of the player. // Scan a line under the name in the career profile for text. for (int cx = xStart, sinceLastLetter = 0; cx < xEnd && sinceLastLetter < maxSinceLastLetter; cx++) { if (Capture.CompareColor(cx, cy, Colors.WHITE, lineScanFade)) { // A letter was detected. sinceLastLetter = 0; List <PlayerNameLetterResult> results = new List <PlayerNameLetterResult>(); // Stores the results of every letter scanned. The most likely letter will be used. for (int ax = cx; ax <= cx + 1; ax++) // Scan this pixel and the next pixel. { for (int i = 0; i < EnglishAlphabet.Length; i++) { List <Point> filledPixels = new List <Point>(); // All detected pixels. float total = 0; // The total number of pixels. float match = 0; // The pixels that match the Capture and the letter markup. bool failed = false; for (int lx = 0; lx < EnglishAlphabet.Markups[i].Width && !failed; lx++) { for (int ly = 0; ly < EnglishAlphabet.Markups[i].Height && !failed; ly++) { PixelType pixelType = GetPixelType(EnglishAlphabet.Markups[i], lx, ly); if (pixelType != PixelType.Any) { // px and py is the Capture's relative letter position. int px = ax + lx; int py = cy + ly + 2 - EnglishAlphabet.Markups[i].Height; bool pixelFilled = Capture.CompareColor(px, py, Colors.WHITE, letterCheckFade); // If a required pixel is missing, fail the letter. failed = pixelType == PixelType.Required && !pixelFilled; if (failed) { break; } total++; if ((pixelFilled && (pixelType == PixelType.Filled || pixelType == PixelType.Required)) || (!pixelFilled && pixelType == PixelType.Empty)) { match++; if (pixelType == PixelType.Filled || pixelType == PixelType.Required) { filledPixels.Add(new Point(lx, ly)); #region DEBUG #if DEBUG if (DebugMenu != null) { DebugMenu.SetDebugImage(px, py, Color.Green); } #endif #endregion } #region DEBUG #if DEBUG else if (DebugMenu != null) { DebugMenu.SetDebugImage(px, py, Color.DarkBlue); } #endif #endregion } #region DEBUG #if DEBUG else if (DebugMenu != null) { if (pixelType == PixelType.Filled) { DebugMenu.SetDebugImage(px, py, Color.DarkRed); } else { DebugMenu.SetDebugImage(px, py, Color.Yellow); } } #endif #endregion } } } if (!failed) { /* Check if every pixel that was detected is connected. For example, || will return false, but H will return true. * This makes letter scanning more certain for the BigNoodleTooOblique font because every pixel in the letters in that font is connected. * If GetPlayerName() and GenerateAlphabet() is extended to other languages this may need to be removed because other languages use * a different font whos characters are seperate; like 'i'. */ bool isConnected = PointsAreConnected(filledPixels); float result = !isConnected ? 0 : match / total; // Add the result to the list. results.Add(new PlayerNameLetterResult(EnglishAlphabet.Letters[i], result, (int)total, (int)match, EnglishAlphabet.Markups[i], EnglishAlphabet.LetterLengths[i])); #region DEBUG #if DEBUG if (DebugMenu != null) { Console.Write($"{EnglishAlphabet.Letters[i]} - R: "); if (result >= cutoff) { Console.BackgroundColor = ConsoleColor.DarkGreen; Console.Write($"{result}"); Console.BackgroundColor = ConsoleColor.Black; } else { Console.Write($"{result}"); } Console.WriteLine($" T: {total} - M: {match} - C: {isConnected}"); } #endif #endregion } #region DEBUG #if DEBUG else if (DebugMenu != null) { Console.WriteLine($"{EnglishAlphabet.Letters[i]} vital pixel missing."); } if (DebugMenu != null) { DebugMenu.InvalidateDebugImage(); if (debugLetters.Contains(EnglishAlphabet.Letters[i])) { Console.ReadLine(); } DebugMenu.ResetDebugImage(); } #endif #endregion } } // Remove the results whos result is less than the cutoff or is null. results = results.Where(result => result != null && result.Result >= cutoff).ToList(); /* Get the most likely letter by the most matching pixels. OrderByDescending and ThenByDescending will order the list like so: | Matching first, | Then percent. | A - Matching: 50, Total: 55 (50 / 55 = 90%) | B - Matching: 50, Total: 60 (60 / 50 = 83%) | C - Matching: 45, Total: 45 (45 / 45 = 100%) | | Getting the highest match is important because if the letter in the player's name is E, then L and F will have 100% | result also because L and F have the same lines as E. If the letter in the player's name is F, E won't be considered | because the lack of the bottom line brings it below the cutoff value. */ PlayerNameLetterResult mostLikely = results.OrderByDescending(result => result.Match).ThenByDescending(result => result.Result).FirstOrDefault(); // mostLikely will be null if no letters made it past the cutoff. if (mostLikely != null) { // Some characters look very similiar to eachother, this improves those conflicts. // The conflicts are manually set in the prioritizeLetter variable from earlier. foreach (var prioritize in prioritizeLetter) { if (mostLikely.Letter == prioritize.Item1) // If the mostLikely letter should be prioritized to something else. { int toCount = results.Count(r => r.Letter == prioritize.Item2); // The number of letters found in the results that the mostLikely's letter should be prioritized to. var bestTo = results.OrderByDescending(result => result.Match).FirstOrDefault(result => result.Letter == prioritize.Item2); // Get the letter to prioritize to. // If there are 2 occurences of the letter that mostLikely should be prioritized to, // or the prioritized letter has a better result percent than the mostLikely letter, // change the mostlikely letter to the prioritized letter. if (toCount == 2 || (bestTo != null && bestTo.Result > mostLikely.Result)) { mostLikely = bestTo; } } } #region DEBUG #if DEBUG if (DebugMenu != null) { Console.BackgroundColor = ConsoleColor.DarkBlue; Console.WriteLine($"Letter detected: {mostLikely.Letter}"); Console.BackgroundColor = ConsoleColor.Black; } #endif #endregion // Add the letter scanned to the player name. playerName += mostLikely.Letter; /* Increase the spot scanned on the X axis by the width of the letter scanned. * This is required so when scanning letters like H, it will start scanning again * on the right side of H. If it started scanning again where 'H' was detected, * it could incorrectly identify 'I' from the line on the right side of H. */ cx += mostLikely.LetterLength; } } // Color check else { sinceLastLetter++; } } // Line scan return(playerName); } }