[
  {
    "path": ".gitignore",
    "content": "*.json\n*.lnk\nsources/assets.zip\nsources/googleapi/GoogleClientIdentifiers.cs\ntest/*\ndatasource/*\nreleases/*\nml/*/data/*\nml/.vscode/*\n!datasource/!Start.bat\n!dalamud/*.json\n\n## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore\n\n# User-specific files\n*.rsuser\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Mono auto generated files\nmono_crash.*\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\n[Aa][Rr][Mm]/\n[Aa][Rr][Mm]64/\nbld/\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n\n# Visual Studio 2015/2017 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# Visual Studio 2017 auto generated files\nGenerated\\ Files/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# Benchmark Results\nBenchmarkDotNet.Artifacts/\n\n# .NET Core\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n\n# StyleCop\nStyleCopReport.xml\n\n# Files built by Visual Studio\n*_i.c\n*_p.c\n*_h.h\n*.ilk\n*.meta\n*.obj\n*.iobj\n*.pch\n*.pdb\n*.ipdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*_wpftmp.csproj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# Visual Studio Trace Files\n*.e2e\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# AxoCover is a Code Coverage Tool\n.axoCover/*\n!.axoCover/settings.json\n\n# Visual Studio code coverage results\n*.coverage\n*.coveragexml\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# Note: Comment the next line if you want to checkin your web deploy settings,\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/[Pp]ackages/*\n# except build/, which is used as an MSBuild target.\n!**/[Pp]ackages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/[Pp]ackages/repositories.config\n# NuGet v3's project.json files produces more ignorable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n*.appx\n*.appxbundle\n*.appxupload\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!?*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n# Including strong name files can present a security risk\n# (https://github.com/github/gitignore/pull/2483#issue-259490424)\n#*.snk\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\nServiceFabricBackup/\n*.rptproj.bak\n\n# SQL Server files\n*.mdf\n*.ldf\n*.ndf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n*.rptproj.rsuser\n*- Backup*.rdl\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\nnode_modules/\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)\n*.vbw\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# CodeRush personal settings\n.cr/personal\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc\n\n# Cake - Uncomment if you are using it\n# tools/**\n# !tools/packages.config\n\n# Tabs Studio\n*.tss\n\n# Telerik's JustMock configuration file\n*.jmconfig\n\n# BizTalk build output\n*.btp.cs\n*.btm.cs\n*.odx.cs\n*.xsd.cs\n\n# OpenCover UI analysis results\nOpenCover/\n\n# Azure Stream Analytics local run output\nASALocalRun/\n\n# MSBuild Binary and Structured Log\n*.binlog\n\n# NVidia Nsight GPU debugger configuration file\n*.nvuser\n\n# MFractors (Xamarin productivity tool) working folder\n.mfractor/\n\n# Local History for Visual Studio\n.localhistory/\n\n# BeatPulse healthcheck temp database\nhealthchecksdb\n\n# Backup folder for Package Reference Convert tool in Visual Studio 2017\nMigrationBackup/"
  },
  {
    "path": "FFTriadBuddy.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio 15\nVisualStudioVersion = 15.0.27703.2026\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"FFTriadBuddy\", \"sources\\FFTriadBuddy.csproj\", \"{4FE48C06-9E9C-46C9-A53E-CFE06EFDCBDC}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{4FE48C06-9E9C-46C9-A53E-CFE06EFDCBDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{4FE48C06-9E9C-46C9-A53E-CFE06EFDCBDC}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{4FE48C06-9E9C-46C9-A53E-CFE06EFDCBDC}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{4FE48C06-9E9C-46C9-A53E-CFE06EFDCBDC}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {4BB10FD8-07CE-4000-A373-6D18CE17C244}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019 MgAl2O4\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# FFTriadBuddy\r\nHelper program for Triple Triad minigame in [Final Fantasy XIV](https://www.finalfantasyxiv.com/), NPC matches only.  \r\n(All used icons are property of SQUARE-ENIX Ltd. All rights reserved)\r\n\r\nFeatures:\r\n\r\n* suggest next move in standalone simulation\r\n* suggest next move with in-game overlay (screenshot based)\r\n* organize cards and npc fights\r\n* prepare deck for given npc\r\n* all game rules are supported\r\n* **super secret** Mini Cactpot mode for in-game overlay\r\n\r\nDalamud plugin can be found here: https://github.com/MgAl2O4/FFTriadBuddyDalamud\r\n\r\n\r\n## Instructions\r\n1. Download [latest release](https://github.com/MgAl2O4/FFTriadBuddy/releases/latest)\r\n2. Unpack and run. \r\n\r\nProgram will attempt to auto update on startup from this repository.\r\n\r\n## Known issues\r\n\r\nUI buttons glitching out after mouse over:  \r\nPlease refer to [this bug report](https://github.com/MgAl2O4/FFTriadBuddy/issues/53#issuecomment-879286853) for solution, it's a known incompatibility between UI framework and Nahimic software.\r\n\r\n\r\n## Bug reports\r\n\r\nBug happens. Whenever it's related to Play: Screenshot mode not recognizing images/cards correctly, please include a screenshot in bug report. Ideally, the one saved by tool in secret-debug-mode, which is exact input image for processing.\r\n* make sure that game show broken state and board is not obscured\r\n* switch to Play: Simulate tab, press [F12] and hit \"Apply rule\" button\r\n* look for screenshot-source-xx.jpg file, saved next to tool's executable\r\n\r\n## Translation\r\n\r\nLocalization of game data (cards, npcs, rules, tournaments, etc) is created from client datamining and is more or less automatic. Tool's UI is now separate from it, but relies on people to contribute translations.\r\n\r\nYou can help with translation here: https://crowdin.com/project/fftriadbuddy\r\n\r\n\r\nContact: MgAl2O4@protonmail.com\r\n"
  },
  {
    "path": "assets/data/cards.xml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <card id=\"1\" rarity=\"0\" type=\"0\" up=\"4\" lt=\"2\" dn=\"3\" rt=\"4\" sort=\"1\" group=\"0\" />\n  <card id=\"2\" rarity=\"0\" type=\"0\" up=\"2\" lt=\"2\" dn=\"7\" rt=\"2\" sort=\"2\" group=\"0\" />\n  <card id=\"3\" rarity=\"0\" type=\"0\" up=\"4\" lt=\"3\" dn=\"3\" rt=\"3\" sort=\"3\" group=\"0\" />\n  <card id=\"4\" rarity=\"0\" type=\"0\" up=\"2\" lt=\"3\" dn=\"4\" rt=\"4\" sort=\"4\" group=\"0\" />\n  <card id=\"5\" rarity=\"0\" type=\"0\" up=\"2\" lt=\"4\" dn=\"3\" rt=\"5\" sort=\"5\" group=\"0\" />\n  <card id=\"6\" rarity=\"0\" type=\"0\" up=\"3\" lt=\"4\" dn=\"3\" rt=\"3\" sort=\"6\" group=\"0\" />\n  <card id=\"7\" rarity=\"0\" type=\"0\" up=\"4\" lt=\"2\" dn=\"5\" rt=\"3\" sort=\"7\" group=\"0\" />\n  <card id=\"8\" rarity=\"0\" type=\"0\" up=\"3\" lt=\"3\" dn=\"3\" rt=\"4\" sort=\"8\" group=\"0\" />\n  <card id=\"9\" rarity=\"0\" type=\"0\" up=\"5\" lt=\"2\" dn=\"5\" rt=\"2\" sort=\"9\" group=\"0\" />\n  <card id=\"10\" rarity=\"0\" type=\"0\" up=\"2\" lt=\"5\" dn=\"2\" rt=\"5\" sort=\"10\" group=\"0\" />\n  <card id=\"11\" rarity=\"0\" type=\"0\" up=\"5\" lt=\"5\" dn=\"2\" rt=\"2\" sort=\"11\" group=\"0\" />\n  <card id=\"12\" rarity=\"0\" type=\"0\" up=\"2\" lt=\"5\" dn=\"5\" rt=\"2\" sort=\"12\" group=\"0\" />\n  <card id=\"13\" rarity=\"0\" type=\"0\" up=\"3\" lt=\"7\" dn=\"2\" rt=\"1\" sort=\"13\" group=\"0\" />\n  <card id=\"14\" rarity=\"0\" type=\"1\" up=\"1\" lt=\"4\" dn=\"7\" rt=\"1\" sort=\"14\" group=\"0\" />\n  <card id=\"15\" rarity=\"0\" type=\"1\" up=\"6\" lt=\"1\" dn=\"3\" rt=\"4\" sort=\"15\" group=\"0\" />\n  <card id=\"16\" rarity=\"0\" type=\"1\" up=\"2\" lt=\"4\" dn=\"5\" rt=\"4\" sort=\"16\" group=\"0\" />\n  <card id=\"17\" rarity=\"0\" type=\"1\" up=\"2\" lt=\"2\" dn=\"4\" rt=\"6\" sort=\"17\" group=\"0\" />\n  <card id=\"18\" rarity=\"0\" type=\"1\" up=\"4\" lt=\"5\" dn=\"3\" rt=\"3\" sort=\"18\" group=\"0\" />\n  <card id=\"19\" rarity=\"0\" type=\"3\" up=\"7\" lt=\"2\" dn=\"3\" rt=\"2\" sort=\"19\" group=\"0\" />\n  <card id=\"20\" rarity=\"0\" type=\"1\" up=\"2\" lt=\"1\" dn=\"3\" rt=\"7\" sort=\"20\" group=\"0\" />\n  <card id=\"21\" rarity=\"1\" type=\"0\" up=\"3\" lt=\"6\" dn=\"7\" rt=\"2\" sort=\"21\" group=\"0\" />\n  <card id=\"22\" rarity=\"1\" type=\"0\" up=\"2\" lt=\"3\" dn=\"6\" rt=\"7\" sort=\"22\" group=\"0\" />\n  <card id=\"23\" rarity=\"1\" type=\"0\" up=\"6\" lt=\"7\" dn=\"2\" rt=\"3\" sort=\"23\" group=\"0\" />\n  <card id=\"24\" rarity=\"1\" type=\"0\" up=\"7\" lt=\"3\" dn=\"2\" rt=\"6\" sort=\"24\" group=\"0\" />\n  <card id=\"25\" rarity=\"1\" type=\"0\" up=\"7\" lt=\"7\" dn=\"2\" rt=\"2\" sort=\"25\" group=\"0\" />\n  <card id=\"26\" rarity=\"1\" type=\"0\" up=\"2\" lt=\"2\" dn=\"7\" rt=\"7\" sort=\"26\" group=\"0\" />\n  <card id=\"27\" rarity=\"1\" type=\"1\" up=\"6\" lt=\"6\" dn=\"2\" rt=\"6\" sort=\"27\" group=\"0\" />\n  <card id=\"28\" rarity=\"1\" type=\"0\" up=\"7\" lt=\"5\" dn=\"5\" rt=\"3\" sort=\"28\" group=\"0\" />\n  <card id=\"29\" rarity=\"1\" type=\"0\" up=\"3\" lt=\"7\" dn=\"5\" rt=\"5\" sort=\"29\" group=\"0\" />\n  <card id=\"30\" rarity=\"1\" type=\"0\" up=\"5\" lt=\"5\" dn=\"3\" rt=\"7\" sort=\"30\" group=\"0\" />\n  <card id=\"31\" rarity=\"1\" type=\"4\" up=\"3\" lt=\"7\" dn=\"7\" rt=\"1\" sort=\"31\" group=\"0\" />\n  <card id=\"32\" rarity=\"1\" type=\"4\" up=\"7\" lt=\"1\" dn=\"3\" rt=\"7\" sort=\"32\" group=\"0\" />\n  <card id=\"33\" rarity=\"1\" type=\"0\" up=\"5\" lt=\"3\" dn=\"7\" rt=\"5\" sort=\"33\" group=\"0\" />\n  <card id=\"34\" rarity=\"1\" type=\"0\" up=\"1\" lt=\"7\" dn=\"3\" rt=\"7\" sort=\"34\" group=\"0\" />\n  <card id=\"35\" rarity=\"1\" type=\"1\" up=\"6\" lt=\"2\" dn=\"6\" rt=\"6\" sort=\"35\" group=\"0\" />\n  <card id=\"36\" rarity=\"1\" type=\"1\" up=\"2\" lt=\"6\" dn=\"6\" rt=\"6\" sort=\"36\" group=\"0\" />\n  <card id=\"37\" rarity=\"1\" type=\"1\" up=\"6\" lt=\"6\" dn=\"6\" rt=\"2\" sort=\"37\" group=\"0\" />\n  <card id=\"38\" rarity=\"2\" type=\"0\" up=\"7\" lt=\"8\" dn=\"4\" rt=\"2\" sort=\"38\" group=\"0\" />\n  <card id=\"39\" rarity=\"2\" type=\"0\" up=\"8\" lt=\"3\" dn=\"7\" rt=\"3\" sort=\"39\" group=\"0\" />\n  <card id=\"40\" rarity=\"2\" type=\"2\" up=\"7\" lt=\"1\" dn=\"6\" rt=\"7\" sort=\"40\" group=\"0\" />\n  <card id=\"41\" rarity=\"2\" type=\"2\" up=\"1\" lt=\"7\" dn=\"7\" rt=\"6\" sort=\"41\" group=\"0\" />\n  <card id=\"42\" rarity=\"2\" type=\"2\" up=\"7\" lt=\"6\" dn=\"1\" rt=\"7\" sort=\"42\" group=\"0\" />\n  <card id=\"43\" rarity=\"2\" type=\"2\" up=\"7\" lt=\"6\" dn=\"7\" rt=\"1\" sort=\"43\" group=\"0\" />\n  <card id=\"44\" rarity=\"2\" type=\"0\" up=\"5\" lt=\"6\" dn=\"6\" rt=\"6\" sort=\"44\" group=\"0\" />\n  <card id=\"45\" rarity=\"2\" type=\"0\" up=\"6\" lt=\"6\" dn=\"5\" rt=\"6\" sort=\"45\" group=\"0\" />\n  <card id=\"46\" rarity=\"2\" type=\"3\" up=\"2\" lt=\"3\" dn=\"8\" rt=\"7\" sort=\"46\" group=\"0\" />\n  <card id=\"47\" rarity=\"2\" type=\"4\" up=\"4\" lt=\"1\" dn=\"8\" rt=\"7\" sort=\"47\" group=\"0\" />\n  <card id=\"48\" rarity=\"2\" type=\"3\" up=\"3\" lt=\"7\" dn=\"8\" rt=\"2\" sort=\"48\" group=\"0\" />\n  <card id=\"49\" rarity=\"2\" type=\"3\" up=\"7\" lt=\"8\" dn=\"1\" rt=\"4\" sort=\"49\" group=\"0\" />\n  <card id=\"50\" rarity=\"2\" type=\"3\" up=\"8\" lt=\"1\" dn=\"4\" rt=\"7\" sort=\"50\" group=\"0\" />\n  <card id=\"51\" rarity=\"3\" type=\"4\" up=\"7\" lt=\"8\" dn=\"9\" rt=\"1\" sort=\"51\" group=\"0\" />\n  <card id=\"52\" rarity=\"3\" type=\"2\" up=\"8\" lt=\"8\" dn=\"1\" rt=\"8\" sort=\"52\" group=\"0\" />\n  <card id=\"53\" rarity=\"3\" type=\"2\" up=\"8\" lt=\"1\" dn=\"8\" rt=\"8\" sort=\"53\" group=\"0\" />\n  <card id=\"54\" rarity=\"3\" type=\"2\" up=\"8\" lt=\"8\" dn=\"8\" rt=\"1\" sort=\"54\" group=\"0\" />\n  <card id=\"55\" rarity=\"3\" type=\"2\" up=\"1\" lt=\"8\" dn=\"8\" rt=\"8\" sort=\"55\" group=\"0\" />\n  <card id=\"56\" rarity=\"3\" type=\"3\" up=\"9\" lt=\"8\" dn=\"3\" rt=\"5\" sort=\"56\" group=\"0\" />\n  <card id=\"57\" rarity=\"3\" type=\"0\" up=\"4\" lt=\"9\" dn=\"4\" rt=\"8\" sort=\"57\" group=\"0\" />\n  <card id=\"58\" rarity=\"3\" type=\"0\" up=\"5\" lt=\"9\" dn=\"9\" rt=\"2\" sort=\"58\" group=\"0\" />\n  <card id=\"59\" rarity=\"3\" type=\"3\" up=\"9\" lt=\"3\" dn=\"3\" rt=\"9\" sort=\"59\" group=\"0\" />\n  <card id=\"60\" rarity=\"3\" type=\"3\" up=\"9\" lt=\"4\" dn=\"9\" rt=\"3\" sort=\"60\" group=\"0\" />\n  <card id=\"61\" rarity=\"4\" type=\"2\" up=\"9\" lt=\"5\" dn=\"9\" rt=\"6\" sort=\"61\" group=\"0\" />\n  <card id=\"62\" rarity=\"4\" type=\"0\" up=\"1\" lt=\"8\" dn=\"10\" rt=\"8\" sort=\"62\" group=\"0\" />\n  <card id=\"63\" rarity=\"4\" type=\"0\" up=\"10\" lt=\"6\" dn=\"4\" rt=\"8\" sort=\"63\" group=\"0\" />\n  <card id=\"64\" rarity=\"4\" type=\"4\" up=\"4\" lt=\"10\" dn=\"5\" rt=\"9\" sort=\"64\" group=\"0\" />\n  <card id=\"65\" rarity=\"4\" type=\"0\" up=\"5\" lt=\"9\" dn=\"10\" rt=\"3\" sort=\"65\" group=\"0\" />\n  <card id=\"66\" rarity=\"4\" type=\"0\" up=\"9\" lt=\"10\" dn=\"1\" rt=\"7\" sort=\"66\" group=\"0\" />\n  <card id=\"67\" rarity=\"4\" type=\"0\" up=\"6\" lt=\"2\" dn=\"9\" rt=\"10\" sort=\"67\" group=\"0\" />\n  <card id=\"68\" rarity=\"4\" type=\"0\" up=\"10\" lt=\"2\" dn=\"5\" rt=\"10\" sort=\"1001\" group=\"5\" />\n  <card id=\"69\" rarity=\"4\" type=\"0\" up=\"10\" lt=\"5\" dn=\"10\" rt=\"1\" sort=\"1002\" group=\"5\" />\n  <card id=\"70\" rarity=\"4\" type=\"0\" up=\"8\" lt=\"2\" dn=\"8\" rt=\"10\" sort=\"1003\" group=\"5\" />\n  <card id=\"71\" rarity=\"4\" type=\"0\" up=\"4\" lt=\"10\" dn=\"4\" rt=\"10\" sort=\"1004\" group=\"5\" />\n  <card id=\"72\" rarity=\"4\" type=\"0\" up=\"4\" lt=\"4\" dn=\"10\" rt=\"10\" sort=\"1005\" group=\"5\" />\n  <card id=\"73\" rarity=\"4\" type=\"0\" up=\"10\" lt=\"10\" dn=\"2\" rt=\"5\" sort=\"1006\" group=\"5\" />\n  <card id=\"74\" rarity=\"4\" type=\"0\" up=\"9\" lt=\"3\" dn=\"9\" rt=\"8\" sort=\"1007\" group=\"5\" />\n  <card id=\"75\" rarity=\"4\" type=\"0\" up=\"6\" lt=\"10\" dn=\"10\" rt=\"1\" sort=\"1008\" group=\"5\" />\n  <card id=\"76\" rarity=\"4\" type=\"0\" up=\"5\" lt=\"10\" dn=\"6\" rt=\"8\" sort=\"1009\" group=\"5\" />\n  <card id=\"77\" rarity=\"4\" type=\"0\" up=\"10\" lt=\"7\" dn=\"1\" rt=\"9\" sort=\"1010\" group=\"5\" />\n  <card id=\"78\" rarity=\"4\" type=\"0\" up=\"4\" lt=\"9\" dn=\"7\" rt=\"9\" sort=\"1011\" group=\"5\" />\n  <card id=\"79\" rarity=\"4\" type=\"0\" up=\"1\" lt=\"7\" dn=\"10\" rt=\"9\" sort=\"1012\" group=\"5\" />\n  <card id=\"80\" rarity=\"4\" type=\"0\" up=\"9\" lt=\"1\" dn=\"7\" rt=\"10\" sort=\"1013\" group=\"5\" />\n  <card id=\"81\" rarity=\"0\" type=\"0\" up=\"4\" lt=\"1\" dn=\"1\" rt=\"7\" sort=\"68\" group=\"0\" />\n  <card id=\"82\" rarity=\"1\" type=\"1\" up=\"2\" lt=\"6\" dn=\"4\" rt=\"7\" sort=\"69\" group=\"0\" />\n  <card id=\"83\" rarity=\"1\" type=\"1\" up=\"6\" lt=\"3\" dn=\"7\" rt=\"3\" sort=\"70\" group=\"0\" />\n  <card id=\"84\" rarity=\"1\" type=\"0\" up=\"7\" lt=\"7\" dn=\"1\" rt=\"3\" sort=\"71\" group=\"0\" />\n  <card id=\"85\" rarity=\"1\" type=\"0\" up=\"5\" lt=\"5\" dn=\"5\" rt=\"5\" sort=\"72\" group=\"0\" />\n  <card id=\"86\" rarity=\"2\" type=\"0\" up=\"5\" lt=\"1\" dn=\"7\" rt=\"8\" sort=\"73\" group=\"0\" />\n  <card id=\"87\" rarity=\"2\" type=\"0\" up=\"1\" lt=\"5\" dn=\"8\" rt=\"7\" sort=\"74\" group=\"0\" />\n  <card id=\"88\" rarity=\"2\" type=\"0\" up=\"8\" lt=\"8\" dn=\"2\" rt=\"3\" sort=\"75\" group=\"0\" />\n  <card id=\"89\" rarity=\"2\" type=\"0\" up=\"8\" lt=\"2\" dn=\"3\" rt=\"8\" sort=\"76\" group=\"0\" />\n  <card id=\"90\" rarity=\"2\" type=\"0\" up=\"4\" lt=\"8\" dn=\"8\" rt=\"1\" sort=\"77\" group=\"0\" />\n  <card id=\"91\" rarity=\"2\" type=\"0\" up=\"1\" lt=\"4\" dn=\"8\" rt=\"8\" sort=\"78\" group=\"0\" />\n  <card id=\"92\" rarity=\"2\" type=\"0\" up=\"8\" lt=\"3\" dn=\"8\" rt=\"1\" sort=\"79\" group=\"0\" />\n  <card id=\"93\" rarity=\"2\" type=\"0\" up=\"1\" lt=\"8\" dn=\"3\" rt=\"8\" sort=\"80\" group=\"0\" />\n  <card id=\"94\" rarity=\"2\" type=\"0\" up=\"8\" lt=\"4\" dn=\"1\" rt=\"7\" sort=\"81\" group=\"0\" />\n  <card id=\"95\" rarity=\"2\" type=\"0\" up=\"7\" lt=\"1\" dn=\"5\" rt=\"8\" sort=\"82\" group=\"0\" />\n  <card id=\"96\" rarity=\"3\" type=\"0\" up=\"1\" lt=\"5\" dn=\"9\" rt=\"9\" sort=\"83\" group=\"0\" />\n  <card id=\"97\" rarity=\"3\" type=\"2\" up=\"9\" lt=\"7\" dn=\"8\" rt=\"1\" sort=\"84\" group=\"0\" />\n  <card id=\"98\" rarity=\"3\" type=\"2\" up=\"1\" lt=\"9\" dn=\"7\" rt=\"8\" sort=\"85\" group=\"0\" />\n  <card id=\"99\" rarity=\"4\" type=\"0\" up=\"10\" lt=\"7\" dn=\"3\" rt=\"8\" sort=\"86\" group=\"0\" />\n  <card id=\"100\" rarity=\"4\" type=\"0\" up=\"3\" lt=\"8\" dn=\"10\" rt=\"7\" sort=\"87\" group=\"0\" />\n  <card id=\"101\" rarity=\"0\" type=\"0\" up=\"1\" lt=\"3\" dn=\"7\" rt=\"2\" sort=\"88\" group=\"0\" />\n  <card id=\"102\" rarity=\"1\" type=\"0\" up=\"5\" lt=\"4\" dn=\"6\" rt=\"5\" sort=\"89\" group=\"0\" />\n  <card id=\"103\" rarity=\"1\" type=\"0\" up=\"4\" lt=\"6\" dn=\"3\" rt=\"6\" sort=\"90\" group=\"0\" />\n  <card id=\"104\" rarity=\"1\" type=\"0\" up=\"6\" lt=\"5\" dn=\"3\" rt=\"5\" sort=\"91\" group=\"0\" />\n  <card id=\"105\" rarity=\"1\" type=\"0\" up=\"6\" lt=\"6\" dn=\"3\" rt=\"4\" sort=\"92\" group=\"0\" />\n  <card id=\"106\" rarity=\"1\" type=\"0\" up=\"7\" lt=\"7\" dn=\"3\" rt=\"2\" sort=\"93\" group=\"0\" />\n  <card id=\"107\" rarity=\"1\" type=\"0\" up=\"6\" lt=\"3\" dn=\"7\" rt=\"3\" sort=\"94\" group=\"0\" />\n  <card id=\"108\" rarity=\"2\" type=\"0\" up=\"8\" lt=\"2\" dn=\"7\" rt=\"4\" sort=\"95\" group=\"0\" />\n  <card id=\"109\" rarity=\"2\" type=\"0\" up=\"5\" lt=\"5\" dn=\"5\" rt=\"8\" sort=\"96\" group=\"0\" />\n  <card id=\"110\" rarity=\"2\" type=\"0\" up=\"6\" lt=\"6\" dn=\"3\" rt=\"6\" sort=\"97\" group=\"0\" />\n  <card id=\"111\" rarity=\"2\" type=\"0\" up=\"3\" lt=\"7\" dn=\"3\" rt=\"7\" sort=\"98\" group=\"0\" />\n  <card id=\"112\" rarity=\"2\" type=\"0\" up=\"6\" lt=\"4\" dn=\"7\" rt=\"4\" sort=\"99\" group=\"0\" />\n  <card id=\"113\" rarity=\"2\" type=\"0\" up=\"6\" lt=\"5\" dn=\"6\" rt=\"6\" sort=\"100\" group=\"0\" />\n  <card id=\"114\" rarity=\"2\" type=\"0\" up=\"6\" lt=\"6\" dn=\"5\" rt=\"7\" sort=\"101\" group=\"0\" />\n  <card id=\"115\" rarity=\"2\" type=\"1\" up=\"8\" lt=\"5\" dn=\"4\" rt=\"5\" sort=\"102\" group=\"0\" />\n  <card id=\"116\" rarity=\"2\" type=\"0\" up=\"7\" lt=\"8\" dn=\"4\" rt=\"3\" sort=\"103\" group=\"0\" />\n  <card id=\"117\" rarity=\"2\" type=\"1\" up=\"2\" lt=\"2\" dn=\"8\" rt=\"8\" sort=\"104\" group=\"0\" />\n  <card id=\"118\" rarity=\"3\" type=\"0\" up=\"9\" lt=\"5\" dn=\"7\" rt=\"6\" sort=\"105\" group=\"0\" />\n  <card id=\"119\" rarity=\"4\" type=\"4\" up=\"8\" lt=\"8\" dn=\"6\" rt=\"8\" sort=\"106\" group=\"0\" />\n  <card id=\"120\" rarity=\"4\" type=\"0\" up=\"7\" lt=\"1\" dn=\"10\" rt=\"9\" sort=\"107\" group=\"0\" />\n  <card id=\"121\" rarity=\"1\" type=\"0\" up=\"7\" lt=\"2\" dn=\"7\" rt=\"2\" sort=\"108\" group=\"0\" />\n  <card id=\"122\" rarity=\"1\" type=\"3\" up=\"2\" lt=\"7\" dn=\"2\" rt=\"7\" sort=\"109\" group=\"0\" />\n  <card id=\"123\" rarity=\"2\" type=\"0\" up=\"8\" lt=\"4\" dn=\"5\" rt=\"4\" sort=\"110\" group=\"0\" />\n  <card id=\"124\" rarity=\"2\" type=\"0\" up=\"2\" lt=\"8\" dn=\"4\" rt=\"7\" sort=\"111\" group=\"0\" />\n  <card id=\"125\" rarity=\"2\" type=\"0\" up=\"7\" lt=\"3\" dn=\"6\" rt=\"4\" sort=\"112\" group=\"0\" />\n  <card id=\"126\" rarity=\"2\" type=\"0\" up=\"3\" lt=\"6\" dn=\"6\" rt=\"6\" sort=\"113\" group=\"0\" />\n  <card id=\"127\" rarity=\"2\" type=\"0\" up=\"7\" lt=\"1\" dn=\"7\" rt=\"5\" sort=\"114\" group=\"0\" />\n  <card id=\"128\" rarity=\"2\" type=\"1\" up=\"3\" lt=\"7\" dn=\"2\" rt=\"8\" sort=\"115\" group=\"0\" />\n  <card id=\"129\" rarity=\"2\" type=\"0\" up=\"4\" lt=\"6\" dn=\"8\" rt=\"4\" sort=\"116\" group=\"0\" />\n  <card id=\"130\" rarity=\"2\" type=\"0\" up=\"6\" lt=\"6\" dn=\"6\" rt=\"3\" sort=\"117\" group=\"0\" />\n  <card id=\"131\" rarity=\"2\" type=\"0\" up=\"8\" lt=\"3\" dn=\"2\" rt=\"7\" sort=\"118\" group=\"0\" />\n  <card id=\"132\" rarity=\"2\" type=\"0\" up=\"1\" lt=\"7\" dn=\"6\" rt=\"7\" sort=\"119\" group=\"0\" />\n  <card id=\"133\" rarity=\"2\" type=\"0\" up=\"4\" lt=\"7\" dn=\"3\" rt=\"7\" sort=\"120\" group=\"0\" />\n  <card id=\"134\" rarity=\"2\" type=\"0\" up=\"7\" lt=\"4\" dn=\"7\" rt=\"3\" sort=\"121\" group=\"0\" />\n  <card id=\"135\" rarity=\"3\" type=\"0\" up=\"9\" lt=\"4\" dn=\"6\" rt=\"7\" sort=\"122\" group=\"0\" />\n  <card id=\"136\" rarity=\"3\" type=\"0\" up=\"7\" lt=\"7\" dn=\"7\" rt=\"7\" sort=\"123\" group=\"0\" />\n  <card id=\"137\" rarity=\"3\" type=\"2\" up=\"6\" lt=\"8\" dn=\"8\" rt=\"6\" sort=\"124\" group=\"0\" />\n  <card id=\"138\" rarity=\"3\" type=\"3\" up=\"9\" lt=\"7\" dn=\"3\" rt=\"7\" sort=\"125\" group=\"0\" />\n  <card id=\"139\" rarity=\"3\" type=\"0\" up=\"8\" lt=\"6\" dn=\"8\" rt=\"6\" sort=\"126\" group=\"0\" />\n  <card id=\"140\" rarity=\"4\" type=\"0\" up=\"7\" lt=\"10\" dn=\"7\" rt=\"6\" sort=\"127\" group=\"0\" />\n  <card id=\"141\" rarity=\"1\" type=\"0\" up=\"4\" lt=\"3\" dn=\"7\" rt=\"3\" sort=\"128\" group=\"0\" />\n  <card id=\"142\" rarity=\"1\" type=\"0\" up=\"2\" lt=\"6\" dn=\"3\" rt=\"6\" sort=\"129\" group=\"0\" />\n  <card id=\"143\" rarity=\"1\" type=\"0\" up=\"7\" lt=\"6\" dn=\"3\" rt=\"3\" sort=\"130\" group=\"0\" />\n  <card id=\"144\" rarity=\"1\" type=\"0\" up=\"5\" lt=\"6\" dn=\"4\" rt=\"6\" sort=\"131\" group=\"0\" />\n  <card id=\"145\" rarity=\"1\" type=\"0\" up=\"2\" lt=\"7\" dn=\"7\" rt=\"2\" sort=\"132\" group=\"0\" />\n  <card id=\"146\" rarity=\"2\" type=\"0\" up=\"1\" lt=\"8\" dn=\"4\" rt=\"8\" sort=\"133\" group=\"0\" />\n  <card id=\"147\" rarity=\"2\" type=\"0\" up=\"1\" lt=\"6\" dn=\"7\" rt=\"7\" sort=\"134\" group=\"0\" />\n  <card id=\"148\" rarity=\"2\" type=\"0\" up=\"4\" lt=\"6\" dn=\"4\" rt=\"7\" sort=\"135\" group=\"0\" />\n  <card id=\"149\" rarity=\"2\" type=\"0\" up=\"5\" lt=\"6\" dn=\"7\" rt=\"6\" sort=\"136\" group=\"0\" />\n  <card id=\"150\" rarity=\"3\" type=\"0\" up=\"6\" lt=\"8\" dn=\"6\" rt=\"8\" sort=\"137\" group=\"0\" />\n  <card id=\"151\" rarity=\"3\" type=\"0\" up=\"9\" lt=\"6\" dn=\"5\" rt=\"6\" sort=\"138\" group=\"0\" />\n  <card id=\"152\" rarity=\"3\" type=\"0\" up=\"5\" lt=\"8\" dn=\"7\" rt=\"8\" sort=\"139\" group=\"0\" />\n  <card id=\"153\" rarity=\"4\" type=\"0\" up=\"7\" lt=\"7\" dn=\"10\" rt=\"6\" sort=\"140\" group=\"0\" />\n  <card id=\"154\" rarity=\"0\" type=\"0\" up=\"3\" lt=\"4\" dn=\"4\" rt=\"1\" sort=\"141\" group=\"0\" />\n  <card id=\"155\" rarity=\"0\" type=\"0\" up=\"6\" lt=\"1\" dn=\"4\" rt=\"1\" sort=\"142\" group=\"0\" />\n  <card id=\"156\" rarity=\"0\" type=\"4\" up=\"4\" lt=\"3\" dn=\"2\" rt=\"3\" sort=\"143\" group=\"0\" />\n  <card id=\"157\" rarity=\"1\" type=\"0\" up=\"4\" lt=\"3\" dn=\"6\" rt=\"6\" sort=\"144\" group=\"0\" />\n  <card id=\"158\" rarity=\"1\" type=\"0\" up=\"7\" lt=\"3\" dn=\"4\" rt=\"5\" sort=\"145\" group=\"0\" />\n  <card id=\"159\" rarity=\"1\" type=\"1\" up=\"5\" lt=\"5\" dn=\"6\" rt=\"3\" sort=\"146\" group=\"0\" />\n  <card id=\"160\" rarity=\"1\" type=\"4\" up=\"6\" lt=\"3\" dn=\"6\" rt=\"3\" sort=\"147\" group=\"0\" />\n  <card id=\"161\" rarity=\"2\" type=\"0\" up=\"5\" lt=\"7\" dn=\"1\" rt=\"7\" sort=\"148\" group=\"0\" />\n  <card id=\"162\" rarity=\"2\" type=\"1\" up=\"7\" lt=\"6\" dn=\"6\" rt=\"2\" sort=\"149\" group=\"0\" />\n  <card id=\"163\" rarity=\"2\" type=\"2\" up=\"7\" lt=\"3\" dn=\"2\" rt=\"8\" sort=\"150\" group=\"0\" />\n  <card id=\"164\" rarity=\"2\" type=\"0\" up=\"4\" lt=\"7\" dn=\"6\" rt=\"5\" sort=\"151\" group=\"0\" />\n  <card id=\"165\" rarity=\"3\" type=\"3\" up=\"8\" lt=\"1\" dn=\"7\" rt=\"9\" sort=\"152\" group=\"0\" />\n  <card id=\"166\" rarity=\"3\" type=\"3\" up=\"9\" lt=\"9\" dn=\"2\" rt=\"5\" sort=\"153\" group=\"0\" />\n  <card id=\"167\" rarity=\"3\" type=\"4\" up=\"3\" lt=\"9\" dn=\"3\" rt=\"9\" sort=\"154\" group=\"0\" />\n  <card id=\"168\" rarity=\"4\" type=\"2\" up=\"10\" lt=\"8\" dn=\"5\" rt=\"6\" sort=\"155\" group=\"0\" />\n  <card id=\"169\" rarity=\"0\" type=\"0\" up=\"1\" lt=\"4\" dn=\"2\" rt=\"6\" sort=\"156\" group=\"0\" />\n  <card id=\"170\" rarity=\"1\" type=\"0\" up=\"5\" lt=\"7\" dn=\"4\" rt=\"4\" sort=\"157\" group=\"0\" />\n  <card id=\"171\" rarity=\"1\" type=\"4\" up=\"3\" lt=\"5\" dn=\"4\" rt=\"7\" sort=\"158\" group=\"0\" />\n  <card id=\"172\" rarity=\"1\" type=\"4\" up=\"3\" lt=\"5\" dn=\"5\" rt=\"5\" sort=\"159\" group=\"0\" />\n  <card id=\"173\" rarity=\"1\" type=\"0\" up=\"4\" lt=\"7\" dn=\"1\" rt=\"6\" sort=\"167\" group=\"0\" />\n  <card id=\"174\" rarity=\"2\" type=\"0\" up=\"3\" lt=\"8\" dn=\"5\" rt=\"5\" sort=\"160\" group=\"0\" />\n  <card id=\"175\" rarity=\"2\" type=\"4\" up=\"5\" lt=\"7\" dn=\"4\" rt=\"5\" sort=\"161\" group=\"0\" />\n  <card id=\"176\" rarity=\"2\" type=\"4\" up=\"6\" lt=\"2\" dn=\"6\" rt=\"7\" sort=\"162\" group=\"0\" />\n  <card id=\"177\" rarity=\"2\" type=\"0\" up=\"8\" lt=\"8\" dn=\"4\" rt=\"1\" sort=\"163\" group=\"0\" />\n  <card id=\"178\" rarity=\"2\" type=\"0\" up=\"2\" lt=\"7\" dn=\"8\" rt=\"3\" sort=\"168\" group=\"0\" />\n  <card id=\"179\" rarity=\"3\" type=\"0\" up=\"4\" lt=\"4\" dn=\"8\" rt=\"9\" sort=\"164\" group=\"0\" />\n  <card id=\"180\" rarity=\"3\" type=\"4\" up=\"3\" lt=\"5\" dn=\"9\" rt=\"7\" sort=\"165\" group=\"0\" />\n  <card id=\"181\" rarity=\"3\" type=\"0\" up=\"6\" lt=\"8\" dn=\"4\" rt=\"7\" sort=\"169\" group=\"0\" />\n  <card id=\"182\" rarity=\"4\" type=\"2\" up=\"3\" lt=\"7\" dn=\"8\" rt=\"10\" sort=\"166\" group=\"0\" />\n  <card id=\"183\" rarity=\"0\" type=\"1\" up=\"1\" lt=\"6\" dn=\"1\" rt=\"5\" sort=\"170\" group=\"0\" />\n  <card id=\"184\" rarity=\"1\" type=\"1\" up=\"4\" lt=\"5\" dn=\"5\" rt=\"4\" sort=\"171\" group=\"0\" />\n  <card id=\"185\" rarity=\"1\" type=\"1\" up=\"3\" lt=\"7\" dn=\"5\" rt=\"2\" sort=\"172\" group=\"0\" />\n  <card id=\"186\" rarity=\"1\" type=\"0\" up=\"4\" lt=\"1\" dn=\"4\" rt=\"7\" sort=\"173\" group=\"0\" />\n  <card id=\"187\" rarity=\"1\" type=\"0\" up=\"5\" lt=\"6\" dn=\"2\" rt=\"6\" sort=\"174\" group=\"0\" />\n  <card id=\"188\" rarity=\"2\" type=\"0\" up=\"6\" lt=\"3\" dn=\"8\" rt=\"3\" sort=\"175\" group=\"0\" />\n  <card id=\"189\" rarity=\"2\" type=\"0\" up=\"8\" lt=\"8\" dn=\"3\" rt=\"1\" sort=\"176\" group=\"0\" />\n  <card id=\"190\" rarity=\"2\" type=\"2\" up=\"2\" lt=\"8\" dn=\"3\" rt=\"8\" sort=\"177\" group=\"0\" />\n  <card id=\"191\" rarity=\"2\" type=\"2\" up=\"3\" lt=\"7\" dn=\"7\" rt=\"5\" sort=\"178\" group=\"0\" />\n  <card id=\"192\" rarity=\"2\" type=\"4\" up=\"7\" lt=\"4\" dn=\"7\" rt=\"4\" sort=\"179\" group=\"0\" />\n  <card id=\"193\" rarity=\"2\" type=\"0\" up=\"4\" lt=\"7\" dn=\"8\" rt=\"2\" sort=\"180\" group=\"0\" />\n  <card id=\"194\" rarity=\"2\" type=\"0\" up=\"3\" lt=\"5\" dn=\"6\" rt=\"8\" sort=\"181\" group=\"0\" />\n  <card id=\"195\" rarity=\"2\" type=\"0\" up=\"6\" lt=\"7\" dn=\"4\" rt=\"4\" sort=\"182\" group=\"0\" />\n  <card id=\"196\" rarity=\"2\" type=\"0\" up=\"6\" lt=\"6\" dn=\"7\" rt=\"4\" sort=\"183\" group=\"0\" />\n  <card id=\"197\" rarity=\"3\" type=\"2\" up=\"7\" lt=\"8\" dn=\"8\" rt=\"2\" sort=\"184\" group=\"0\" />\n  <card id=\"198\" rarity=\"3\" type=\"4\" up=\"9\" lt=\"2\" dn=\"8\" rt=\"6\" sort=\"185\" group=\"0\" />\n  <card id=\"199\" rarity=\"3\" type=\"3\" up=\"2\" lt=\"8\" dn=\"8\" rt=\"7\" sort=\"186\" group=\"0\" />\n  <card id=\"200\" rarity=\"3\" type=\"3\" up=\"6\" lt=\"9\" dn=\"1\" rt=\"8\" sort=\"187\" group=\"0\" />\n  <card id=\"201\" rarity=\"4\" type=\"4\" up=\"6\" lt=\"6\" dn=\"7\" rt=\"10\" sort=\"188\" group=\"0\" />\n  <card id=\"202\" rarity=\"4\" type=\"0\" up=\"2\" lt=\"10\" dn=\"5\" rt=\"10\" sort=\"189\" group=\"0\" />\n  <card id=\"203\" rarity=\"0\" type=\"0\" up=\"5\" lt=\"2\" dn=\"5\" rt=\"1\" sort=\"190\" group=\"0\" />\n  <card id=\"204\" rarity=\"1\" type=\"0\" up=\"7\" lt=\"2\" dn=\"2\" rt=\"6\" sort=\"191\" group=\"0\" />\n  <card id=\"205\" rarity=\"1\" type=\"1\" up=\"1\" lt=\"5\" dn=\"7\" rt=\"5\" sort=\"192\" group=\"0\" />\n  <card id=\"206\" rarity=\"2\" type=\"0\" up=\"2\" lt=\"7\" dn=\"6\" rt=\"7\" sort=\"193\" group=\"0\" />\n  <card id=\"207\" rarity=\"2\" type=\"0\" up=\"5\" lt=\"8\" dn=\"6\" rt=\"3\" sort=\"194\" group=\"0\" />\n  <card id=\"208\" rarity=\"2\" type=\"0\" up=\"4\" lt=\"7\" dn=\"1\" rt=\"8\" sort=\"195\" group=\"0\" />\n  <card id=\"209\" rarity=\"3\" type=\"0\" up=\"9\" lt=\"2\" dn=\"5\" rt=\"8\" sort=\"196\" group=\"0\" />\n  <card id=\"210\" rarity=\"4\" type=\"0\" up=\"1\" lt=\"10\" dn=\"10\" rt=\"6\" sort=\"197\" group=\"0\" />\n  <card id=\"211\" rarity=\"0\" type=\"0\" up=\"3\" lt=\"2\" dn=\"1\" rt=\"6\" sort=\"198\" group=\"0\" />\n  <card id=\"212\" rarity=\"1\" type=\"0\" up=\"4\" lt=\"2\" dn=\"7\" rt=\"4\" sort=\"199\" group=\"0\" />\n  <card id=\"213\" rarity=\"1\" type=\"0\" up=\"6\" lt=\"4\" dn=\"4\" rt=\"4\" sort=\"200\" group=\"0\" />\n  <card id=\"214\" rarity=\"1\" type=\"0\" up=\"6\" lt=\"2\" dn=\"3\" rt=\"7\" sort=\"201\" group=\"0\" />\n  <card id=\"215\" rarity=\"2\" type=\"0\" up=\"7\" lt=\"7\" dn=\"1\" rt=\"7\" sort=\"202\" group=\"0\" />\n  <card id=\"216\" rarity=\"2\" type=\"0\" up=\"7\" lt=\"1\" dn=\"7\" rt=\"7\" sort=\"203\" group=\"0\" />\n  <card id=\"217\" rarity=\"2\" type=\"3\" up=\"8\" lt=\"4\" dn=\"8\" rt=\"2\" sort=\"204\" group=\"0\" />\n  <card id=\"218\" rarity=\"2\" type=\"1\" up=\"3\" lt=\"3\" dn=\"8\" rt=\"7\" sort=\"205\" group=\"0\" />\n  <card id=\"219\" rarity=\"3\" type=\"0\" up=\"8\" lt=\"9\" dn=\"1\" rt=\"7\" sort=\"206\" group=\"0\" />\n  <card id=\"220\" rarity=\"4\" type=\"0\" up=\"10\" lt=\"2\" dn=\"7\" rt=\"9\" sort=\"207\" group=\"0\" />\n  <card id=\"221\" rarity=\"0\" type=\"0\" up=\"6\" lt=\"1\" dn=\"1\" rt=\"5\" sort=\"212\" group=\"0\" />\n  <card id=\"222\" rarity=\"1\" type=\"0\" up=\"2\" lt=\"7\" dn=\"5\" rt=\"6\" sort=\"213\" group=\"0\" />\n  <card id=\"223\" rarity=\"1\" type=\"0\" up=\"3\" lt=\"7\" dn=\"3\" rt=\"5\" sort=\"215\" group=\"0\" />\n  <card id=\"224\" rarity=\"2\" type=\"0\" up=\"3\" lt=\"8\" dn=\"4\" rt=\"8\" sort=\"208\" group=\"0\" />\n  <card id=\"225\" rarity=\"2\" type=\"0\" up=\"4\" lt=\"7\" dn=\"5\" rt=\"7\" sort=\"214\" group=\"0\" />\n  <card id=\"226\" rarity=\"2\" type=\"0\" up=\"3\" lt=\"8\" dn=\"7\" rt=\"3\" sort=\"216\" group=\"0\" />\n  <card id=\"227\" rarity=\"2\" type=\"0\" up=\"5\" lt=\"8\" dn=\"2\" rt=\"7\" sort=\"217\" group=\"0\" />\n  <card id=\"228\" rarity=\"3\" type=\"2\" up=\"2\" lt=\"7\" dn=\"7\" rt=\"8\" sort=\"209\" group=\"0\" />\n  <card id=\"229\" rarity=\"4\" type=\"0\" up=\"7\" lt=\"6\" dn=\"10\" rt=\"7\" sort=\"210\" group=\"0\" />\n  <card id=\"230\" rarity=\"4\" type=\"0\" up=\"10\" lt=\"5\" dn=\"10\" rt=\"2\" sort=\"211\" group=\"0\" />\n  <card id=\"231\" rarity=\"0\" type=\"0\" up=\"7\" lt=\"1\" dn=\"1\" rt=\"4\" sort=\"224\" group=\"0\" />\n  <card id=\"232\" rarity=\"1\" type=\"0\" up=\"3\" lt=\"7\" dn=\"1\" rt=\"7\" sort=\"218\" group=\"0\" />\n  <card id=\"233\" rarity=\"2\" type=\"0\" up=\"2\" lt=\"6\" dn=\"6\" rt=\"7\" sort=\"219\" group=\"0\" />\n  <card id=\"234\" rarity=\"2\" type=\"0\" up=\"5\" lt=\"8\" dn=\"5\" rt=\"5\" sort=\"220\" group=\"0\" />\n  <card id=\"235\" rarity=\"2\" type=\"0\" up=\"1\" lt=\"7\" dn=\"7\" rt=\"7\" sort=\"221\" group=\"0\" />\n  <card id=\"236\" rarity=\"2\" type=\"0\" up=\"4\" lt=\"8\" dn=\"4\" rt=\"7\" sort=\"225\" group=\"0\" />\n  <card id=\"237\" rarity=\"2\" type=\"0\" up=\"7\" lt=\"2\" dn=\"8\" rt=\"5\" sort=\"226\" group=\"0\" />\n  <card id=\"238\" rarity=\"2\" type=\"4\" up=\"8\" lt=\"4\" dn=\"1\" rt=\"8\" sort=\"222\" group=\"0\" />\n  <card id=\"239\" rarity=\"4\" type=\"0\" up=\"10\" lt=\"8\" dn=\"1\" rt=\"8\" sort=\"227\" group=\"0\" />\n  <card id=\"240\" rarity=\"4\" type=\"0\" up=\"6\" lt=\"9\" dn=\"3\" rt=\"10\" sort=\"223\" group=\"0\" />\n  <card id=\"241\" rarity=\"0\" type=\"3\" up=\"1\" lt=\"4\" dn=\"1\" rt=\"7\" sort=\"228\" group=\"0\" />\n  <card id=\"242\" rarity=\"1\" type=\"0\" up=\"4\" lt=\"5\" dn=\"3\" rt=\"7\" sort=\"235\" group=\"0\" />\n  <card id=\"243\" rarity=\"1\" type=\"0\" up=\"7\" lt=\"1\" dn=\"1\" rt=\"7\" sort=\"236\" group=\"0\" />\n  <card id=\"244\" rarity=\"2\" type=\"4\" up=\"6\" lt=\"5\" dn=\"8\" rt=\"3\" sort=\"229\" group=\"0\" />\n  <card id=\"245\" rarity=\"2\" type=\"0\" up=\"7\" lt=\"4\" dn=\"4\" rt=\"8\" sort=\"237\" group=\"0\" />\n  <card id=\"246\" rarity=\"2\" type=\"0\" up=\"7\" lt=\"7\" dn=\"7\" rt=\"1\" sort=\"230\" group=\"0\" />\n  <card id=\"247\" rarity=\"2\" type=\"0\" up=\"6\" lt=\"6\" dn=\"6\" rt=\"6\" sort=\"231\" group=\"0\" />\n  <card id=\"248\" rarity=\"3\" type=\"1\" up=\"6\" lt=\"9\" dn=\"6\" rt=\"7\" sort=\"232\" group=\"0\" />\n  <card id=\"249\" rarity=\"3\" type=\"0\" up=\"9\" lt=\"4\" dn=\"8\" rt=\"4\" sort=\"238\" group=\"0\" />\n  <card id=\"250\" rarity=\"4\" type=\"0\" up=\"6\" lt=\"10\" dn=\"7\" rt=\"7\" sort=\"233\" group=\"0\" />\n  <card id=\"251\" rarity=\"4\" type=\"3\" up=\"6\" lt=\"8\" dn=\"8\" rt=\"8\" sort=\"234\" group=\"0\" />\n  <card id=\"252\" rarity=\"4\" type=\"0\" up=\"7\" lt=\"10\" dn=\"9\" rt=\"1\" sort=\"1014\" group=\"5\" />\n  <card id=\"253\" rarity=\"0\" type=\"0\" up=\"1\" lt=\"7\" dn=\"2\" rt=\"3\" sort=\"239\" group=\"0\" />\n  <card id=\"254\" rarity=\"0\" type=\"0\" up=\"7\" lt=\"2\" dn=\"2\" rt=\"2\" sort=\"240\" group=\"0\" />\n  <card id=\"255\" rarity=\"0\" type=\"0\" up=\"5\" lt=\"1\" dn=\"6\" rt=\"1\" sort=\"241\" group=\"0\" />\n  <card id=\"256\" rarity=\"1\" type=\"0\" up=\"7\" lt=\"4\" dn=\"4\" rt=\"4\" sort=\"242\" group=\"0\" />\n  <card id=\"257\" rarity=\"1\" type=\"1\" up=\"7\" lt=\"5\" dn=\"5\" rt=\"1\" sort=\"243\" group=\"0\" />\n  <card id=\"258\" rarity=\"1\" type=\"0\" up=\"6\" lt=\"4\" dn=\"7\" rt=\"1\" sort=\"244\" group=\"0\" />\n  <card id=\"259\" rarity=\"1\" type=\"0\" up=\"1\" lt=\"3\" dn=\"7\" rt=\"7\" sort=\"245\" group=\"0\" />\n  <card id=\"260\" rarity=\"2\" type=\"0\" up=\"2\" lt=\"6\" dn=\"8\" rt=\"6\" sort=\"246\" group=\"0\" />\n  <card id=\"261\" rarity=\"2\" type=\"0\" up=\"8\" lt=\"6\" dn=\"6\" rt=\"2\" sort=\"247\" group=\"0\" />\n  <card id=\"262\" rarity=\"2\" type=\"0\" up=\"6\" lt=\"2\" dn=\"6\" rt=\"8\" sort=\"248\" group=\"0\" />\n  <card id=\"263\" rarity=\"2\" type=\"0\" up=\"6\" lt=\"8\" dn=\"2\" rt=\"6\" sort=\"249\" group=\"0\" />\n  <card id=\"264\" rarity=\"2\" type=\"0\" up=\"8\" lt=\"5\" dn=\"5\" rt=\"5\" sort=\"250\" group=\"0\" />\n  <card id=\"265\" rarity=\"2\" type=\"0\" up=\"1\" lt=\"8\" dn=\"8\" rt=\"3\" sort=\"251\" group=\"0\" />\n  <card id=\"266\" rarity=\"2\" type=\"0\" up=\"4\" lt=\"4\" dn=\"7\" rt=\"6\" sort=\"252\" group=\"0\" />\n  <card id=\"267\" rarity=\"3\" type=\"0\" up=\"8\" lt=\"3\" dn=\"8\" rt=\"8\" sort=\"253\" group=\"0\" />\n  <card id=\"268\" rarity=\"3\" type=\"3\" up=\"2\" lt=\"9\" dn=\"5\" rt=\"9\" sort=\"254\" group=\"0\" />\n  <card id=\"269\" rarity=\"3\" type=\"3\" up=\"9\" lt=\"2\" dn=\"5\" rt=\"9\" sort=\"255\" group=\"0\" />\n  <card id=\"270\" rarity=\"3\" type=\"0\" up=\"1\" lt=\"9\" dn=\"9\" rt=\"5\" sort=\"256\" group=\"0\" />\n  <card id=\"271\" rarity=\"4\" type=\"0\" up=\"8\" lt=\"6\" dn=\"6\" rt=\"10\" sort=\"257\" group=\"0\" />\n  <card id=\"272\" rarity=\"4\" type=\"0\" up=\"1\" lt=\"9\" dn=\"9\" rt=\"9\" sort=\"258\" group=\"0\" />\n  <card id=\"273\" rarity=\"0\" type=\"0\" up=\"2\" lt=\"7\" dn=\"2\" rt=\"2\" sort=\"259\" group=\"0\" />\n  <card id=\"274\" rarity=\"0\" type=\"0\" up=\"1\" lt=\"2\" dn=\"6\" rt=\"4\" sort=\"260\" group=\"0\" />\n  <card id=\"275\" rarity=\"1\" type=\"0\" up=\"3\" lt=\"5\" dn=\"6\" rt=\"5\" sort=\"261\" group=\"0\" />\n  <card id=\"276\" rarity=\"1\" type=\"1\" up=\"7\" lt=\"6\" dn=\"2\" rt=\"4\" sort=\"262\" group=\"0\" />\n  <card id=\"277\" rarity=\"1\" type=\"1\" up=\"7\" lt=\"4\" dn=\"6\" rt=\"2\" sort=\"263\" group=\"0\" />\n  <card id=\"278\" rarity=\"2\" type=\"0\" up=\"8\" lt=\"7\" dn=\"2\" rt=\"4\" sort=\"264\" group=\"0\" />\n  <card id=\"279\" rarity=\"2\" type=\"0\" up=\"4\" lt=\"6\" dn=\"6\" rt=\"8\" sort=\"265\" group=\"0\" />\n  <card id=\"280\" rarity=\"3\" type=\"0\" up=\"7\" lt=\"7\" dn=\"9\" rt=\"4\" sort=\"266\" group=\"0\" />\n  <card id=\"281\" rarity=\"4\" type=\"0\" up=\"10\" lt=\"9\" dn=\"7\" rt=\"1\" sort=\"267\" group=\"0\" />\n  <card id=\"282\" rarity=\"4\" type=\"0\" up=\"4\" lt=\"8\" dn=\"6\" rt=\"10\" sort=\"268\" group=\"0\" />\n  <card id=\"283\" rarity=\"0\" type=\"0\" up=\"2\" lt=\"2\" dn=\"2\" rt=\"7\" sort=\"269\" group=\"0\" />\n  <card id=\"284\" rarity=\"0\" type=\"1\" up=\"4\" lt=\"6\" dn=\"2\" rt=\"2\" sort=\"270\" group=\"0\" />\n  <card id=\"285\" rarity=\"1\" type=\"0\" up=\"2\" lt=\"4\" dn=\"7\" rt=\"6\" sort=\"271\" group=\"0\" />\n  <card id=\"286\" rarity=\"1\" type=\"0\" up=\"5\" lt=\"5\" dn=\"6\" rt=\"4\" sort=\"278\" group=\"0\" />\n  <card id=\"287\" rarity=\"2\" type=\"0\" up=\"6\" lt=\"2\" dn=\"8\" rt=\"6\" sort=\"272\" group=\"0\" />\n  <card id=\"288\" rarity=\"2\" type=\"0\" up=\"8\" lt=\"7\" dn=\"1\" rt=\"6\" sort=\"273\" group=\"0\" />\n  <card id=\"289\" rarity=\"2\" type=\"0\" up=\"7\" lt=\"5\" dn=\"8\" rt=\"2\" sort=\"274\" group=\"0\" />\n  <card id=\"290\" rarity=\"2\" type=\"0\" up=\"5\" lt=\"6\" dn=\"3\" rt=\"8\" sort=\"275\" group=\"0\" />\n  <card id=\"291\" rarity=\"3\" type=\"4\" up=\"8\" lt=\"7\" dn=\"1\" rt=\"9\" sort=\"276\" group=\"0\" />\n  <card id=\"292\" rarity=\"4\" type=\"0\" up=\"9\" lt=\"9\" dn=\"2\" rt=\"9\" sort=\"277\" group=\"0\" />\n  <card id=\"293\" rarity=\"4\" type=\"4\" up=\"10\" lt=\"10\" dn=\"4\" rt=\"1\" sort=\"279\" group=\"0\" />\n  <card id=\"294\" rarity=\"0\" type=\"1\" up=\"2\" lt=\"4\" dn=\"6\" rt=\"2\" sort=\"280\" group=\"0\" />\n  <card id=\"295\" rarity=\"1\" type=\"0\" up=\"1\" lt=\"7\" dn=\"1\" rt=\"7\" sort=\"288\" group=\"0\" />\n  <card id=\"296\" rarity=\"1\" type=\"0\" up=\"6\" lt=\"4\" dn=\"2\" rt=\"6\" sort=\"281\" group=\"0\" />\n  <card id=\"297\" rarity=\"2\" type=\"0\" up=\"5\" lt=\"4\" dn=\"7\" rt=\"7\" sort=\"282\" group=\"0\" />\n  <card id=\"298\" rarity=\"2\" type=\"4\" up=\"8\" lt=\"4\" dn=\"6\" rt=\"6\" sort=\"289\" group=\"0\" />\n  <card id=\"299\" rarity=\"2\" type=\"0\" up=\"6\" lt=\"1\" dn=\"7\" rt=\"8\" sort=\"290\" group=\"0\" />\n  <card id=\"300\" rarity=\"3\" type=\"0\" up=\"8\" lt=\"4\" dn=\"9\" rt=\"4\" sort=\"283\" group=\"0\" />\n  <card id=\"301\" rarity=\"3\" type=\"3\" up=\"7\" lt=\"9\" dn=\"8\" rt=\"1\" sort=\"284\" group=\"0\" />\n  <card id=\"302\" rarity=\"3\" type=\"4\" up=\"1\" lt=\"9\" dn=\"8\" rt=\"7\" sort=\"285\" group=\"0\" />\n  <card id=\"303\" rarity=\"4\" type=\"0\" up=\"4\" lt=\"8\" dn=\"10\" rt=\"6\" sort=\"286\" group=\"0\" />\n  <card id=\"304\" rarity=\"4\" type=\"2\" up=\"2\" lt=\"10\" dn=\"10\" rt=\"5\" sort=\"287\" group=\"0\" />\n  <card id=\"305\" rarity=\"0\" type=\"0\" up=\"4\" lt=\"7\" dn=\"1\" rt=\"1\" sort=\"297\" group=\"0\" />\n  <card id=\"306\" rarity=\"1\" type=\"0\" up=\"3\" lt=\"3\" dn=\"7\" rt=\"6\" sort=\"291\" group=\"0\" />\n  <card id=\"307\" rarity=\"2\" type=\"0\" up=\"6\" lt=\"1\" dn=\"8\" rt=\"7\" sort=\"298\" group=\"0\" />\n  <card id=\"308\" rarity=\"2\" type=\"0\" up=\"8\" lt=\"7\" dn=\"6\" rt=\"1\" sort=\"299\" group=\"0\" />\n  <card id=\"309\" rarity=\"2\" type=\"0\" up=\"4\" lt=\"8\" dn=\"6\" rt=\"6\" sort=\"300\" group=\"0\" />\n  <card id=\"310\" rarity=\"2\" type=\"0\" up=\"6\" lt=\"6\" dn=\"4\" rt=\"8\" sort=\"301\" group=\"0\" />\n  <card id=\"311\" rarity=\"2\" type=\"0\" up=\"8\" lt=\"6\" dn=\"4\" rt=\"6\" sort=\"302\" group=\"0\" />\n  <card id=\"312\" rarity=\"3\" type=\"4\" up=\"7\" lt=\"1\" dn=\"9\" rt=\"8\" sort=\"292\" group=\"0\" />\n  <card id=\"313\" rarity=\"3\" type=\"0\" up=\"7\" lt=\"9\" dn=\"7\" rt=\"1\" sort=\"293\" group=\"0\" />\n  <card id=\"314\" rarity=\"3\" type=\"0\" up=\"5\" lt=\"9\" dn=\"1\" rt=\"9\" sort=\"294\" group=\"0\" />\n  <card id=\"315\" rarity=\"4\" type=\"0\" up=\"10\" lt=\"5\" dn=\"8\" rt=\"6\" sort=\"295\" group=\"0\" />\n  <card id=\"316\" rarity=\"4\" type=\"2\" up=\"5\" lt=\"9\" dn=\"6\" rt=\"9\" sort=\"296\" group=\"0\" />\n  <card id=\"317\" rarity=\"1\" type=\"0\" up=\"5\" lt=\"4\" dn=\"4\" rt=\"7\" sort=\"303\" group=\"0\" />\n  <card id=\"318\" rarity=\"2\" type=\"2\" up=\"8\" lt=\"2\" dn=\"8\" rt=\"4\" sort=\"304\" group=\"0\" />\n  <card id=\"319\" rarity=\"2\" type=\"4\" up=\"7\" lt=\"5\" dn=\"8\" rt=\"3\" sort=\"305\" group=\"0\" />\n  <card id=\"320\" rarity=\"2\" type=\"2\" up=\"8\" lt=\"3\" dn=\"4\" rt=\"7\" sort=\"309\" group=\"0\" />\n  <card id=\"321\" rarity=\"2\" type=\"0\" up=\"1\" lt=\"6\" dn=\"7\" rt=\"8\" sort=\"310\" group=\"0\" />\n  <card id=\"322\" rarity=\"2\" type=\"0\" up=\"8\" lt=\"1\" dn=\"7\" rt=\"6\" sort=\"311\" group=\"0\" />\n  <card id=\"323\" rarity=\"3\" type=\"0\" up=\"9\" lt=\"1\" dn=\"7\" rt=\"8\" sort=\"306\" group=\"0\" />\n  <card id=\"324\" rarity=\"3\" type=\"4\" up=\"9\" lt=\"8\" dn=\"7\" rt=\"1\" sort=\"307\" group=\"0\" />\n  <card id=\"325\" rarity=\"3\" type=\"0\" up=\"4\" lt=\"4\" dn=\"9\" rt=\"9\" sort=\"312\" group=\"0\" />\n  <card id=\"326\" rarity=\"4\" type=\"0\" up=\"6\" lt=\"10\" dn=\"4\" rt=\"8\" sort=\"308\" group=\"0\" />\n  <card id=\"327\" rarity=\"0\" type=\"0\" up=\"4\" lt=\"4\" dn=\"3\" rt=\"4\" sort=\"313\" group=\"0\" />\n  <card id=\"328\" rarity=\"0\" type=\"0\" up=\"3\" lt=\"4\" dn=\"4\" rt=\"4\" sort=\"314\" group=\"0\" />\n  <card id=\"329\" rarity=\"0\" type=\"0\" up=\"5\" lt=\"4\" dn=\"1\" rt=\"4\" sort=\"315\" group=\"0\" />\n  <card id=\"330\" rarity=\"1\" type=\"1\" up=\"4\" lt=\"4\" dn=\"7\" rt=\"4\" sort=\"316\" group=\"0\" />\n  <card id=\"331\" rarity=\"1\" type=\"0\" up=\"4\" lt=\"6\" dn=\"6\" rt=\"4\" sort=\"317\" group=\"0\" />\n  <card id=\"332\" rarity=\"1\" type=\"0\" up=\"7\" lt=\"3\" dn=\"7\" rt=\"1\" sort=\"318\" group=\"0\" />\n  <card id=\"333\" rarity=\"2\" type=\"0\" up=\"6\" lt=\"8\" dn=\"7\" rt=\"1\" sort=\"319\" group=\"0\" />\n  <card id=\"334\" rarity=\"2\" type=\"0\" up=\"8\" lt=\"7\" dn=\"3\" rt=\"3\" sort=\"320\" group=\"0\" />\n  <card id=\"335\" rarity=\"2\" type=\"0\" up=\"5\" lt=\"8\" dn=\"5\" rt=\"6\" sort=\"332\" group=\"0\" />\n  <card id=\"336\" rarity=\"2\" type=\"2\" up=\"4\" lt=\"1\" dn=\"8\" rt=\"8\" sort=\"321\" group=\"0\" />\n  <card id=\"337\" rarity=\"2\" type=\"0\" up=\"7\" lt=\"1\" dn=\"8\" rt=\"6\" sort=\"322\" group=\"0\" />\n  <card id=\"338\" rarity=\"2\" type=\"0\" up=\"7\" lt=\"7\" dn=\"3\" rt=\"7\" sort=\"323\" group=\"0\" />\n  <card id=\"339\" rarity=\"3\" type=\"2\" up=\"5\" lt=\"1\" dn=\"9\" rt=\"9\" sort=\"324\" group=\"0\" />\n  <card id=\"340\" rarity=\"3\" type=\"4\" up=\"9\" lt=\"3\" dn=\"6\" rt=\"8\" sort=\"325\" group=\"0\" />\n  <card id=\"341\" rarity=\"3\" type=\"3\" up=\"4\" lt=\"9\" dn=\"9\" rt=\"3\" sort=\"326\" group=\"0\" />\n  <card id=\"342\" rarity=\"3\" type=\"0\" up=\"7\" lt=\"9\" dn=\"6\" rt=\"6\" sort=\"327\" group=\"0\" />\n  <card id=\"343\" rarity=\"3\" type=\"0\" up=\"9\" lt=\"3\" dn=\"7\" rt=\"7\" sort=\"328\" group=\"0\" />\n  <card id=\"344\" rarity=\"4\" type=\"2\" up=\"5\" lt=\"10\" dn=\"3\" rt=\"10\" sort=\"329\" group=\"0\" />\n  <card id=\"345\" rarity=\"4\" type=\"2\" up=\"10\" lt=\"3\" dn=\"10\" rt=\"5\" sort=\"330\" group=\"0\" />\n  <card id=\"346\" rarity=\"4\" type=\"4\" up=\"6\" lt=\"6\" dn=\"9\" rt=\"9\" sort=\"331\" group=\"0\" />\n  <card id=\"347\" rarity=\"0\" type=\"0\" up=\"1\" lt=\"6\" dn=\"5\" rt=\"1\" sort=\"333\" group=\"0\" />\n  <card id=\"348\" rarity=\"0\" type=\"0\" up=\"1\" lt=\"1\" dn=\"7\" rt=\"4\" sort=\"341\" group=\"0\" />\n  <card id=\"349\" rarity=\"1\" type=\"1\" up=\"4\" lt=\"7\" dn=\"4\" rt=\"4\" sort=\"342\" group=\"0\" />\n  <card id=\"350\" rarity=\"2\" type=\"0\" up=\"6\" lt=\"8\" dn=\"1\" rt=\"7\" sort=\"334\" group=\"0\" />\n  <card id=\"351\" rarity=\"2\" type=\"0\" up=\"7\" lt=\"6\" dn=\"1\" rt=\"8\" sort=\"335\" group=\"0\" />\n  <card id=\"352\" rarity=\"2\" type=\"0\" up=\"8\" lt=\"1\" dn=\"6\" rt=\"7\" sort=\"336\" group=\"0\" />\n  <card id=\"353\" rarity=\"2\" type=\"0\" up=\"6\" lt=\"7\" dn=\"8\" rt=\"1\" sort=\"337\" group=\"0\" />\n  <card id=\"354\" rarity=\"3\" type=\"0\" up=\"8\" lt=\"1\" dn=\"9\" rt=\"7\" sort=\"338\" group=\"0\" />\n  <card id=\"355\" rarity=\"3\" type=\"0\" up=\"7\" lt=\"8\" dn=\"1\" rt=\"9\" sort=\"339\" group=\"0\" />\n  <card id=\"356\" rarity=\"4\" type=\"0\" up=\"7\" lt=\"10\" dn=\"2\" rt=\"9\" sort=\"340\" group=\"0\" />\n  <card id=\"357\" rarity=\"0\" type=\"0\" up=\"1\" lt=\"5\" dn=\"1\" rt=\"6\" sort=\"349\" group=\"0\" />\n  <card id=\"358\" rarity=\"1\" type=\"0\" up=\"2\" lt=\"4\" dn=\"6\" rt=\"6\" sort=\"350\" group=\"0\" />\n  <card id=\"359\" rarity=\"1\" type=\"0\" up=\"7\" lt=\"2\" dn=\"5\" rt=\"5\" sort=\"351\" group=\"0\" />\n  <card id=\"360\" rarity=\"1\" type=\"0\" up=\"4\" lt=\"7\" dn=\"2\" rt=\"6\" sort=\"352\" group=\"0\" />\n  <card id=\"361\" rarity=\"2\" type=\"0\" up=\"6\" lt=\"7\" dn=\"7\" rt=\"4\" sort=\"343\" group=\"0\" />\n  <card id=\"362\" rarity=\"2\" type=\"0\" up=\"1\" lt=\"6\" dn=\"8\" rt=\"7\" sort=\"344\" group=\"0\" />\n  <card id=\"363\" rarity=\"2\" type=\"1\" up=\"2\" lt=\"8\" dn=\"8\" rt=\"2\" sort=\"353\" group=\"0\" />\n  <card id=\"364\" rarity=\"3\" type=\"0\" up=\"7\" lt=\"6\" dn=\"8\" rt=\"7\" sort=\"345\" group=\"0\" />\n  <card id=\"365\" rarity=\"3\" type=\"0\" up=\"6\" lt=\"7\" dn=\"7\" rt=\"8\" sort=\"346\" group=\"0\" />\n  <card id=\"366\" rarity=\"3\" type=\"0\" up=\"5\" lt=\"9\" dn=\"5\" rt=\"8\" sort=\"347\" group=\"0\" />\n  <card id=\"367\" rarity=\"4\" type=\"0\" up=\"9\" lt=\"4\" dn=\"10\" rt=\"5\" sort=\"348\" group=\"0\" />\n  <card id=\"368\" rarity=\"0\" type=\"0\" up=\"6\" lt=\"3\" dn=\"1\" rt=\"3\" sort=\"354\" group=\"0\" />\n  <card id=\"369\" rarity=\"0\" type=\"0\" up=\"2\" lt=\"5\" dn=\"5\" rt=\"2\" sort=\"360\" group=\"0\" />\n  <card id=\"370\" rarity=\"1\" type=\"0\" up=\"5\" lt=\"5\" dn=\"7\" rt=\"1\" sort=\"355\" group=\"0\" />\n  <card id=\"371\" rarity=\"1\" type=\"0\" up=\"4\" lt=\"1\" dn=\"6\" rt=\"7\" sort=\"356\" group=\"0\" />\n  <card id=\"372\" rarity=\"2\" type=\"0\" up=\"5\" lt=\"7\" dn=\"5\" rt=\"6\" sort=\"361\" group=\"0\" />\n  <card id=\"373\" rarity=\"2\" type=\"0\" up=\"8\" lt=\"3\" dn=\"2\" rt=\"8\" sort=\"362\" group=\"0\" />\n  <card id=\"374\" rarity=\"2\" type=\"0\" up=\"1\" lt=\"7\" dn=\"6\" rt=\"8\" sort=\"357\" group=\"0\" />\n  <card id=\"375\" rarity=\"3\" type=\"0\" up=\"8\" lt=\"6\" dn=\"7\" rt=\"7\" sort=\"358\" group=\"0\" />\n  <card id=\"376\" rarity=\"3\" type=\"0\" up=\"7\" lt=\"8\" dn=\"6\" rt=\"7\" sort=\"359\" group=\"0\" />\n  <card id=\"377\" rarity=\"4\" type=\"0\" up=\"4\" lt=\"8\" dn=\"7\" rt=\"10\" sort=\"363\" group=\"0\" />\n  <card id=\"378\" rarity=\"0\" type=\"0\" up=\"4\" lt=\"5\" dn=\"1\" rt=\"4\" sort=\"369\" group=\"0\" />\n  <card id=\"379\" rarity=\"1\" type=\"0\" up=\"7\" lt=\"2\" dn=\"6\" rt=\"4\" sort=\"370\" group=\"0\" />\n  <card id=\"380\" rarity=\"1\" type=\"0\" up=\"3\" lt=\"6\" dn=\"7\" rt=\"3\" sort=\"371\" group=\"0\" />\n  <card id=\"381\" rarity=\"2\" type=\"0\" up=\"6\" lt=\"8\" dn=\"4\" rt=\"5\" sort=\"364\" group=\"0\" />\n  <card id=\"382\" rarity=\"2\" type=\"0\" up=\"3\" lt=\"7\" dn=\"5\" rt=\"7\" sort=\"365\" group=\"0\" />\n  <card id=\"383\" rarity=\"2\" type=\"0\" up=\"4\" lt=\"2\" dn=\"8\" rt=\"8\" sort=\"372\" group=\"0\" />\n  <card id=\"384\" rarity=\"2\" type=\"0\" up=\"7\" lt=\"5\" dn=\"2\" rt=\"8\" sort=\"373\" group=\"0\" />\n  <card id=\"385\" rarity=\"3\" type=\"0\" up=\"5\" lt=\"5\" dn=\"9\" rt=\"8\" sort=\"366\" group=\"0\" />\n  <card id=\"386\" rarity=\"3\" type=\"0\" up=\"8\" lt=\"5\" dn=\"5\" rt=\"9\" sort=\"374\" group=\"0\" />\n  <card id=\"387\" rarity=\"4\" type=\"0\" up=\"10\" lt=\"5\" dn=\"2\" rt=\"10\" sort=\"367\" group=\"0\" />\n  <card id=\"388\" rarity=\"4\" type=\"0\" up=\"9\" lt=\"5\" dn=\"10\" rt=\"4\" sort=\"368\" group=\"0\" />\n  <card id=\"389\" rarity=\"0\" type=\"0\" up=\"2\" lt=\"3\" dn=\"4\" rt=\"3\" sort=\"385\" group=\"0\" />\n  <card id=\"390\" rarity=\"1\" type=\"0\" up=\"5\" lt=\"6\" dn=\"2\" rt=\"3\" sort=\"386\" group=\"0\" />\n  <card id=\"391\" rarity=\"1\" type=\"0\" up=\"4\" lt=\"4\" dn=\"5\" rt=\"5\" sort=\"387\" group=\"0\" />\n  <card id=\"392\" rarity=\"1\" type=\"0\" up=\"5\" lt=\"7\" dn=\"4\" rt=\"3\" sort=\"389\" group=\"0\" />\n  <card id=\"393\" rarity=\"2\" type=\"0\" up=\"5\" lt=\"5\" dn=\"7\" rt=\"5\" sort=\"388\" group=\"0\" />\n  <card id=\"394\" rarity=\"2\" type=\"0\" up=\"8\" lt=\"6\" dn=\"7\" rt=\"1\" sort=\"375\" group=\"0\" />\n  <card id=\"395\" rarity=\"2\" type=\"0\" up=\"1\" lt=\"7\" dn=\"8\" rt=\"6\" sort=\"376\" group=\"0\" />\n  <card id=\"396\" rarity=\"2\" type=\"0\" up=\"1\" lt=\"8\" dn=\"6\" rt=\"7\" sort=\"377\" group=\"0\" />\n  <card id=\"397\" rarity=\"2\" type=\"0\" up=\"1\" lt=\"8\" dn=\"7\" rt=\"6\" sort=\"378\" group=\"0\" />\n  <card id=\"398\" rarity=\"2\" type=\"0\" up=\"7\" lt=\"8\" dn=\"6\" rt=\"1\" sort=\"379\" group=\"0\" />\n  <card id=\"399\" rarity=\"2\" type=\"0\" up=\"7\" lt=\"8\" dn=\"1\" rt=\"6\" sort=\"380\" group=\"0\" />\n  <card id=\"400\" rarity=\"2\" type=\"0\" up=\"7\" lt=\"6\" dn=\"8\" rt=\"1\" sort=\"381\" group=\"0\" />\n  <card id=\"401\" rarity=\"2\" type=\"0\" up=\"7\" lt=\"6\" dn=\"6\" rt=\"6\" sort=\"382\" group=\"0\" />\n  <card id=\"402\" rarity=\"3\" type=\"0\" up=\"6\" lt=\"9\" dn=\"6\" rt=\"7\" sort=\"383\" group=\"0\" />\n  <card id=\"403\" rarity=\"3\" type=\"2\" up=\"9\" lt=\"5\" dn=\"7\" rt=\"6\" sort=\"390\" group=\"0\" />\n  <card id=\"404\" rarity=\"4\" type=\"0\" up=\"9\" lt=\"9\" dn=\"9\" rt=\"1\" sort=\"384\" group=\"0\" />\n  <card id=\"405\" rarity=\"4\" type=\"0\" up=\"10\" lt=\"7\" dn=\"8\" rt=\"5\" sort=\"1015\" group=\"5\" />\n  <card id=\"406\" rarity=\"0\" type=\"1\" up=\"5\" lt=\"3\" dn=\"4\" rt=\"2\" sort=\"391\" group=\"0\" />\n  <card id=\"407\" rarity=\"0\" type=\"0\" up=\"3\" lt=\"5\" dn=\"3\" rt=\"3\" sort=\"392\" group=\"0\" />\n  <card id=\"408\" rarity=\"1\" type=\"1\" up=\"5\" lt=\"5\" dn=\"4\" rt=\"5\" sort=\"393\" group=\"0\" />\n  <card id=\"409\" rarity=\"1\" type=\"0\" up=\"6\" lt=\"5\" dn=\"3\" rt=\"4\" sort=\"394\" group=\"0\" />\n  <card id=\"410\" rarity=\"1\" type=\"0\" up=\"3\" lt=\"5\" dn=\"4\" rt=\"6\" sort=\"395\" group=\"0\" />\n  <card id=\"411\" rarity=\"1\" type=\"0\" up=\"4\" lt=\"6\" dn=\"4\" rt=\"5\" sort=\"396\" group=\"0\" />\n  <card id=\"412\" rarity=\"1\" type=\"0\" up=\"5\" lt=\"3\" dn=\"6\" rt=\"5\" sort=\"397\" group=\"0\" />\n  <card id=\"413\" rarity=\"2\" type=\"0\" up=\"5\" lt=\"7\" dn=\"7\" rt=\"2\" sort=\"398\" group=\"0\" />\n  <card id=\"414\" rarity=\"2\" type=\"0\" up=\"3\" lt=\"7\" dn=\"5\" rt=\"6\" sort=\"399\" group=\"0\" />\n  <card id=\"415\" rarity=\"2\" type=\"0\" up=\"6\" lt=\"6\" dn=\"4\" rt=\"6\" sort=\"400\" group=\"0\" />\n  <card id=\"416\" rarity=\"2\" type=\"0\" up=\"7\" lt=\"7\" dn=\"4\" rt=\"4\" sort=\"401\" group=\"0\" />\n  <card id=\"417\" rarity=\"2\" type=\"0\" up=\"5\" lt=\"3\" dn=\"7\" rt=\"6\" sort=\"402\" group=\"0\" />\n  <card id=\"418\" rarity=\"3\" type=\"0\" up=\"7\" lt=\"5\" dn=\"6\" rt=\"8\" sort=\"403\" group=\"0\" />\n  <card id=\"419\" rarity=\"4\" type=\"0\" up=\"9\" lt=\"8\" dn=\"5\" rt=\"7\" sort=\"404\" group=\"0\" />\n  <card id=\"420\" rarity=\"4\" type=\"0\" up=\"3\" lt=\"10\" dn=\"9\" rt=\"5\" sort=\"405\" group=\"0\" />\n  <card id=\"421\" rarity=\"2\" type=\"0\" up=\"7\" lt=\"3\" dn=\"3\" rt=\"8\" sort=\"410\" group=\"0\" />\n  <card id=\"422\" rarity=\"3\" type=\"0\" up=\"5\" lt=\"7\" dn=\"8\" rt=\"6\" sort=\"406\" group=\"0\" />\n  <card id=\"423\" rarity=\"3\" type=\"0\" up=\"8\" lt=\"8\" dn=\"5\" rt=\"5\" sort=\"407\" group=\"0\" />\n  <card id=\"424\" rarity=\"4\" type=\"0\" up=\"5\" lt=\"5\" dn=\"10\" rt=\"9\" sort=\"408\" group=\"0\" />\n  <card id=\"425\" rarity=\"4\" type=\"0\" up=\"9\" lt=\"10\" dn=\"6\" rt=\"3\" sort=\"409\" group=\"0\" />\n  <card id=\"426\" rarity=\"1\" type=\"0\" up=\"6\" lt=\"4\" dn=\"5\" rt=\"4\" sort=\"411\" group=\"0\" />\n  <card id=\"427\" rarity=\"2\" type=\"0\" up=\"7\" lt=\"8\" dn=\"4\" rt=\"4\" sort=\"412\" group=\"0\" />\n  <card id=\"428\" rarity=\"2\" type=\"0\" up=\"8\" lt=\"4\" dn=\"4\" rt=\"7\" sort=\"413\" group=\"0\" />\n  <card id=\"429\" rarity=\"2\" type=\"0\" up=\"4\" lt=\"4\" dn=\"7\" rt=\"8\" sort=\"414\" group=\"0\" />\n  <card id=\"430\" rarity=\"2\" type=\"0\" up=\"4\" lt=\"7\" dn=\"4\" rt=\"8\" sort=\"415\" group=\"0\" />\n  <card id=\"431\" rarity=\"2\" type=\"0\" up=\"4\" lt=\"7\" dn=\"8\" rt=\"4\" sort=\"416\" group=\"0\" />\n  <card id=\"432\" rarity=\"3\" type=\"0\" up=\"9\" lt=\"7\" dn=\"7\" rt=\"2\" sort=\"417\" group=\"0\" />\n  <card id=\"433\" rarity=\"3\" type=\"0\" up=\"8\" lt=\"2\" dn=\"9\" rt=\"6\" sort=\"418\" group=\"0\" />\n  <card id=\"434\" rarity=\"3\" type=\"0\" up=\"6\" lt=\"8\" dn=\"8\" rt=\"5\" sort=\"419\" group=\"0\" />\n  <card id=\"435\" rarity=\"4\" type=\"0\" up=\"10\" lt=\"10\" dn=\"4\" rt=\"4\" sort=\"420\" group=\"0\" />\n  <card id=\"436\" rarity=\"1\" type=\"0\" up=\"3\" lt=\"6\" dn=\"5\" rt=\"4\" sort=\"421\" group=\"0\" />\n  <card id=\"437\" rarity=\"2\" type=\"0\" up=\"8\" lt=\"5\" dn=\"6\" rt=\"3\" sort=\"422\" group=\"0\" />\n  <card id=\"438\" rarity=\"3\" type=\"0\" up=\"7\" lt=\"7\" dn=\"7\" rt=\"7\" sort=\"423\" group=\"0\" />\n  <card id=\"439\" rarity=\"4\" type=\"0\" up=\"7\" lt=\"9\" dn=\"1\" rt=\"10\" sort=\"424\" group=\"0\" />\n  <card id=\"440\" rarity=\"3\" type=\"0\" up=\"8\" lt=\"6\" dn=\"5\" rt=\"8\" sort=\"425\" group=\"0\" />\n  <card id=\"441\" rarity=\"1\" type=\"0\" up=\"2\" lt=\"5\" dn=\"3\" rt=\"7\" sort=\"426\" group=\"0\" />\n  <card id=\"442\" rarity=\"1\" type=\"0\" up=\"7\" lt=\"2\" dn=\"6\" rt=\"2\" sort=\"427\" group=\"0\" />\n  <card id=\"443\" rarity=\"2\" type=\"1\" up=\"2\" lt=\"7\" dn=\"4\" rt=\"8\" sort=\"428\" group=\"0\" />\n  <card id=\"444\" rarity=\"3\" type=\"0\" up=\"7\" lt=\"4\" dn=\"7\" rt=\"9\" sort=\"9999\" group=\"0\" />\n</root>"
  },
  {
    "path": "assets/data/hashes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <hash type=\"Rule\" value=\"734146C648739673E4F01B3AE4FD069931BDE015D08686F620DBEB35495293199EF2F8\" name=\"All Open\" />\n  <hash type=\"Rule\" value=\"6241880BB81B0288E63942B1FDBAC5E1103D09C2261379776879B3604CACD30D9F98D6\" name=\"All Open\" />\n  <hash type=\"Rule\" value=\"AF41CD92A8B34270D4F57BB2C4FD06CC317DE469808A86F6108BE62949516324CEF2F8\" name=\"All Open\" />\n  <hash type=\"Rule\" value=\"ED41E76E98AB8170F5B1C23668D81AD900BD2213F60A5873322D5C684D7E903DBF4AB3\" name=\"Ascension\" />\n  <hash type=\"Rule\" value=\"1441DDF9972B8C08E0F69A38CA892585037F1AA6873593FF483FB1495C108856C8E1E4\" name=\"Ascension\" />\n  <hash type=\"Rule\" value=\"4041568A6AB30B58FD3696BC1F692B4F203996D44236FABB60E71A3C08003114373AC4\" name=\"Chaos\" />\n  <hash type=\"Rule\" value=\"394184035193A161E878C37E25A8A68D7B7C2BE4939AC83FF5B185419DA316430C5CEF\" name=\"Descension\" />\n  <hash type=\"Rule\" value=\"5C41C3C41A43CABDC337243FDA67A4015E3A24CCD8AB5A3F30BAD5134E1205D0399399\" name=\"Fallen Ace\" />\n  <hash type=\"Rule\" value=\"A841AC8F66A30F64FA3691BC6F997A5A643A9AE01375FEFB64D30A7D041031003B35C8\" name=\"Order\" />\n  <hash type=\"Rule\" value=\"9A41211B2A359274C67143B743DD505901FB9DAD0374D7B73B6A089B1EC8C9321B4E6A\" name=\"Plus\" />\n  <hash type=\"Rule\" value=\"C7418F435172FB72D795ACB80DCCE56998A863A8A1F1E0F50B138AC8588172573F1FD4\" name=\"Plus\" />\n  <hash type=\"Rule\" value=\"12418EC062521FB0E5AC96F1185A853D037B06B598650667F8AE57F91D13C20EAB0CB8\" name=\"Plus\" />\n  <hash type=\"Rule\" value=\"B54123C18ADF96A6C8779C70E24EF991173D598E424F0BFA4A0B2A0C1F6478056AB0D0\" name=\"Reverse\" />\n  <hash type=\"Rule\" value=\"E5419E551D22C010C82698BF4A7D460E253BF48177FEEAF6180B52B9AD8127099B01DD\" name=\"Same\" />\n  <hash type=\"Rule\" value=\"E341BF02FF17BD88E7754470FD5751E0B77CBC655546A2B22082A32B64F0C6A44E7E85\" name=\"Same\" />\n  <hash type=\"Rule\" value=\"E441E2991921C119C935E5FE0ABD668D2037F4C073FDEBF3180B16648D9233566B65E8\" name=\"Same\" />\n  <hash type=\"Rule\" value=\"6541CC069EEF3F52C465D737032950B62BEC1E988E2499B7A5270E175F0268B40D43D2\" name=\"Same\" />\n  <hash type=\"Rule\" value=\"0B41F2FBE5EF4017F53561B582F227E166E40540D79125F7AECFA47A0C47D44D0B2096\" name=\"Sudden Death\" />\n  <hash type=\"Rule\" value=\"7F419FF69DEF0123F232257791B667D085B40500D7802AFB9ACE68351D8FD89E275486\" name=\"Sudden Death\" />\n  <hash type=\"Rule\" value=\"B841E4288D1F1A53E8FB8A7DA17F96DD142FA144DDF4A0E7501FC8852964CF001AE1C4\" name=\"Swap\" />\n  <hash type=\"Rule\" value=\"D1410F9E6343689DC2F5C83D9DBC2488817F829EF1854937888CAC12F447D6076BE6D9\" name=\"Three Open\" />\n  <hash type=\"Rule\" value=\"C041946CA6935D8EC0FA803EA8BC11C4803F0D9DA56785378488BC02E85BD2066BE2C4\" name=\"Three Open\" />\n</root>"
  },
  {
    "path": "assets/data/loc.xml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <loc type=\"1\" id=\"0\" en=\"\" />\n  <loc type=\"1\" id=\"1\" de=\"Zufällig\" en=\"Roulette\" fr=\"Roulette\" ja=\"ルーレット\" cn=\"天选\" ko=\"무작위 규칙\" />\n  <loc type=\"1\" id=\"2\" de=\"Alle offen\" en=\"All Open\" fr=\"Toutes sur table\" ja=\"オールオープン\" cn=\"全明牌\" ko=\"모두 공개\" />\n  <loc type=\"1\" id=\"3\" de=\"Drei offen\" en=\"Three Open\" fr=\"Trois sur table\" ja=\"スリーオープン\" cn=\"三明牌\" ko=\"3장 공개\" />\n  <loc type=\"1\" id=\"4\" de=\"Verlängerung\" en=\"Sudden Death\" fr=\"Mort subite\" ja=\"サドンデス\" cn=\"不胜不休\" ko=\"연장전\" />\n  <loc type=\"1\" id=\"5\" de=\"Umkehrung\" en=\"Reverse\" fr=\"Inversion\" ja=\"リバース\" cn=\"逆转\" ko=\"역전\" />\n  <loc type=\"1\" id=\"6\" de=\"Assfresser\" en=\"Fallen Ace\" fr=\"Tueur d'as\" ja=\"エースキラー\" cn=\"王牌杀手\" ko=\"에이스 약화\" />\n  <loc type=\"1\" id=\"7\" de=\"Gleich\" en=\"Same\" fr=\"Identique\" ja=\"セイム\" cn=\"同数\" ko=\"동수\" />\n  <loc type=\"1\" id=\"8\" de=\"Plus\" en=\"Plus\" fr=\"Plus\" ja=\"プラス\" cn=\"加算\" ko=\"합산\" />\n  <loc type=\"1\" id=\"9\" de=\"Bonus\" en=\"Ascension\" fr=\"Bonus\" ja=\"タイプアセンド\" cn=\"同类强化\" ko=\"유형 강화\" />\n  <loc type=\"1\" id=\"10\" de=\"Malus\" en=\"Descension\" fr=\"Malus\" ja=\"タイプディセンド\" cn=\"同类弱化\" ko=\"유형 약화\" />\n  <loc type=\"1\" id=\"11\" de=\"Ordnung\" en=\"Order\" fr=\"Ordre\" ja=\"オーダー\" cn=\"秩序\" ko=\"순서대로\" />\n  <loc type=\"1\" id=\"12\" de=\"Chaos\" en=\"Chaos\" fr=\"Chaos\" ja=\"カオス\" cn=\"混乱\" ko=\"무작위 순서\" />\n  <loc type=\"1\" id=\"13\" de=\"Tauschen\" en=\"Swap\" fr=\"Échange\" ja=\"スワップ\" cn=\"交换\" ko=\"카드 교환\" />\n  <loc type=\"1\" id=\"14\" de=\"Zufallshand\" en=\"Random\" fr=\"Aléatoire\" ja=\"ランダムハンド\" cn=\"随机\" ko=\"무작위 패\" />\n  <loc type=\"1\" id=\"15\" de=\"Improvisation\" en=\"Draft\" fr=\"Draft\" ja=\"ドラフト\" cn=\"选拔\" ko=\"카드 선발\" />\n  <loc type=\"2\" id=\"0\" en=\"\" />\n  <loc type=\"2\" id=\"1\" de=\"Wilde Stämme\" en=\"Society\" fr=\"Homme-bête\" ja=\"獣人タイプ\" cn=\"兽人\" ko=\"수인\" />\n  <loc type=\"2\" id=\"2\" de=\"Primae\" en=\"Primal\" fr=\"Primordial\" ja=\"蛮神タイプ\" cn=\"蛮神\" ko=\"야만신\" />\n  <loc type=\"2\" id=\"3\" de=\"Bund\" en=\"Scion\" fr=\"Héritier\" ja=\"暁タイプ\" cn=\"拂晓\" ko=\"새벽\" />\n  <loc type=\"2\" id=\"4\" de=\"Garlear\" en=\"Garlean\" fr=\"Garlemaldais\" ja=\"帝国タイプ\" cn=\"帝国\" ko=\"제국\" />\n  <loc type=\"3\" id=\"0\" en=\"(hidden)\" />\n  <loc type=\"3\" id=\"1\" de=\"Dodo\" en=\"Dodo\" fr=\"dodo\" ja=\"ドードー\" cn=\"渡渡鸟\" ko=\"도도\" />\n  <loc type=\"3\" id=\"2\" de=\"Tomberry\" en=\"Tonberry\" fr=\"tomberry\" ja=\"トンベリ\" cn=\"冬贝利\" ko=\"톤베리\" />\n  <loc type=\"3\" id=\"3\" de=\"Sabotender\" en=\"Sabotender\" fr=\"pampa\" ja=\"サボテンダー\" cn=\"仙人刺\" ko=\"사보텐더\" />\n  <loc type=\"3\" id=\"4\" de=\"Spriggan\" en=\"Spriggan\" fr=\"spriggan\" ja=\"スプリガン\" cn=\"魔石精\" ko=\"스프리건\" />\n  <loc type=\"3\" id=\"5\" de=\"Pudding\" en=\"Pudding\" fr=\"pudding\" ja=\"プリン\" cn=\"软糊怪\" ko=\"푸딩\" />\n  <loc type=\"3\" id=\"6\" de=\"Bomber\" en=\"Bomb\" fr=\"bombo\" ja=\"ボム\" cn=\"爆弹怪\" ko=\"봄\" />\n  <loc type=\"3\" id=\"7\" de=\"Mandragora\" en=\"Mandragora\" fr=\"mandragore\" ja=\"マンドラゴラ\" cn=\"蔓德拉\" ko=\"만드라고라\" />\n  <loc type=\"3\" id=\"8\" de=\"Kobalos\" en=\"Coblyn\" fr=\"coblyn\" ja=\"コブラン\" cn=\"矿爬虫\" ko=\"코브란\" />\n  <loc type=\"3\" id=\"9\" de=\"Morbol\" en=\"Morbol\" fr=\"morbol\" ja=\"モルボル\" cn=\"魔界花\" ko=\"몰볼\" />\n  <loc type=\"3\" id=\"10\" de=\"Coeurl\" en=\"Coeurl\" fr=\"coeurl\" ja=\"クァール\" cn=\"长须豹\" ko=\"커얼\" />\n  <loc type=\"3\" id=\"11\" de=\"Ahriman\" en=\"Ahriman\" fr=\"ahriman\" ja=\"アーリマン\" cn=\"冥鬼之眼\" ko=\"아리만\" />\n  <loc type=\"3\" id=\"12\" de=\"Goobbue\" en=\"Goobbue\" fr=\"goobbue\" ja=\"グゥーブー\" cn=\"古菩猩猩\" ko=\"구부\" />\n  <loc type=\"3\" id=\"13\" de=\"Chocobo\" en=\"Chocobo\" fr=\"chocobo\" ja=\"チョコボ\" cn=\"陆行鸟\" ko=\"초코보\" />\n  <loc type=\"3\" id=\"14\" de=\"Amalj'aa\" en=\"Amalj'aa\" fr=\"Amalj'aa\" ja=\"アマルジャ\" cn=\"蜥蜴人\" ko=\"아말쟈\" />\n  <loc type=\"3\" id=\"15\" de=\"Ixal\" en=\"Ixal\" fr=\"Ixal\" ja=\"イクサル\" cn=\"鸟人\" ko=\"이크살\" />\n  <loc type=\"3\" id=\"16\" de=\"Sylphe\" en=\"Sylph\" fr=\"Sylphe\" ja=\"悪い子シルフ\" cn=\"坏妖精\" ko=\"말썽쟁이 실프\" />\n  <loc type=\"3\" id=\"17\" de=\"Kobold\" en=\"Kobold\" fr=\"Kobold\" ja=\"コボルド\" cn=\"地灵\" ko=\"코볼드\" />\n  <loc type=\"3\" id=\"18\" de=\"Sahagin\" en=\"Sahagin\" fr=\"Sahuagin\" ja=\"サハギン\" cn=\"鱼人\" ko=\"사하긴\" />\n  <loc type=\"3\" id=\"19\" de=\"Tataru\" en=\"Tataru Taru\" fr=\"Tataru\" ja=\"タタル・タル\" cn=\"塔塔露·塔露\" ko=\"타타루 타루\" />\n  <loc type=\"3\" id=\"20\" de=\"Mogry\" en=\"Moogle\" fr=\"Mog\" ja=\"モーグリ\" cn=\"莫古力\" ko=\"모그리\" />\n  <loc type=\"3\" id=\"21\" de=\"Sirene\" en=\"Siren\" fr=\"sirène\" ja=\"セイレーン\" cn=\"塞壬\" ko=\"세이렌\" />\n  <loc type=\"3\" id=\"22\" de=\"Ultros &amp; Typhon\" en=\"Ultros &amp; Typhon\" fr=\"Orthros &amp; Typhon\" ja=\"オルトロス＆テュポーン\" cn=\"奥尔特罗斯&amp;提丰\" ko=\"오르트로스 &amp; 티폰\" />\n  <loc type=\"3\" id=\"23\" de=\"Dämonenwand\" en=\"Demon Wall\" fr=\"mur démonique\" ja=\"デモンズウォール\" cn=\"恶魔墙\" ko=\"악마의 벽\" />\n  <loc type=\"3\" id=\"24\" de=\"Sukkubus\" en=\"Succubus\" fr=\"succube\" ja=\"サキュバス\" cn=\"梦魔\" ko=\"서큐버스\" />\n  <loc type=\"3\" id=\"25\" de=\"Chimära\" en=\"Chimera\" fr=\"chimère\" ja=\"キマイラ\" cn=\"奇美拉\" ko=\"키마이라\" />\n  <loc type=\"3\" id=\"26\" de=\"Blauer Drache\" en=\"Blue Dragon\" fr=\"dragon bleu\" ja=\"ブルードラゴン\" cn=\"蓝龙\" ko=\"푸른 용\" />\n  <loc type=\"3\" id=\"27\" de=\"Bugaal Ja\" en=\"Scarface Bugaal Ja\" fr=\"Bugaal Ja\" ja=\"美男のブガージャ\" cn=\"美男子布迦加\" ko=\"잘생긴 부가쟈\" />\n  <loc type=\"3\" id=\"28\" de=\"Momodi\" en=\"Momodi Modi\" fr=\"Momodi\" ja=\"モモディ・モディ\" cn=\"莫莫蒂·莫蒂\" ko=\"모모디 모디\" />\n  <loc type=\"3\" id=\"29\" de=\"Baderon\" en=\"Baderon Tenfingers\" fr=\"Baderon\" ja=\"十指のバデロン\" cn=\"十指巴德龙\" ko=\"열 손가락 바데론\" />\n  <loc type=\"3\" id=\"30\" de=\"Miounne\" en=\"Mother Miounne\" fr=\"Miounne\" ja=\"ミューヌ\" cn=\"缪恩\" ko=\"뮨\" />\n  <loc type=\"3\" id=\"31\" de=\"Livia sas Junius\" en=\"Livia sas Junius\" fr=\"Livia\" ja=\"リウィア・サス・ユニウス\" cn=\"莉维亚·萨斯·尤尼乌斯\" ko=\"리위아 사스 유니우스\" />\n  <loc type=\"3\" id=\"32\" de=\"Rhitahtyn sas Arvina\" en=\"Rhitahtyn sas Arvina\" fr=\"Rhitahtyn\" ja=\"リットアティン・サス・アルヴィナ\" cn=\"里塔提恩·萨斯·阿维纳\" ko=\"리트아틴 사스 아르비나\" />\n  <loc type=\"3\" id=\"33\" de=\"Biggs &amp; Wedge\" en=\"Biggs &amp; Wedge\" fr=\"Biggs &amp; Wedge\" ja=\"ビッグス＆ウェッジ\" cn=\"比格斯&amp;魏吉\" ko=\"빅스 &amp; 웨지\" />\n  <loc type=\"3\" id=\"34\" de=\"Gerolt\" en=\"Gerolt\" fr=\"Gerolt\" ja=\"ゲロルト・ブラックソーン\" cn=\"盖罗尔特·布拉克索恩\" ko=\"게롤트 블랙손\" />\n  <loc type=\"3\" id=\"35\" de=\"Frixio\" en=\"Frixio\" fr=\"Frixio\" ja=\"フリクシオ\" cn=\"弗里克希奥\" ko=\"프리크시오\" />\n  <loc type=\"3\" id=\"36\" de=\"Mutagenix\" en=\"Mutamix Bubblypots\" fr=\"Mutamix\" ja=\"ミュタミクス\" cn=\"缪塔米克斯\" ko=\"뮤타믹스\" />\n  <loc type=\"3\" id=\"37\" de=\"Memeroon\" en=\"Memeroon\" fr=\"Memeroon\" ja=\"メメルン\" cn=\"梅梅卢恩\" ko=\"메메룬\" />\n  <loc type=\"3\" id=\"38\" de=\"Behemoth\" en=\"Behemoth\" fr=\"béhémoth\" ja=\"ベヒーモス\" cn=\"贝希摩斯\" ko=\"베히모스\" />\n  <loc type=\"3\" id=\"39\" de=\"Gilgamesch &amp; Enkidu\" en=\"Gilgamesh &amp; Enkidu\" fr=\"Gilgamesh &amp; Enkidu\" ja=\"ギルガメッシュ＆エンキドゥ\" cn=\"吉尔伽美什&amp;恩奇都\" ko=\"길가메시 &amp; 엔키두\" />\n  <loc type=\"3\" id=\"40\" de=\"Ifrit\" en=\"Ifrit\" fr=\"Ifrit\" ja=\"イフリート\" cn=\"伊弗利特\" ko=\"이프리트\" />\n  <loc type=\"3\" id=\"41\" de=\"Titan\" en=\"Titan\" fr=\"Titan\" ja=\"タイタン\" cn=\"泰坦\" ko=\"타이탄\" />\n  <loc type=\"3\" id=\"42\" de=\"Garuda\" en=\"Garuda\" fr=\"Garuda\" ja=\"ガルーダ\" cn=\"迦楼罗\" ko=\"가루다\" />\n  <loc type=\"3\" id=\"43\" de=\"Mogul Mog\" en=\"Good King Moggle Mog XII\" fr=\"Moggle Mog XII\" ja=\"善王モグル・モグXII世\" cn=\"贤王莫古尔·莫古十二世\" ko=\"선왕 모그루 모그 XII세\" />\n  <loc type=\"3\" id=\"44\" de=\"Raya-O-Senna &amp; A-Ruhn-Senna\" en=\"Raya-O-Senna &amp; A-Ruhn-Senna\" fr=\"Raya-O-Senna &amp; A-Ruhn-Senna\" ja=\"ラヤ・オ・センナ＆ア・ルン・センナ\" cn=\"拉雅·奥·神纳&amp;阿·卢恩·神纳\" ko=\"라야 오 센나 &amp; 아 룬 센나\" />\n  <loc type=\"3\" id=\"45\" de=\"Godbert Manderville\" en=\"Godbert Manderville\" fr=\"Godbert\" ja=\"ゴッドベルト・マンダヴィル\" cn=\"戈德伯特·曼德维尔\" ko=\"고드베르트 맨더빌\" />\n  <loc type=\"3\" id=\"46\" de=\"Thancred\" en=\"Thancred\" fr=\"Thancred\" ja=\"サンクレッド\" cn=\"桑克瑞德\" ko=\"산크레드\" />\n  <loc type=\"3\" id=\"47\" de=\"Nero tol Scaeva\" en=\"Nero tol Scaeva\" fr=\"Nero\" ja=\"ネロ・トル・スカエウァ\" cn=\"尼禄·托尔·斯卡艾瓦\" ko=\"네로 톨 스카이와\" />\n  <loc type=\"3\" id=\"48\" de=\"Papalymo &amp; Yda\" en=\"Papalymo &amp; Yda\" fr=\"Papalymo &amp; Yda\" ja=\"パパリモ＆イダ\" cn=\"帕帕力莫&amp;伊达\" ko=\"파파리모 &amp; 이다\" />\n  <loc type=\"3\" id=\"49\" de=\"Y'shtola\" en=\"Y'shtola\" fr=\"Y'shtola\" ja=\"ヤ・シュトラ\" cn=\"雅·修特拉\" ko=\"야슈톨라\" />\n  <loc type=\"3\" id=\"50\" de=\"Urianger\" en=\"Urianger\" fr=\"Urianger\" ja=\"ウリエンジェ・オギュレ\" cn=\"于里昂热·奥居雷\" ko=\"위리앙제 오귀레\" />\n  <loc type=\"3\" id=\"51\" de=\"Ultima-Waffe\" en=\"Ultima Weapon\" fr=\"Ultima Arma\" ja=\"アルテマウェポン\" cn=\"究极神兵\" ko=\"알테마 웨폰\" />\n  <loc type=\"3\" id=\"52\" de=\"Odin\" en=\"Odin\" fr=\"Odin\" ja=\"オーディン\" cn=\"奥丁\" ko=\"오딘\" />\n  <loc type=\"3\" id=\"53\" de=\"Ramuh\" en=\"Ramuh\" fr=\"Ramuh\" ja=\"ラムウ\" cn=\"拉姆\" ko=\"라무\" />\n  <loc type=\"3\" id=\"54\" de=\"Leviathan\" en=\"Leviathan\" fr=\"Léviathan\" ja=\"リヴァイアサン\" cn=\"利维亚桑\" ko=\"리바이어선\" />\n  <loc type=\"3\" id=\"55\" de=\"Shiva\" en=\"Shiva\" fr=\"Shiva\" ja=\"シヴァ\" cn=\"希瓦\" ko=\"시바\" />\n  <loc type=\"3\" id=\"56\" de=\"Minfilia\" en=\"Minfilia\" fr=\"Minfilia\" ja=\"ミンフィリア\" cn=\"敏菲利亚\" ko=\"민필리아\" />\n  <loc type=\"3\" id=\"57\" de=\"Lahabrea\" en=\"Lahabrea\" fr=\"Lahabrea\" ja=\"アシエン・ラハブレア\" cn=\"无影拉哈布雷亚\" ko=\"아씨엔 라하브레아\" />\n  <loc type=\"3\" id=\"58\" de=\"Cid Garlond\" en=\"Cid Garlond\" fr=\"Cid\" ja=\"シド・ガーロンド\" cn=\"西德·加隆德\" ko=\"시드 갈론드\" />\n  <loc type=\"3\" id=\"59\" de=\"Alphinaud &amp; Alisaie\" en=\"Alphinaud &amp; Alisaie\" fr=\"Alphinaud &amp; Alisaie\" ja=\"アルフィノ＆アリゼー\" cn=\"阿尔菲诺&amp;阿莉塞\" ko=\"알피노 &amp; 알리제\" />\n  <loc type=\"3\" id=\"60\" de=\"Louisoix Leveilleur\" en=\"Louisoix Leveilleur\" fr=\"Louisoix\" ja=\"ルイゾワ・ルヴェユール\" cn=\"路易索瓦·莱韦耶勒尔\" ko=\"루이수아 르베유르\" />\n  <loc type=\"3\" id=\"61\" de=\"Bahamut\" en=\"Bahamut\" fr=\"Bahamut\" ja=\"バハムート\" cn=\"巴哈姆特\" ko=\"바하무트\" />\n  <loc type=\"3\" id=\"62\" de=\"Hildibrand &amp; Nashu\" en=\"Hildibrand &amp; Nashu Mhakaracca\" fr=\"Hildibrand &amp; Nashu Mhakaracca\" ja=\"ヒルディブランド＆ナシュ・マカラッカ\" cn=\"希尔迪布兰德&amp;娜修·玛卡拉卡\" ko=\"힐디브랜드 &amp; 나슈 마카라카\" />\n  <loc type=\"3\" id=\"63\" de=\"Nanamo\" en=\"Nanamo Ul Namo\" fr=\"Nanamo\" ja=\"ナナモ・ウル・ナモ\" cn=\"娜娜莫·乌尔·娜莫\" ko=\"나나모 울 나모\" />\n  <loc type=\"3\" id=\"64\" de=\"Gaius van Baelsar\" en=\"Gaius van Baelsar\" fr=\"Gaius\" ja=\"ガイウス・ヴァン・バエサル\" cn=\"盖乌斯·范·巴埃萨\" ko=\"가이우스 반 바일사르\" />\n  <loc type=\"3\" id=\"65\" de=\"Merlwyb\" en=\"Merlwyb Bloefhiswyn\" fr=\"Merlwyb\" ja=\"メルウィブ・ブルーフィスウィン\" cn=\"梅尔维布·布鲁菲斯维因\" ko=\"멜위브 블루피쉰\" />\n  <loc type=\"3\" id=\"66\" de=\"Kan-E-Senna\" en=\"Kan-E-Senna\" fr=\"Kan-E-Senna\" ja=\"カヌ・エ・センナ\" cn=\"嘉恩·艾·神纳\" ko=\"카느 에 센나\" />\n  <loc type=\"3\" id=\"67\" de=\"Raubahn\" en=\"Raubahn Aldynn\" fr=\"Raubahn\" ja=\"ラウバーン・アルディン\" cn=\"劳班·阿尔丁\" ko=\"라우반 알딘\" />\n  <loc type=\"3\" id=\"68\" de=\"Krieger des Lichts\" en=\"Warrior of Light\" fr=\"Guerrier de la Lumière\" ja=\"ウォーリア・オブ・ライト\" cn=\"光之战士\" ko=\"빛의 전사\" />\n  <loc type=\"3\" id=\"69\" de=\"Firion\" en=\"Firion\" fr=\"Firion\" ja=\"フリオニール\" cn=\"弗利奥尼尔\" ko=\"프리오닐\" />\n  <loc type=\"3\" id=\"70\" de=\"Zwiebelritter\" en=\"Onion Knight\" fr=\"chevalier Oignon\" ja=\"オニオンナイト\" cn=\"洋葱剑士\" ko=\"양파 기사\" />\n  <loc type=\"3\" id=\"71\" de=\"Cecil Harvey\" en=\"Cecil Harvey\" fr=\"Cécil Harvey\" ja=\"セシル・ハーヴィ\" cn=\"塞西尔·哈维\" ko=\"세실 하비\" />\n  <loc type=\"3\" id=\"72\" de=\"Bartz Klauser\" en=\"Bartz Klauser\" fr=\"Bartz\" ja=\"バッツ・クラウザー\" cn=\"巴兹·克劳泽\" ko=\"바츠 클라우저\" />\n  <loc type=\"3\" id=\"73\" de=\"Terra Branford\" en=\"Terra Branford\" fr=\"Terra\" ja=\"ティナ・ブランフォード\" cn=\"蒂娜·布兰福德\" ko=\"티나 브랜포드\" />\n  <loc type=\"3\" id=\"74\" de=\"Cloud Strife\" en=\"Cloud Strife\" fr=\"Cloud\" ja=\"クラウド・ストライフ\" cn=\"克劳德·斯特莱夫\" ko=\"클라우드 스트라이프\" />\n  <loc type=\"3\" id=\"75\" de=\"Squall Leonhart\" en=\"Squall Leonhart\" fr=\"Squall\" ja=\"スコール・レオンハート\" cn=\"斯考尔·莱昂哈特\" ko=\"스콜 레온하트\" />\n  <loc type=\"3\" id=\"76\" de=\"Zidane Tribal\" en=\"Zidane Tribal\" fr=\"Djidane\" ja=\"ジタン・トライバル\" cn=\"吉坦·特莱巴尔\" ko=\"지탄 트라이벌\" />\n  <loc type=\"3\" id=\"77\" de=\"Tidus\" en=\"Tidus\" fr=\"Tidus\" ja=\"ティーダ\" cn=\"提达\" ko=\"티다\" />\n  <loc type=\"3\" id=\"78\" de=\"Shantotto\" en=\"Shantotto\" fr=\"Shantotto\" ja=\"シャントット\" cn=\"香托托\" ko=\"샨토토\" />\n  <loc type=\"3\" id=\"79\" de=\"Vaan\" en=\"Vaan\" fr=\"Vaan\" ja=\"ヴァン\" cn=\"梵\" ko=\"반\" />\n  <loc type=\"3\" id=\"80\" de=\"Lightning\" en=\"Lightning\" fr=\"Lightning\" ja=\"ライトニング\" cn=\"雷光\" ko=\"라이트닝\" />\n  <loc type=\"3\" id=\"81\" de=\"Geira-Katze\" en=\"Gaelicat\" fr=\"gaélichat\" ja=\"ゲイラキャット\" cn=\"风筝猫\" ko=\"게일리캣\" />\n  <loc type=\"3\" id=\"82\" de=\"Vanu Vanu\" en=\"Vanu Vanu\" fr=\"Vanu Vanu\" ja=\"バヌバヌ\" cn=\"瓦努族\" ko=\"바누바누\" />\n  <loc type=\"3\" id=\"83\" de=\"Gnath\" en=\"Gnath\" fr=\"Gnathe\" ja=\"グナース\" cn=\"骨颌族\" ko=\"그나스\" />\n  <loc type=\"3\" id=\"84\" de=\"Yugiri Nebelschleicher\" en=\"Yugiri Mistwalker\" fr=\"Yugiri\" ja=\"霧隠のユウギリ\" cn=\"雾中行者夕雾\" ko=\"안개의 유우기리\" />\n  <loc type=\"3\" id=\"85\" de=\"Chocomoppel\" en=\"Fat Chocobo\" fr=\"gros chocobo\" ja=\"でぶチョコボ\" cn=\"胖陆行鸟\" ko=\"뚱보초코보\" />\n  <loc type=\"3\" id=\"86\" de=\"Greif\" en=\"Griffin\" fr=\"griffon\" ja=\"グリフィン\" cn=\"狮鹫\" ko=\"그리핀\" />\n  <loc type=\"3\" id=\"87\" de=\"Tioman\" en=\"Tioman\" fr=\"Tioman\" ja=\"ティオマン\" cn=\"刁曼\" ko=\"티오만\" />\n  <loc type=\"3\" id=\"88\" de=\"Estinien\" en=\"Estinien\" fr=\"Estinien\" ja=\"エスティニアン\" cn=\"埃斯蒂尼安\" ko=\"에스티니앙\" />\n  <loc type=\"3\" id=\"89\" de=\"Lucia goe Junius\" en=\"Lucia goe Junius\" fr=\"Lucia\" ja=\"ルキア\" cn=\"露琪亚\" ko=\"루키아\" />\n  <loc type=\"3\" id=\"90\" de=\"Ysayle\" en=\"Ysayle\" fr=\"Ysayle\" ja=\"イゼル\" cn=\"伊塞勒\" ko=\"이젤\" />\n  <loc type=\"3\" id=\"91\" de=\"Hilda\" en=\"Hilda\" fr=\"Hilda\" ja=\"ヒルダ\" cn=\"希尔达\" ko=\"힐다\" />\n  <loc type=\"3\" id=\"92\" de=\"Matoya\" en=\"Matoya\" fr=\"Matoya\" ja=\"マトーヤ\" cn=\"玛托雅\" ko=\"마토야\" />\n  <loc type=\"3\" id=\"93\" de=\"Graf Edmont Fortemps\" en=\"Count Edmont de Fortemps\" fr=\"Edmont de Fortemps\" ja=\"エドモン・ド・フォルタン\" cn=\"埃德蒙·德·福尔唐\" ko=\"에드몽 드 포르탕\" />\n  <loc type=\"3\" id=\"94\" de=\"Byblos\" en=\"Byblos\" fr=\"Byblos\" ja=\"ビブロス\" cn=\"比布鲁斯\" ko=\"비블로스\" />\n  <loc type=\"3\" id=\"95\" de=\"Haurchefant\" en=\"Haurchefant\" fr=\"Haurchefant\" ja=\"オルシュファン\" cn=\"奥尔什方\" ko=\"오르슈팡\" />\n  <loc type=\"3\" id=\"96\" de=\"Aymeric\" en=\"Aymeric\" fr=\"Aymeric\" ja=\"アイメリク\" cn=\"艾默里克\" ko=\"아이메리크\" />\n  <loc type=\"3\" id=\"97\" de=\"Ravana\" en=\"Ravana\" fr=\"Ravana\" ja=\"ラーヴァナ\" cn=\"罗波那\" ko=\"라바나\" />\n  <loc type=\"3\" id=\"98\" de=\"Bismarck\" en=\"Bismarck\" fr=\"Bismarck\" ja=\"ビスマルク\" cn=\"俾斯麦\" ko=\"비스마르크\" />\n  <loc type=\"3\" id=\"99\" de=\"Nidhogg\" en=\"Nidhogg\" fr=\"Nidhogg\" ja=\"ニーズヘッグ\" cn=\"尼德霍格\" ko=\"니드호그\" />\n  <loc type=\"3\" id=\"100\" de=\"Midgardsormr\" en=\"Midgardsormr\" fr=\"Midgardsormr\" ja=\"ミドガルズオルム\" cn=\"尘世幻龙\" ko=\"미드가르드오름\" />\n  <loc type=\"3\" id=\"101\" de=\"Glotzauge\" en=\"Deepeye\" fr=\"oculus\" ja=\"ディープアイ\" cn=\"深瞳\" ko=\"볼록눈\" />\n  <loc type=\"3\" id=\"102\" de=\"Archaeornis\" en=\"Archaeornis\" fr=\"archéornis\" ja=\"アルケオーニス\" cn=\"古鸟\" ko=\"아르케오니스\" />\n  <loc type=\"3\" id=\"103\" de=\"Paissa\" en=\"Paissa\" fr=\"païssa\" ja=\"パイッサ\" cn=\"猴面雀\" ko=\"파이싸\" />\n  <loc type=\"3\" id=\"104\" de=\"Dhalmel\" en=\"Dhalmel\" fr=\"dhalmel\" ja=\"ダルメル\" cn=\"长颈驼\" ko=\"달멜\" />\n  <loc type=\"3\" id=\"105\" de=\"Bandersnatch\" en=\"Bandersnatch\" fr=\"bandersnatch\" ja=\"バンダースナッチ\" cn=\"斑攫兽\" ko=\"밴더스내치\" />\n  <loc type=\"3\" id=\"106\" de=\"Kriecher\" en=\"Crawler\" fr=\"chenille\" ja=\"クロウラー\" cn=\"毛爬虫\" ko=\"누에\" />\n  <loc type=\"3\" id=\"107\" de=\"Poroggo\" en=\"Poroggo\" fr=\"poroggo\" ja=\"ポロッゴ\" cn=\"智蛙\" ko=\"포록고\" />\n  <loc type=\"3\" id=\"108\" de=\"Vedrfölnir\" en=\"Vedrfolnir\" fr=\"Vedrfolnir\" ja=\"ヴェズルフェルニル\" cn=\"维德佛尔尼尔\" ko=\"베드르폴니르\" />\n  <loc type=\"3\" id=\"109\" de=\"Coeurl-Königin\" en=\"Coeurlregina\" fr=\"Regina coeurl\" ja=\"クァールレギナ\" cn=\"长须豹女王\" ko=\"커얼레기나\" />\n  <loc type=\"3\" id=\"110\" de=\"Progenitrix\" en=\"Progenitrix\" fr=\"Maman bombo\" ja=\"マザーボム\" cn=\"爆弹之母\" ko=\"엄마 봄\" />\n  <loc type=\"3\" id=\"111\" de=\"Belladonna\" en=\"Belladonna\" fr=\"Belladone\" ja=\"ベラドンナ\" cn=\"剧毒美人\" ko=\"벨라돈나\" />\n  <loc type=\"3\" id=\"112\" de=\"Echidna\" en=\"Echidna\" fr=\"Echidna\" ja=\"エキドナ\" cn=\"艾奇德娜\" ko=\"에키드나\" />\n  <loc type=\"3\" id=\"113\" de=\"Pipin Tarupin\" en=\"Pipin Tarupin\" fr=\"Pipin Tarupin\" ja=\"ピピン・タルピン\" cn=\"皮平·塔鲁平\" ko=\"피핀 타루핀\" />\n  <loc type=\"3\" id=\"114\" de=\"Julyan Manderville\" en=\"Julyan Manderville\" fr=\"Julyan Manderville\" ja=\"ジュリアン・マンダヴィル\" cn=\"茱莉安·曼德维尔\" ko=\"줄리안 맨더빌\" />\n  <loc type=\"3\" id=\"115\" de=\"Moglin\" en=\"Moglin\" fr=\"Moglin\" ja=\"モグリン\" cn=\"莫古灵\" ko=\"모그린\" />\n  <loc type=\"3\" id=\"116\" de=\"Charibert\" en=\"Charibert\" fr=\"Charibert\" ja=\"シャリベル\" cn=\"沙里贝尔\" ko=\"샤리베르\" />\n  <loc type=\"3\" id=\"117\" de=\"Rundblox\" en=\"Roundrox\" fr=\"Roundrox\" ja=\"ラウンドロクス\" cn=\"朗德洛克斯\" ko=\"라운드록스\" />\n  <loc type=\"3\" id=\"118\" de=\"Señor Kaktor\" en=\"Senor Sabotender\" fr=\"Señor Pampa\" ja=\"セニョール・サボテンダー\" cn=\"仙人刺先生\" ko=\"세뇨르 사보텐더\" />\n  <loc type=\"3\" id=\"119\" de=\"Regula van Hydrus\" en=\"Regula van Hydrus\" fr=\"Regula van Hydrus\" ja=\"レグラ・ヴァン・ヒュドルス\" cn=\"雷古拉·范·休著斯\" ko=\"레굴라 반 히드루스\" />\n  <loc type=\"3\" id=\"120\" de=\"Papst Thordan\" en=\"Archbishop Thordan VII\" fr=\"Thordan VII\" ja=\"トールダン7世\" cn=\"托尔丹七世\" ko=\"토르당 7세\" />\n  <loc type=\"3\" id=\"121\" de=\"Honoroit\" en=\"Honoroit\" fr=\"Honoroit\" ja=\"オノロワ\" cn=\"奥诺鲁瓦\" ko=\"오노루아\" />\n  <loc type=\"3\" id=\"122\" de=\"Grauer Felsen &amp; Coultenet\" en=\"Hoary Boulder &amp; Coultenet\" fr=\"Hoary Boulder &amp; Coultenet\" ja=\"ホーリー・ボルダー＆クルトゥネ\" cn=\"赫利·博尔达&amp;库尔特内\" ko=\"빛바랜 바위 &amp; 쿨테네\" />\n  <loc type=\"3\" id=\"123\" de=\"Brachiosaurus\" en=\"Brachiosaur\" fr=\"brachiosaure\" ja=\"ブラキオレイドス\" cn=\"腕龙\" ko=\"브라키오레이도스\" />\n  <loc type=\"3\" id=\"124\" de=\"Dunkelschuppe\" en=\"Darkscale\" fr=\"Sombrécaille\" ja=\"ダークスケール\" cn=\"暗鳞黑龙\" ko=\"검은미늘\" />\n  <loc type=\"3\" id=\"125\" de=\"Fenrir\" en=\"Fenrir\" fr=\"Fenrir\" ja=\"フェンリル\" cn=\"芬里尔\" ko=\"펜리르\" />\n  <loc type=\"3\" id=\"126\" de=\"Kraken\" en=\"Kraken\" fr=\"Kraken\" ja=\"クラーケン\" cn=\"克拉肯\" ko=\"크라켄\" />\n  <loc type=\"3\" id=\"127\" de=\"Statthalter\" en=\"Vicegerent to the Warden\" fr=\"adjoint de la Gardienne\" ja=\"アーゼマヴァイスジェレント\" cn=\"审理神代言者\" ko=\"아제마 교황\" />\n  <loc type=\"3\" id=\"128\" de=\"Molaa Ja Ja\" en=\"Manxome Molaa Ja Ja\" fr=\"Molaa Ja Ja\" ja=\"光鱗のモラージャジャ\" cn=\"光鳞兄弟莫拉加加\" ko=\"빛나는 비늘의 몰라쟈쟈\" />\n  <loc type=\"3\" id=\"129\" de=\"Ferdiad\" en=\"Ferdiad\" fr=\"Ferdiad\" ja=\"フェルディア\" cn=\"弗迪亚\" ko=\"페르디아\" />\n  <loc type=\"3\" id=\"130\" de=\"Calcobrina\" en=\"Calcabrina\" fr=\"Calcabrina\" ja=\"カルコブリーナ\" cn=\"卡尔克布莉娜\" ko=\"칼카브리나\" />\n  <loc type=\"3\" id=\"131\" de=\"Kuribu\" en=\"Kuribu\" fr=\"Kuribu\" ja=\"クリブ\" cn=\"基路伯\" ko=\"쿠리부\" />\n  <loc type=\"3\" id=\"132\" de=\"Phlegethon\" en=\"Phlegethon\" fr=\"Phlégéthon\" ja=\"ティターン\" cn=\"提坦\" ko=\"티탄\" />\n  <loc type=\"3\" id=\"133\" de=\"Artoirel\" en=\"Artoirel de Fortemps\" fr=\"Artoirel\" ja=\"アルトアレール・ド・フォルタン\" cn=\"阿图瓦雷尔·德·福尔唐\" ko=\"아르투아렐 드 포르탕\" />\n  <loc type=\"3\" id=\"134\" de=\"Emmanellain\" en=\"Emmanellain de Fortemps\" fr=\"Emmanellain\" ja=\"エマネラン・ド・フォルタン\" cn=\"埃马内兰·德·福尔唐\" ko=\"에마넬랭 드 포르탕\" />\n  <loc type=\"3\" id=\"135\" de=\"Xande\" en=\"Xande\" fr=\"Xande\" ja=\"ザンデ\" cn=\"赞德\" ko=\"잔데\" />\n  <loc type=\"3\" id=\"136\" de=\"Brutalus\" en=\"Brute Justice\" fr=\"Justicier\" ja=\"ブルートジャスティス\" cn=\"残暴正义号\" ko=\"포악한 심판자\" />\n  <loc type=\"3\" id=\"137\" de=\"Sephirot\" en=\"Sephirot\" fr=\"Sephirot\" ja=\"セフィロト\" cn=\"萨菲洛特\" ko=\"세피로트\" />\n  <loc type=\"3\" id=\"138\" de=\"F'lhaminn\" en=\"F'lhaminn\" fr=\"F'lhaminn\" ja=\"フ・ラミン\" cn=\"芙·拉敏\" ko=\"프라민\" />\n  <loc type=\"3\" id=\"139\" de=\"Vidofnir\" en=\"Vidofnir\" fr=\"Vidofnir\" ja=\"ヴィゾーヴニル\" cn=\"维德弗尼尔\" ko=\"비도프니르\" />\n  <loc type=\"3\" id=\"140\" de=\"Wolke der Dunkelheit\" en=\"Cloud of Darkness\" fr=\"Nuage de Ténèbres\" ja=\"暗闇の雲\" cn=\"暗黑之云\" ko=\"어둠의 구름\" />\n  <loc type=\"3\" id=\"141\" de=\"Lolorito Nanarito\" en=\"Lolorito Nanarito\" fr=\"Lolorito Nanarito\" ja=\"ロロリト・ナナリト\" cn=\"罗罗力特·纳纳力特\" ko=\"로로리토 나나리토\" />\n  <loc type=\"3\" id=\"142\" de=\"Gibrillont\" en=\"Gibrillont\" fr=\"Gibrillont\" ja=\"ジブリオン\" cn=\"吉布里隆\" ko=\"지브리옹\" />\n  <loc type=\"3\" id=\"143\" de=\"Laniaitte de Haillenarte\" en=\"Laniaitte de Haillenarte\" fr=\"Laniaitte de Haillenarte\" ja=\"ラニエット・ド・アインハルト\" cn=\"拉妮艾特·德·艾因哈特\" ko=\"라니에트 드 아유나르트\" />\n  <loc type=\"3\" id=\"144\" de=\"Rhoswen\" en=\"Rhoswen\" fr=\"Rhoswen\" ja=\"ローズウェン\" cn=\"罗丝温\" ko=\"로즈웬\" />\n  <loc type=\"3\" id=\"145\" de=\"Carvallain de Gorgagne\" en=\"Carvallain de Gorgagne\" fr=\"Carvallain de Gorgagne\" ja=\"カルヴァラン・ド・ゴルガニュ\" cn=\"卡尔瓦兰·德·戈尔加涅\" ko=\"카르발랭 드 고르가뉴\" />\n  <loc type=\"3\" id=\"146\" de=\"Kal Myhk\" en=\"Kal Myhk\" fr=\"Kal Myhk\" ja=\"カル・ミーク\" cn=\"卡尔·米克\" ko=\"칼 미크\" />\n  <loc type=\"3\" id=\"147\" de=\"Waukkeon\" en=\"Waukkeon\" fr=\"waukkeon\" ja=\"ワウッケオン\" cn=\"招雷巨蛇\" ko=\"와우케온\" />\n  <loc type=\"3\" id=\"148\" de=\"Kurator\" en=\"Curator\" fr=\"Conservateur\" ja=\"キュレーター\" cn=\"博物总管\" ko=\"전시 책임자\" />\n  <loc type=\"3\" id=\"149\" de=\"Rauschebart\" en=\"Mistbeard\" fr=\"Barbe-Brume\" ja=\"霧髭\" cn=\"大海盗雾须王\" ko=\"안개수염\" />\n  <loc type=\"3\" id=\"150\" de=\"Unei &amp; Doga\" en=\"Unei &amp; Doga\" fr=\"Unéi &amp; Doga\" ja=\"ウネ＆ドーガ\" cn=\"乌内&amp;多加\" ko=\"우네 &amp; 도가\" />\n  <loc type=\"3\" id=\"151\" de=\"Tiamat\" en=\"Tiamat\" fr=\"Tiamat\" ja=\"ティアマット\" cn=\"提亚马特\" ko=\"티아마트\" />\n  <loc type=\"3\" id=\"152\" de=\"Calofisteri\" en=\"Calofisteri\" fr=\"Calofisteri\" ja=\"カロフィステリ\" cn=\"卡洛菲斯提莉\" ko=\"칼로피스테리\" />\n  <loc type=\"3\" id=\"153\" de=\"Hraesvelgr\" en=\"Hraesvelgr\" fr=\"Hraesvelgr\" ja=\"フレースヴェルグ\" cn=\"赫拉斯瓦尔格\" ko=\"흐레스벨그\" />\n  <loc type=\"3\" id=\"154\" de=\"Apkallu\" en=\"Apkallu\" fr=\"apkallu\" ja=\"アプカル\" cn=\"碧企鹅\" ko=\"아프칼루\" />\n  <loc type=\"3\" id=\"155\" de=\"Kolibri\" en=\"Colibri\" fr=\"colibri\" ja=\"コリブリ\" cn=\"蜂鸟\" ko=\"콜리브리\" />\n  <loc type=\"3\" id=\"156\" de=\"Magitek-Todesklaue\" en=\"Magitek Death Claw\" fr=\"griffe de mort magitek\" ja=\"魔導デスクロー\" cn=\"魔导死亡爪\" ko=\"마도 죽음손아귀\" />\n  <loc type=\"3\" id=\"157\" de=\"Flüssige Flamme\" en=\"Liquid Flame\" fr=\"flamme liquide\" ja=\"リクイドフレイム\" cn=\"液态火焰\" ko=\"액체 불꽃\" />\n  <loc type=\"3\" id=\"158\" de=\"Schaf\" en=\"Lost Lamb\" fr=\"agneau égaré\" ja=\"シープ\" cn=\"迷途羊羔\" ko=\"양\" />\n  <loc type=\"3\" id=\"159\" de=\"Mogry-Postbote\" en=\"Delivery Moogle\" fr=\"Mog postier\" ja=\"レターモーグリ\" cn=\"邮差莫古力\" ko=\"배달부 모그리\" />\n  <loc type=\"3\" id=\"160\" de=\"Magitek-Colossus\" en=\"Magitek Colossus\" fr=\"colosse magitek\" ja=\"魔導コロッサス\" cn=\"魔导巨兵\" ko=\"마도 콜로서스\" />\n  <loc type=\"3\" id=\"161\" de=\"Strix\" en=\"Strix\" fr=\"Strix\" ja=\"ストリックス\" cn=\"博学林鸮\" ko=\"스트릭스\" />\n  <loc type=\"3\" id=\"162\" de=\"Tozol Huatotl\" en=\"Tozol Huatotl\" fr=\"Tozol Huatotl\" ja=\"晴嵐のトゾル・ファトトル\" cn=\"晴天霹雳托佐尔·法托特尔\" ko=\"청풍의 토졸 후아토틀\" />\n  <loc type=\"3\" id=\"163\" de=\"Prim-Alexander\" en=\"Alexander Prime\" fr=\"Primo-Alexander\" ja=\"アレキサンダー・プライム\" cn=\"至尊亚历山大\" ko=\"알렉산더 프라임\" />\n  <loc type=\"3\" id=\"164\" de=\"Brendt, Brennan &amp; Bremondt\" en=\"Brendt, Brennan, &amp; Bremondt\" fr=\"Brendt, Brennan &amp; Bremondt\" ja=\"気さくな三兄弟\" cn=\"直爽的三兄弟\" ko=\"넉살 좋은 삼형제\" />\n  <loc type=\"3\" id=\"165\" de=\"Thancred (Heavensward)\" en=\"Heavensward Thancred\" fr=\"Thancred (Heavensward)\" ja=\"サンクレッド（蒼天）\" cn=\"桑克瑞德（苍穹）\" ko=\"산크레드(창천의 이슈가르드)\" />\n  <loc type=\"3\" id=\"166\" de=\"Y'shtola (Heavensward)\" en=\"Heavensward Y'shtola\" fr=\"Y'shtola (Heavensward)\" ja=\"ヤ・シュトラ（蒼天）\" cn=\"雅·修特拉（苍穹）\" ko=\"야슈톨라(창천의 이슈가르드)\" />\n  <loc type=\"3\" id=\"167\" de=\"Nael van Darnus\" en=\"Nael van Darnus\" fr=\"Nael van Darnus\" ja=\"ネール・ヴァン・ダーナス\" cn=\"奈尔·范·达纳斯\" ko=\"넬 반 다르누스\" />\n  <loc type=\"3\" id=\"168\" de=\"Sophia\" en=\"Sophia\" fr=\"Sophia\" ja=\"ソフィア\" cn=\"索菲娅\" ko=\"소피아\" />\n  <loc type=\"3\" id=\"169\" de=\"Opo-Opo\" en=\"Opo-opo\" fr=\"opo-opo\" ja=\"オポオポ\" cn=\"奥猴\" ko=\"오포오포\" />\n  <loc type=\"3\" id=\"170\" de=\"Adaman-Taimai\" en=\"Adamantoise\" fr=\"adamankhélone\" ja=\"アダマンタス\" cn=\"精金龟\" ko=\"금강거북\" />\n  <loc type=\"3\" id=\"171\" de=\"Magitek-Frontbrecher\" en=\"Magitek Vanguard\" fr=\"avant-garde magitek\" ja=\"魔導ヴァンガード\" cn=\"魔导先锋\" ko=\"마도 뱅가드\" />\n  <loc type=\"3\" id=\"172\" de=\"Magitek-Kanonenschiff\" en=\"Magitek Gunship\" fr=\"vaisseau magitek\" ja=\"魔導ガンシップ\" cn=\"魔导炮艇\" ko=\"마도 건십\" />\n  <loc type=\"3\" id=\"173\" de=\"Goldhäschen\" en=\"Gold Saucer Attendant\" fr=\"Abbelina\" ja=\"バニー\" cn=\"兔女郎\" ko=\"토끼 소녀\" />\n  <loc type=\"3\" id=\"174\" de=\"Lavaskorpion\" en=\"Lava Scorpion\" fr=\"scorpion de lave\" ja=\"ラーヴァスコーピオン\" cn=\"熔岩蝎\" ko=\"용암 전갈\" />\n  <loc type=\"3\" id=\"175\" de=\"Magitek-Prädator\" en=\"Magitek Predator\" fr=\"prédateur magitek\" ja=\"魔導プレデター\" cn=\"魔导猎手\" ko=\"마도 프레데터\" />\n  <loc type=\"3\" id=\"176\" de=\"Magitek-Himmelspanzer\" en=\"Magitek Sky Armor\" fr=\"armure aérienne magitek\" ja=\"魔導スカイアーマー\" cn=\"魔导浮空装甲\" ko=\"마도 스카이아머\" />\n  <loc type=\"3\" id=\"177\" de=\"Der Greif\" en=\"The Griffin\" fr=\"Le Griffon\" ja=\"鉄仮面\" cn=\"铁面公卿\" ko=\"철가면\" />\n  <loc type=\"3\" id=\"178\" de=\"Roland\" en=\"Roland\" fr=\"Roland\" ja=\"ローランド\" cn=\"罗兰德\" ko=\"롤랜드\" />\n  <loc type=\"3\" id=\"179\" de=\"Nihil-Diabolos\" en=\"Diabolos Hollow\" fr=\"Diabolos nihil\" ja=\"ディアボロス・ホロー\" cn=\"虚空迪亚波罗斯\" ko=\"공허의 디아볼로스\" />\n  <loc type=\"3\" id=\"180\" de=\"Panzerwaffe\" en=\"Armored Weapon\" fr=\"arme blindée\" ja=\"アームドウェポン\" cn=\"武装重甲\" ko=\"무장 병기\" />\n  <loc type=\"3\" id=\"181\" de=\"Gigi\" en=\"Gigi\" fr=\"Gigi\" ja=\"ギギ\" cn=\"吉吉\" ko=\"기기\" />\n  <loc type=\"3\" id=\"182\" de=\"Zurvan\" en=\"Zurvan\" fr=\"Zurvan\" ja=\"ズルワーン\" cn=\"祖尔宛\" ko=\"주르반\" />\n  <loc type=\"3\" id=\"183\" de=\"Namazuo\" en=\"Namazu\" fr=\"Namazu\" ja=\"ナマズオ\" cn=\"鲶鱼精\" ko=\"나마즈오\" />\n  <loc type=\"3\" id=\"184\" de=\"Kojin\" en=\"Kojin\" fr=\"Kojin\" ja=\"コウジン\" cn=\"甲人\" ko=\"코우진\" />\n  <loc type=\"3\" id=\"185\" de=\"Ananta\" en=\"Ananta\" fr=\"Ananta\" ja=\"アナンタ\" cn=\"阿难陀族\" ko=\"아난타\" />\n  <loc type=\"3\" id=\"186\" de=\"M'naago\" en=\"M'naago\" fr=\"M'naago\" ja=\"メ・ナーゴ\" cn=\"梅·娜格\" ko=\"메나고\" />\n  <loc type=\"3\" id=\"187\" de=\"Kotokaze\" en=\"Kotokaze\" fr=\"Kotokaze\" ja=\"コトカゼ\" cn=\"琴风\" ko=\"코토카제\" />\n  <loc type=\"3\" id=\"188\" de=\"Mammut\" en=\"Mammoth\" fr=\"mammouth\" ja=\"マンモス\" cn=\"长毛象\" ko=\"매머드\" />\n  <loc type=\"3\" id=\"189\" de=\"Phoebad\" en=\"Phoebad\" fr=\"phoebad\" ja=\"フォーバッド\" cn=\"弗巴德\" ko=\"포배드\" />\n  <loc type=\"3\" id=\"190\" de=\"Susano\" en=\"Susano\" fr=\"Susano\" ja=\"スサノオ\" cn=\"须佐之男\" ko=\"스사노오\" />\n  <loc type=\"3\" id=\"191\" de=\"Lakshmi\" en=\"Lakshmi\" fr=\"Lakshmi\" ja=\"ラクシュミ\" cn=\"吉祥天女\" ko=\"락슈미\" />\n  <loc type=\"3\" id=\"192\" de=\"Grynewaht\" en=\"Grynewaht\" fr=\"Grynewaht\" ja=\"グリーンワート\" cn=\"格林瓦特\" ko=\"그륀바트\" />\n  <loc type=\"3\" id=\"193\" de=\"Rasho\" en=\"Rasho\" fr=\"Rasho\" ja=\"ラショウ\" cn=\"罗生\" ko=\"라쇼\" />\n  <loc type=\"3\" id=\"194\" de=\"Cirina\" en=\"Cirina\" fr=\"Cirina\" ja=\"シリナ\" cn=\"其日娜\" ko=\"시리나\" />\n  <loc type=\"3\" id=\"195\" de=\"Magnai\" en=\"Magnai\" fr=\"Magnai\" ja=\"マグナイ\" cn=\"玛格奈\" ko=\"마그나이\" />\n  <loc type=\"3\" id=\"196\" de=\"Sadu\" en=\"Sadu\" fr=\"Sadu\" ja=\"サドゥ\" cn=\"纱都\" ko=\"사두\" />\n  <loc type=\"3\" id=\"197\" de=\"Shinryu\" en=\"Shinryu\" fr=\"Shinryu\" ja=\"神龍\" cn=\"神龙\" ko=\"신룡\" />\n  <loc type=\"3\" id=\"198\" de=\"Yotsuyu\" en=\"Yotsuyu\" fr=\"Yotsuyu\" ja=\"ヨツユ\" cn=\"夜露\" ko=\"요츠유\" />\n  <loc type=\"3\" id=\"199\" de=\"Krile\" en=\"Krile\" fr=\"Krile Baldesion\" ja=\"クルル・バルデシオン\" cn=\"可露儿·巴尔德西昂\" ko=\"쿠루루 발데시온\" />\n  <loc type=\"3\" id=\"200\" de=\"Lyse\" en=\"Lyse\" fr=\"Lyse\" ja=\"リセ\" cn=\"莉瑟\" ko=\"리세\" />\n  <loc type=\"3\" id=\"201\" de=\"Zenos yae Galvus\" en=\"Zenos yae Galvus\" fr=\"Zenos yae Galvus\" ja=\"ゼノス・イェー・ガルヴァス\" cn=\"芝诺斯·耶·加尔乌斯\" ko=\"제노스 예 갈부스\" />\n  <loc type=\"3\" id=\"202\" de=\"Hien\" en=\"Hien\" fr=\"Hien\" ja=\"ヒエン\" cn=\"飞燕\" ko=\"히엔\" />\n  <loc type=\"3\" id=\"203\" de=\"Moosling\" en=\"Mossling\" fr=\"moussemousse\" ja=\"モスモス\" cn=\"苔小妖\" ko=\"이끼이끼\" />\n  <loc type=\"3\" id=\"204\" de=\"Chapuli\" en=\"Chapuli\" fr=\"chapuli\" ja=\"チャプリ\" cn=\"洽普利蚱蜢\" ko=\"큰메뚜기\" />\n  <loc type=\"3\" id=\"205\" de=\"Qiqirn-Fleischfresser\" en=\"Qiqirn Meateater\" fr=\"Qiqirn croque-viande\" ja=\"キキルン・ミートイーター\" cn=\"卢恩人食肉者\" ko=\"키키룬 고기냠냠이\" />\n  <loc type=\"3\" id=\"206\" de=\"Hrodric Giftzunge\" en=\"Hrodric Poisontongue\" fr=\"Hrodric le Médisant\" ja=\"直言のフロドリック\" cn=\"直言不讳赫罗德里克\" ko=\"입바른 흐로드릭\" />\n  <loc type=\"3\" id=\"207\" de=\"Fordola rem Lupus\" en=\"Fordola rem Lupis\" fr=\"Fordola rem Lupus\" ja=\"フォルドラ・レム・ルプス\" cn=\"芙朵拉·雷姆·卢普斯\" ko=\"포르돌라 렘 루푸스\" />\n  <loc type=\"3\" id=\"208\" de=\"Rofocale\" en=\"Rofocale\" fr=\"Rofocale le Roi centaure\" ja=\"人馬王ロフォカレ\" cn=\"人马王洛弗卡勒\" ko=\"인마왕 로포칼레\" />\n  <loc type=\"3\" id=\"209\" de=\"Argath Thadalfus\" en=\"Argath Thadalfus\" fr=\"Argath Thadalfus\" ja=\"冷血剣アルガス\" cn=\"冷血剑阿加斯\" ko=\"냉혈검 아르가스\" />\n  <loc type=\"3\" id=\"210\" de=\"Raubahn &amp; Pipin\" en=\"Raubahn &amp; Pipin\" fr=\"Raubahn &amp; Pipin\" ja=\"ラウバーン＆ピピン\" cn=\"劳班&amp;皮平\" ko=\"라우반 &amp; 피핀\" />\n  <loc type=\"3\" id=\"211\" de=\"Koja\" en=\"Koja\" fr=\"koja\" ja=\"コジャ\" cn=\"高加颅\" ko=\"코자\" />\n  <loc type=\"3\" id=\"212\" de=\"Ango\" en=\"Ango\" fr=\"ango\" ja=\"アンゴ\" cn=\"安居鮟鱇\" ko=\"안고\" />\n  <loc type=\"3\" id=\"213\" de=\"Wegleitsystem\" en=\"Guidance Node\" fr=\"sphère de guidage\" ja=\"誘導システム\" cn=\"导航系统\" ko=\"유도 시스템\" />\n  <loc type=\"3\" id=\"214\" de=\"Tansui\" en=\"Tansui\" fr=\"Tansui\" ja=\"タンスイ\" cn=\"湛水\" ko=\"탄스이\" />\n  <loc type=\"3\" id=\"215\" de=\"Genbu\" en=\"Genbu\" fr=\"Genbu\" ja=\"玄武\" cn=\"玄武\" ko=\"현무\" />\n  <loc type=\"3\" id=\"216\" de=\"Byakko\" en=\"Byakko\" fr=\"Byakko\" ja=\"白虎\" cn=\"白虎\" ko=\"백호\" />\n  <loc type=\"3\" id=\"217\" de=\"Arenvald Lentinus\" en=\"Arenvald Lentinus\" fr=\"Arenvald Lentinus\" ja=\"アレンヴァルド・レンティヌス\" cn=\"阿雷恩瓦尔德·伦提努斯\" ko=\"아렌발드 렌티누스\" />\n  <loc type=\"3\" id=\"218\" de=\"Lupin\" en=\"Lupin\" fr=\"Lupin\" ja=\"人狼族\" cn=\"狼人族\" ko=\"인랑족\" />\n  <loc type=\"3\" id=\"219\" de=\"Hancock\" en=\"Hancock\" fr=\"Hancock\" ja=\"ハンコック\" cn=\"汉考克\" ko=\"행콕\" />\n  <loc type=\"3\" id=\"220\" de=\"Hisui &amp; Kurenai\" en=\"Hisui &amp; Kurenai\" fr=\"Hisui &amp; Kurenai\" ja=\"ヒスイ＆クレナイ\" cn=\"翡翠&amp;红\" ko=\"히스이 &amp; 쿠레나이\" />\n  <loc type=\"3\" id=\"221\" de=\"Wanyudo &amp; Katasharin\" en=\"Wanyudo &amp; Katasharin\" fr=\"wanyûdô &amp; katasharin\" ja=\"ワニュウドウ＆カタシャリン\" cn=\"轮入道&amp;片轮车\" ko=\"총각바퀴 &amp; 처녀바퀴\" />\n  <loc type=\"3\" id=\"222\" de=\"Hatamoto\" en=\"Hatamoto\" fr=\"hatamoto\" ja=\"ハタモト\" cn=\"旗本\" ko=\"직속 무사\" />\n  <loc type=\"3\" id=\"223\" de=\"Flöckchen\" en=\"Yukinko\" fr=\"yukinko\" ja=\"ユキンコ\" cn=\"雪童子\" ko=\"눈요정\" />\n  <loc type=\"3\" id=\"224\" de=\"Qitian Dasheng\" en=\"Qitian Dasheng\" fr=\"Qitian Dasheng\" ja=\"セイテンタイセイ\" cn=\"齐天大圣\" ko=\"제천대성\" />\n  <loc type=\"3\" id=\"225\" de=\"Hiruko\" en=\"Hiruko\" fr=\"Hiruko\" ja=\"ヒルコ\" cn=\"蛭子\" ko=\"히루코\" />\n  <loc type=\"3\" id=\"226\" de=\"Glückshase\" en=\"Happy Bunny\" fr=\"lapin du bonheur\" ja=\"しあわせうさぎ\" cn=\"幸福兔\" ko=\"행운토끼\" />\n  <loc type=\"3\" id=\"227\" de=\"Louhi\" en=\"Louhi\" fr=\"Louhi\" ja=\"ロウヒ\" cn=\"娄希\" ko=\"로우히\" />\n  <loc type=\"3\" id=\"228\" de=\"Tsukuyomi\" en=\"Tsukuyomi\" fr=\"Tsukuyomi\" ja=\"ツクヨミ\" cn=\"月读\" ko=\"츠쿠요미\" />\n  <loc type=\"3\" id=\"229\" de=\"Yiasmat\" en=\"Yiazmat\" fr=\"Yiazmat\" ja=\"鬼龍ヤズマット\" cn=\"鬼龙雅兹玛特\" ko=\"귀룡 야즈마트\" />\n  <loc type=\"3\" id=\"230\" de=\"Gosetsu\" en=\"Gosetsu\" fr=\"Gosetsu\" ja=\"ゴウセツ\" cn=\"豪雪\" ko=\"고우세츠\" />\n  <loc type=\"3\" id=\"231\" de=\"Karakuri Hannya\" en=\"Karakuri Hanya\" fr=\"hannya karakuri\" ja=\"カラクリ・ハンニャ\" cn=\"机关般若\" ko=\"꼭두각시 한냐\" />\n  <loc type=\"3\" id=\"232\" de=\"Muhtsu\" en=\"Muud Suud\" fr=\"moudsoud\" ja=\"ムードスード\" cn=\"姆德斯德\" ko=\"무드수드\" />\n  <loc type=\"3\" id=\"233\" de=\"Tokkapchi\" en=\"Tokkapchi\" fr=\"Tokkapchi\" ja=\"トカップチ\" cn=\"枯腐泥妖\" ko=\"진흙장사\" />\n  <loc type=\"3\" id=\"234\" de=\"Nebeldrache\" en=\"Mist Dragon\" fr=\"dragon des brumes\" ja=\"ミストドラゴン\" cn=\"雾龙\" ko=\"안개 드래곤\" />\n  <loc type=\"3\" id=\"235\" de=\"Suzaku\" en=\"Suzaku\" fr=\"Suzaku\" ja=\"朱雀\" cn=\"朱雀\" ko=\"주작\" />\n  <loc type=\"3\" id=\"236\" de=\"Pazuzu\" en=\"Pazuzu\" fr=\"Pazuzu\" ja=\"パズズ\" cn=\"帕祖祖\" ko=\"파주주\" />\n  <loc type=\"3\" id=\"237\" de=\"Penthesilea\" en=\"Penthesilea\" fr=\"Penthésilée\" ja=\"ペンテシレイア\" cn=\"彭忒西勒亚\" ko=\"펜테실레이아\" />\n  <loc type=\"3\" id=\"238\" de=\"Asahi sas Brutus\" en=\"Asahi sas Brutus\" fr=\"Asahi sas Brutus\" ja=\"アサヒ・サス・ブルトゥス\" cn=\"朝阳·萨斯·布鲁图斯\" ko=\"아사히 사스 브루투스\" />\n  <loc type=\"3\" id=\"239\" de=\"Yojinbo &amp; Daigoro\" en=\"Yojimbo &amp; Daigoro\" fr=\"Yojimbo &amp; Daigoro\" ja=\"ヨウジンボウ&amp;ダイゴロウ\" cn=\"保镖&amp;大五郎\" ko=\"요우진보 &amp; 다이고로\" />\n  <loc type=\"3\" id=\"240\" de=\"Omega\" en=\"Omega\" fr=\"Oméga\" ja=\"オメガ\" cn=\"欧米茄\" ko=\"오메가\" />\n  <loc type=\"3\" id=\"241\" de=\"Tataru (Stormblood)\" en=\"Stormblood Tataru Taru\" fr=\"Tataru (Stormblood)\" ja=\"タタル・タル（紅蓮）\" cn=\"塔塔露·塔露（红莲）\" ko=\"타타루 타루(홍련)\" />\n  <loc type=\"3\" id=\"242\" de=\"Dvergr\" en=\"Dvergr\" fr=\"Dvergr\" ja=\"ドゥエルグ\" cn=\"矮儒\" ko=\"드베르그\" />\n  <loc type=\"3\" id=\"243\" de=\"Ejika Tsunjika\" en=\"Ejika Tsunjika\" fr=\"Ejika Tsunjika\" ja=\"エジカ・ツンジカ\" cn=\"艾吉卡·岑吉卡\" ko=\"에지카 츤지카\" />\n  <loc type=\"3\" id=\"244\" de=\"Prometheus\" en=\"Prometheus\" fr=\"Prometheus\" ja=\"プロメテウス\" cn=\"普罗米修斯\" ko=\"프로메테우스\" />\n  <loc type=\"3\" id=\"245\" de=\"Kristallwächter\" en=\"Provenance Watcher\" fr=\"Gardien de Provenance\" ja=\"プロヴェナンス・ウォッチャー\" cn=\"起源守望者\" ko=\"기원 관찰자\" />\n  <loc type=\"3\" id=\"246\" de=\"Seiryu\" en=\"Seiryu\" fr=\"Seiryû\" ja=\"青龍\" cn=\"青龙\" ko=\"청룡\" />\n  <loc type=\"3\" id=\"247\" de=\"Alpha\" en=\"Alpha\" fr=\"Alpha\" ja=\"アルファ\" cn=\"阿尔法\" ko=\"알파\" />\n  <loc type=\"3\" id=\"248\" de=\"Majestät Goldbartel\" en=\"Great Gold Whisker\" fr=\"Altesse namazu nain doré\" ja=\"ナマキン大王\" cn=\"金鲶大王\" ko=\"금마즈오 대왕\" />\n  <loc type=\"3\" id=\"249\" de=\"Gilgamesch (Stormblood)\" en=\"Stormblood Gilgamesh\" fr=\"Gilgamesh (Stormblood)\" ja=\"ギルガメッシュ（紅蓮）\" cn=\"吉尔伽美什（红莲）\" ko=\"길가메시(홍련)\" />\n  <loc type=\"3\" id=\"250\" de=\"Cherub Ultima\" en=\"Ultima, the High Seraph\" fr=\"Ultima, la Grande Séraphine\" ja=\"聖天使アルテマ\" cn=\"圣天使阿尔蒂玛\" ko=\"성천사 알테마\" />\n  <loc type=\"3\" id=\"251\" de=\"Alphinaud &amp; Alisaie (Stormblood)\" en=\"Stormblood Alphinaud &amp; Alisaie\" fr=\"Alphinaud &amp; Alisaie (Stormblood)\" ja=\"アルフィノ＆アリゼー（紅蓮）\" cn=\"阿尔菲诺&amp;阿莉塞（红莲）\" ko=\"알피노 &amp; 알리제(홍련)\" />\n  <loc type=\"3\" id=\"252\" de=\"Noctis Lucis Caelum\" en=\"Noctis Lucis Caelum\" fr=\"Noctis Lucis Caelum\" ja=\"ノクティス・ルシス・チェラム\" cn=\"诺克提斯·路西斯·切拉姆\" ko=\"녹티스 루시스 카일룸\" />\n  <loc type=\"3\" id=\"253\" de=\"Amaro\" en=\"Amaro\" fr=\"amaro\" ja=\"アマロ\" cn=\"阿马罗\" ko=\"아마로\" />\n  <loc type=\"3\" id=\"254\" de=\"Böse Waffe\" en=\"Evil Weapon\" fr=\"arme maligne\" ja=\"イビルウェポン\" cn=\"恶魔兵装\" ko=\"무기마\" />\n  <loc type=\"3\" id=\"255\" de=\"Eheleute Chai\" en=\"Lord and Lady Chai\" fr=\"Chai-Nuzz &amp; Dulia-Chai\" ja=\"チャイ夫妻\" cn=\"采夫妇\" ko=\"차이 부부\" />\n  <loc type=\"3\" id=\"256\" de=\"Riesenkaktor\" en=\"Gigantender\" fr=\"gigapampa\" ja=\"ギガテンダー\" cn=\"巨人掌\" ko=\"기가텐더\" />\n  <loc type=\"3\" id=\"257\" de=\"Feo Ul\" en=\"Feo Ul\" fr=\"Feo Ul\" ja=\"フェオ＝ウル\" cn=\"菲奥·乌儿\" ko=\"페오 울\" />\n  <loc type=\"3\" id=\"258\" de=\"Runar\" en=\"Runar\" fr=\"Runar\" ja=\"ルナル\" cn=\"鲁纳尔\" ko=\"루나르\" />\n  <loc type=\"3\" id=\"259\" de=\"Grenoldt\" en=\"Grenoldt\" fr=\"Grenoldt\" ja=\"グレノルト\" cn=\"盖雷诺尔特\" ko=\"그레놀트\" />\n  <loc type=\"3\" id=\"260\" de=\"Philia\" en=\"Philia\" fr=\"Philia\" ja=\"フィリア\" cn=\"斐利亚\" ko=\"필리아\" />\n  <loc type=\"3\" id=\"261\" de=\"Titania\" en=\"Titania\" fr=\"Titania\" ja=\"ティターニア\" cn=\"妖灵王缇坦妮雅\" ko=\"티타니아\" />\n  <loc type=\"3\" id=\"262\" de=\"Eros\" en=\"Eros\" fr=\"Éros\" ja=\"エロース\" cn=\"艾洛斯\" ko=\"에로스\" />\n  <loc type=\"3\" id=\"263\" de=\"Storge\" en=\"Storge\" fr=\"Storgê\" ja=\"ストルゲー\" cn=\"斯托尔戈\" ko=\"스토르게\" />\n  <loc type=\"3\" id=\"264\" de=\"Formidabel\" en=\"Formidable\" fr=\"Formidable\" ja=\"フォーミダブル\" cn=\"畏惧装甲\" ko=\"어마무시\" />\n  <loc type=\"3\" id=\"265\" de=\"Lyna\" en=\"Lyna\" fr=\"Lyna\" ja=\"ライナ\" cn=\"莱楠\" ko=\"라이나\" />\n  <loc type=\"3\" id=\"266\" de=\"Närrinnen\" en=\"Jongleurs of Eulmore\" fr=\"bouffonnes d'Eulmore\" ja=\"ユールモアの道化\" cn=\"游末邦的小丑\" ko=\"율모어의 광대\" />\n  <loc type=\"3\" id=\"267\" de=\"Innozenz\" en=\"Innocence\" fr=\"Innocence\" ja=\"イノセンス\" cn=\"无瑕灵君\" ko=\"이노센스\" />\n  <loc type=\"3\" id=\"268\" de=\"Y'shtola (Shadowbringers)\" en=\"Shadowbringers Y'shtola\" fr=\"Y'shtola (Shadowbringers)\" ja=\"ヤ・シュトラ（漆黒）\" cn=\"雅·修特拉（暗影）\" ko=\"야슈톨라(칠흑)\" />\n  <loc type=\"3\" id=\"269\" de=\"Urianger (Shadowbringers)\" en=\"Shadowbringers Urianger\" fr=\"Urianger (Shadowbringers)\" ja=\"ウリエンジェ（漆黒）\" cn=\"于里昂热（暗影）\" ko=\"위리앙제(칠흑)\" />\n  <loc type=\"3\" id=\"270\" de=\"Ran'jit\" en=\"Ran'jit\" fr=\"Ran'jit\" ja=\"ランジート\" cn=\"兰吉特\" ko=\"란지트\" />\n  <loc type=\"3\" id=\"271\" de=\"Hades\" en=\"Hades\" fr=\"Hadès\" ja=\"ハーデス\" cn=\"哈迪斯\" ko=\"하데스\" />\n  <loc type=\"3\" id=\"272\" de=\"Ardbert\" en=\"Ardbert\" fr=\"Ardbert\" ja=\"アルバート\" cn=\"阿尔博特\" ko=\"아르버트\" />\n  <loc type=\"3\" id=\"273\" de=\"Hobgoblin\" en=\"Hobgoblin\" fr=\"hobgobelin\" ja=\"ホブゴブリン\" cn=\"大哥布林\" ko=\"홉고블린\" />\n  <loc type=\"3\" id=\"274\" de=\"Quiexie\" en=\"Porxie\" fr=\"porxie\" ja=\"ポークシー\" cn=\"仙子猪\" ko=\"포크시\" />\n  <loc type=\"3\" id=\"275\" de=\"Leguan\" en=\"Iguana\" fr=\"iguane\" ja=\"イグアナ\" cn=\"鬣蜥\" ko=\"이구아나\" />\n  <loc type=\"3\" id=\"276\" de=\"Nu Mou\" en=\"Nu Mou\" fr=\"Nu Mou\" ja=\"ン・モゥ\" cn=\"恩莫\" ko=\"응 모우\" />\n  <loc type=\"3\" id=\"277\" de=\"Fuath\" en=\"Fuath\" fr=\"Fuath\" ja=\"フーア\" cn=\"水妖\" ko=\"푸아\" />\n  <loc type=\"3\" id=\"278\" de=\"Leanan Sidhe\" en=\"Leannan Sith\" fr=\"Leannan Sith\" ja=\"リャナンシー\" cn=\"凉南希\" ko=\"랴난시\" />\n  <loc type=\"3\" id=\"279\" de=\"Einsiedler\" en=\"Seeker of Solitude\" fr=\"ermite du palais\" ja=\"黒の導師\" cn=\"漆黑导师\" ko=\"검은 도사\" />\n  <loc type=\"3\" id=\"280\" de=\"Orakel des Lichts\" en=\"Oracle of Light\" fr=\"prêtresse de la Lumière\" ja=\"光の巫女\" cn=\"光之巫女\" ko=\"빛의 무녀\" />\n  <loc type=\"3\" id=\"281\" de=\"Archaeotania\" en=\"Archaeotania\" fr=\"Archaeotania\" ja=\"アルケオタニア\" cn=\"古塔尼亚\" ko=\"아르케오타니아\" />\n  <loc type=\"3\" id=\"282\" de=\"9S\" en=\"9S\" fr=\"9S\" ja=\"９Ｓ\" cn=\"9S\" ko=\"9S\" />\n  <loc type=\"3\" id=\"283\" de=\"Blumenkorb\" en=\"Flower Basket\" fr=\"panier fleuri\" ja=\"フラワーバスケット\" cn=\"花束篮筐\" ko=\"꽃바구니\" />\n  <loc type=\"3\" id=\"284\" de=\"Qitari\" en=\"Qitari\" fr=\"Qitari\" ja=\"キタリ\" cn=\"奇塔利\" ko=\"키타리\" />\n  <loc type=\"3\" id=\"285\" de=\"Gnoll\" en=\"Gnoll\" fr=\"gnole\" ja=\"ノール\" cn=\"异豺\" ko=\"놀\" />\n  <loc type=\"3\" id=\"286\" de=\"Lizbeth\" en=\"Lizbeth\" fr=\"Lizbeth\" ja=\"リズベス\" cn=\"莉兹贝丝\" ko=\"리즈베스\" />\n  <loc type=\"3\" id=\"287\" de=\"Fledersquatch\" en=\"Batsquatch\" fr=\"Batsquatch\" ja=\"バッツカッチ\" cn=\"大脚野蝠\" ko=\"배츠콰치\" />\n  <loc type=\"3\" id=\"288\" de=\"Geläuterte Unzucht\" en=\"Forgiven Obscenity\" fr=\"obscénité pardonnée\" ja=\"フォーギヴン・オブセニティー\" cn=\"得到宽恕的猥亵\" ko=\"면죄된 외설\" />\n  <loc type=\"3\" id=\"289\" de=\"Huaca\" en=\"Huaca\" fr=\"Huaca\" ja=\"フアガス\" cn=\"华迦斯\" ko=\"후아카\" />\n  <loc type=\"3\" id=\"290\" de=\"Abscheuliches Monstrum\" en=\"Unknown\" fr=\"inconnu\" ja=\"正体不明\" cn=\"不明物体\" ko=\"정체불명\" />\n  <loc type=\"3\" id=\"291\" de=\"Rubin-Waffe\" en=\"Ruby Weapon\" fr=\"Arme Rubis\" ja=\"ルビーウェポン\" cn=\"红宝石神兵\" ko=\"루비 웨폰\" />\n  <loc type=\"3\" id=\"292\" de=\"Therion\" en=\"Therion\" fr=\"Mégatherion\" ja=\"メガセリオン\" cn=\"至大灾兽\" ko=\"메가테리온\" />\n  <loc type=\"3\" id=\"293\" de=\"Varis yae Galvus\" en=\"Varis yae Galvus\" fr=\"Varis yae Galvus\" ja=\"ヴァリス・イェー・ガルヴァス\" cn=\"瓦厉斯·耶·加尔乌斯\" ko=\"바리스 예 갈부스\" />\n  <loc type=\"3\" id=\"294\" de=\"Zwerg\" en=\"Dwarf\" fr=\"Nain\" ja=\"ドワーフ\" cn=\"矮人\" ko=\"드워프\" />\n  <loc type=\"3\" id=\"295\" de=\"Ehll Tou\" en=\"Ehll Tou\" fr=\"Ehll Tou\" ja=\"エル・トゥ\" cn=\"艾尔·图\" ko=\"엘 투\" />\n  <loc type=\"3\" id=\"296\" de=\"Zwergenpanzer\" en=\"Rolling Tankard\" fr=\"automatank\" ja=\"ドワーフ戦車\" cn=\"矮人战车\" ko=\"드워프 전차\" />\n  <loc type=\"3\" id=\"297\" de=\"Lugus\" en=\"Lugus\" fr=\"Lugus\" ja=\"ルゴス\" cn=\"卢格斯\" ko=\"루구스\" />\n  <loc type=\"3\" id=\"298\" de=\"Dawon\" en=\"Dawon\" fr=\"Dawon\" ja=\"ドゥン\" cn=\"达温\" ko=\"다우언\" />\n  <loc type=\"3\" id=\"299\" de=\"Adrammelech\" en=\"Adrammelech\" fr=\"Adrammelech\" ja=\"アドラメレク\" cn=\"阿德拉梅里克\" ko=\"아드람멜렉\" />\n  <loc type=\"3\" id=\"300\" de=\"Elidibus\" en=\"Elidibus\" fr=\"Elidibus\" ja=\"アシエン・エリディブス\" cn=\"无影艾里迪布斯\" ko=\"아씨엔 엘리디부스\" />\n  <loc type=\"3\" id=\"301\" de=\"Thancred (Shadowbringers)\" en=\"Shadowbringers Thancred\" fr=\"Thancred (Shadowbringers)\" ja=\"サンクレッド（漆黒）\" cn=\"桑克瑞德（暗影）\" ko=\"산크레드(칠흑)\" />\n  <loc type=\"3\" id=\"302\" de=\"Saphir-Waffe\" en=\"Sapphire Weapon\" fr=\"Arme Saphir\" ja=\"サファイアウェポン\" cn=\"蓝宝石神兵\" ko=\"사파이어 웨폰\" />\n  <loc type=\"3\" id=\"303\" de=\"2P\" en=\"2P\" fr=\"2P\" ja=\"２Ｐ\" cn=\"2P\" ko=\"2P\" />\n  <loc type=\"3\" id=\"304\" de=\"Krieger des Lichts (Shadowbringers)\" en=\"Shadowbringers Warrior of Light\" fr=\"Guerrier de la Lumière (Shadowbringers)\" ja=\"ウォーリア・オブ・ライト（漆黒）\" cn=\"光之战士（暗影）\" ko=\"빛의 전사(칠흑)\" />\n  <loc type=\"3\" id=\"305\" de=\"Blaubert Veilchenblau\" en=\"Great Azuro\" fr=\"Bleuet masqué\" ja=\"マスク・ザ・ブルー\" cn=\"蓝色假面\" ko=\"청가면\" />\n  <loc type=\"3\" id=\"306\" de=\"Muttersau\" en=\"Mother Porxie\" fr=\"mère porxie\" ja=\"マザーポークシー\" cn=\"仙子猪之母\" ko=\"마더 포크시\" />\n  <loc type=\"3\" id=\"307\" de=\"Trinität der Sucher\" en=\"Trinity Seeker\" fr=\"trinité soudée\" ja=\"トリニティ・シーカー\" cn=\"求道之三位一体\" ko=\"탐구의 삼위일체\" />\n  <loc type=\"3\" id=\"308\" de=\"Trinität der Eingeschworenen\" en=\"Trinity Avowed\" fr=\"trinité féale\" ja=\"トリニティ・アヴァウド\" cn=\"誓约之三位一体\" ko=\"맹세의 삼위일체\" />\n  <loc type=\"3\" id=\"309\" de=\"Azulmagia\" en=\"Azulmagia\" fr=\"Azulmagia\" ja=\"アポカリョープス\" cn=\"启示者\" ko=\"아포칼리옵스\" />\n  <loc type=\"3\" id=\"310\" de=\"Siegfried\" en=\"Siegfried\" fr=\"Siegfried\" ja=\"ジークフリード\" cn=\"齐格弗里德\" ko=\"지크프리드\" />\n  <loc type=\"3\" id=\"311\" de=\"Gogo der Mime\" en=\"Gogo, Master of Mimicry\" fr=\"Gogo le mime\" ja=\"ものまね士ゴゴ\" cn=\"模仿师格格\" ko=\"흉내쟁이 고고\" />\n  <loc type=\"3\" id=\"312\" de=\"Smaragd-Waffe\" en=\"Emerald Weapon\" fr=\"Arme Émeraude\" ja=\"エメラルドウェポン\" cn=\"绿宝石神兵\" ko=\"에메랄드 웨폰\" />\n  <loc type=\"3\" id=\"313\" de=\"Ryne\" en=\"Ryne\" fr=\"Ryne\" ja=\"リーン\" cn=\"琳\" ko=\"린\" />\n  <loc type=\"3\" id=\"314\" de=\"Gaia\" en=\"Gaia\" fr=\"Gaïa\" ja=\"ガイア\" cn=\"盖娅\" ko=\"가이아\" />\n  <loc type=\"3\" id=\"315\" de=\"Edens Verheißung\" en=\"Eden's Promise\" fr=\"Promesse d'Éden\" ja=\"プロミス・オブ・エデン\" cn=\"伊甸之约\" ko=\"에덴의 약속\" />\n  <loc type=\"3\" id=\"316\" de=\"Phoenix\" en=\"Phoenix\" fr=\"Phénix\" ja=\"フェニックス\" cn=\"不死鸟\" ko=\"피닉스\" />\n  <loc type=\"3\" id=\"317\" de=\"Kerkermeister\" en=\"Keeper of the Keys\" fr=\"gardien hypogéen\" ja=\"キーパー・オブ・ダンジョン\" cn=\"宝库守护者\" ko=\"지하미궁 수호자\" />\n  <loc type=\"3\" id=\"318\" de=\"Luna-Bahamut\" en=\"Lunar Bahamut\" fr=\"Luna-Bahamut\" ja=\"ルナバハムート\" cn=\"真月巴哈姆特\" ko=\"루나 바하무트\" />\n  <loc type=\"3\" id=\"319\" de=\"Valens van Varro\" en=\"Valens van Varro\" fr=\"Valens van Varro\" ja=\"ウァレンス・ヴァン・ウァロ\" cn=\"瓦伦斯·范·瓦罗\" ko=\"발렌스 반 바로\" />\n  <loc type=\"3\" id=\"320\" de=\"Luna-Ifrit\" en=\"Lunar Ifrit\" fr=\"Luna-Ifrit\" ja=\"ルナイフリート\" cn=\"真月伊弗利特\" ko=\"루나 이프리트\" />\n  <loc type=\"3\" id=\"321\" de=\"Shemhazai der IV. Legion\" en=\"4th-make Shemhazai\" fr=\"Shemhazai de la 4e légion\" ja=\"IVレギオン・シュミハザ\" cn=\"第四军团谢米哈扎\" ko=\"IV군단 셰미하자\" />\n  <loc type=\"3\" id=\"322\" de=\"Cuchulainn der IV. Legion\" en=\"4th-make Cuchulainn\" fr=\"Cúchulainn de la 4e légion\" ja=\"IVレギオン・キュクレイン\" cn=\"第四军团丘库雷因\" ko=\"IV군단 쿠훌린\" />\n  <loc type=\"3\" id=\"323\" de=\"G-Krieger\" en=\"G-Warrior\" fr=\"Guerrier-G\" ja=\"Gウォリアー\" cn=\"究极战士G型\" ko=\"G 워리어\" />\n  <loc type=\"3\" id=\"324\" de=\"Diamant-Waffe\" en=\"Diamond Weapon\" fr=\"Arme Diamant\" ja=\"ダイヤウェポン\" cn=\"钻石神兵\" ko=\"다이아몬드 웨폰\" />\n  <loc type=\"3\" id=\"325\" de=\"Diablo-Armament\" en=\"Diablo Armament\" fr=\"batterie d'artillerie Diablo\" ja=\"ディアブロ・アーマメント\" cn=\"迪亚布罗魔兵\" ko=\"디아블로 아머먼트\" />\n  <loc type=\"3\" id=\"326\" de=\"2B\" en=\"2B\" fr=\"2B\" ja=\"２Ｂ\" cn=\"2B\" ko=\"2B\" />\n  <loc type=\"3\" id=\"327\" de=\"Troll\" en=\"Troll\" fr=\"troll\" ja=\"トロル\" cn=\"巨魔\" ko=\"트롤\" />\n  <loc type=\"3\" id=\"328\" de=\"Pisaca\" en=\"Pisaca\" fr=\"pishacha\" ja=\"ピシャーチャ\" cn=\"毕舍遮\" ko=\"피샤차\" />\n  <loc type=\"3\" id=\"329\" de=\"Ea\" en=\"Ea\" fr=\"Éa\" ja=\"イーア\" cn=\"异亚\" ko=\"이아\" />\n  <loc type=\"3\" id=\"330\" de=\"Arkasodara\" en=\"Arkasodara\" fr=\"Arkasodara\" ja=\"アルカソーダラ\" cn=\"悌阳象族\" ko=\"아르카소다라\" />\n  <loc type=\"3\" id=\"331\" de=\"Loporrit\" en=\"Loporrit\" fr=\"Loporrite\" ja=\"レポリット\" cn=\"兔兔族\" ko=\"레포릿\" />\n  <loc type=\"3\" id=\"332\" de=\"Argos\" en=\"Argos\" fr=\"Argos\" ja=\"アルゴス\" cn=\"阿尔戈斯\" ko=\"아르고스\" />\n  <loc type=\"3\" id=\"333\" de=\"Hermes\" en=\"Hermes\" fr=\"Hermès\" ja=\"ヘルメス\" cn=\"赫尔墨斯\" ko=\"헤르메스\" />\n  <loc type=\"3\" id=\"334\" de=\"Splitterseele Amon\" en=\"Amon the Undying\" fr=\"Amon\" ja=\"異執のアモン\" cn=\"邪执外道的亚蒙\" ko=\"이집의 아몬\" />\n  <loc type=\"3\" id=\"335\" de=\"Erichthonios\" en=\"Erichthonios\" fr=\"Érichthonios\" ja=\"エリクトニオス\" cn=\"埃里克特翁尼亚斯\" ko=\"에리크토니오스\" />\n  <loc type=\"3\" id=\"336\" de=\"Magus-Schwestern\" en=\"Magus Sisters\" fr=\"sœurs Magus\" ja=\"メーガス三姉妹\" cn=\"梅加斯三姐妹\" ko=\"메이거스 세 자매\" />\n  <loc type=\"3\" id=\"337\" de=\"Meteion\" en=\"Meteion\" fr=\"Météion\" ja=\"メーティオン\" cn=\"梅蒂恩\" ko=\"메테이온\" />\n  <loc type=\"3\" id=\"338\" de=\"Fourchenault Leveilleur\" en=\"Fourchenault Leveilleur\" fr=\"Fourchenault\" ja=\"フルシュノ・ルヴェユール\" cn=\"富尔什诺·莱韦耶勒尔\" ko=\"푸르슈노 르베유르\" />\n  <loc type=\"3\" id=\"339\" de=\"Anima\" en=\"Anima\" fr=\"Anima\" ja=\"アニマ\" cn=\"阿尼玛\" ko=\"아니마\" />\n  <loc type=\"3\" id=\"340\" de=\"Quintus van Cinna\" en=\"Quintus van Cinna\" fr=\"Quintus van Cinna\" ja=\"クイントゥス・ヴァン・キンナ\" cn=\"昆图斯·范·秦纳\" ko=\"퀸투스 반 킨나\" />\n  <loc type=\"3\" id=\"341\" de=\"Alphinaud &amp; Alisaie (Endwalker)\" en=\"Endwalker Alphinaud &amp; Alisaie\" fr=\"Alphinaud &amp; Alisaie (Endwalker)\" ja=\"アルフィノ＆アリゼー（暁月）\" cn=\"阿尔菲诺&amp;阿莉塞（晓月）\" ko=\"알피노 &amp; 알리제(효월)\" />\n  <loc type=\"3\" id=\"342\" de=\"Hythlodaeus\" en=\"Hythlodaeus\" fr=\"Hythlodaeus\" ja=\"ヒュトロダエウス\" cn=\"希斯拉德\" ko=\"휘틀로다이우스\" />\n  <loc type=\"3\" id=\"343\" de=\"Vrtra\" en=\"Vrtra\" fr=\"Vrtra\" ja=\"ヴリトラ\" cn=\"弗栗多\" ko=\"브리트라\" />\n  <loc type=\"3\" id=\"344\" de=\"Zodiark\" en=\"Zodiark\" fr=\"Zordiarche\" ja=\"ゾディアーク\" cn=\"佐迪亚克\" ko=\"조디아크\" />\n  <loc type=\"3\" id=\"345\" de=\"Hydaelyn\" en=\"Hydaelyn\" fr=\"Hydaelyn\" ja=\"ハイデリン\" cn=\"海德林\" ko=\"하이델린\" />\n  <loc type=\"3\" id=\"346\" de=\"Zenos Galvus\" en=\"Zenos Galvus\" fr=\"Zenos Galvus\" ja=\"ゼノス・ガルヴァス\" cn=\"芝诺斯·加尔乌斯\" ko=\"제노스 갈부스\" />\n  <loc type=\"3\" id=\"347\" de=\"Schutzwall\" en=\"Rampart\" fr=\"rempart\" ja=\"ランパート\" cn=\"铁壁\" ko=\"방어벽\" />\n  <loc type=\"3\" id=\"348\" de=\"Hippowagen\" en=\"Hippo Cart\" fr=\"hippochar\" ja=\"ヒッポカート\" cn=\"河马车\" ko=\"하마 마차\" />\n  <loc type=\"3\" id=\"349\" de=\"Gajasura\" en=\"Gajasura\" fr=\"Gajasura\" ja=\"ガジャースラ\" cn=\"魔邪象族\" ko=\"가자수라\" />\n  <loc type=\"3\" id=\"350\" de=\"Rhalgr\" en=\"Rhalgr\" fr=\"Rhalgr\" ja=\"ラールガー\" cn=\"拉尔戈\" ko=\"랄거\" />\n  <loc type=\"3\" id=\"351\" de=\"Azeyma\" en=\"Azeyma\" fr=\"Azeyma\" ja=\"アーゼマ\" cn=\"阿泽玛\" ko=\"아제마\" />\n  <loc type=\"3\" id=\"352\" de=\"Nald\" en=\"Nald\" fr=\"Nald\" ja=\"ナル\" cn=\"纳尔\" ko=\"날\" />\n  <loc type=\"3\" id=\"353\" de=\"Thal\" en=\"Thal\" fr=\"Thal\" ja=\"ザル\" cn=\"札尔\" ko=\"달\" />\n  <loc type=\"3\" id=\"354\" de=\"Chi\" en=\"Chi\" fr=\"Khi\" ja=\"カイ\" cn=\"希\" ko=\"키\" />\n  <loc type=\"3\" id=\"355\" de=\"Daivadipa\" en=\"Daivadipa\" fr=\"Daivadipa\" ja=\"ダイヴァディーパ\" cn=\"明灯天王\" ko=\"다이바디파\" />\n  <loc type=\"3\" id=\"356\" de=\"Endsängerin\" en=\"Endsinger\" fr=\"chantre de l'anéantissement\" ja=\"終焉を謳うもの\" cn=\"讴歌终结之物\" ko=\"종언을 노래하는 자\" />\n  <loc type=\"3\" id=\"357\" de=\"N-7000\" en=\"N-7000\" fr=\"N-7000\" ja=\"N-7000\" cn=\"N-7000\" ko=\"N-7000\" />\n  <loc type=\"3\" id=\"358\" de=\"Geryon der Gewaltsame\" en=\"Geryon the Steer\" fr=\"Géryon le Dominateur\" ja=\"覇道のゲーリュオン\" cn=\"征服者革律翁\" ko=\"패도의 게리온\" />\n  <loc type=\"3\" id=\"359\" de=\"Gladiator von Sil'dih\" en=\"Gladiator of Sil'dih\" fr=\"gladiateur sildien\" ja=\"シラディハ・グラディアトル\" cn=\"希拉狄哈剑斗士\" ko=\"실디하 검투사\" />\n  <loc type=\"3\" id=\"360\" de=\"Ritter der Thorne\" en=\"Thorne Knight\" fr=\"chevalier-mage des Thorne\" ja=\"ソーン・マジックナイト\" cn=\"索恩自走人偶\" ko=\"손 마법기사\" />\n  <loc type=\"3\" id=\"361\" de=\"Beatrice\" en=\"Beatrice\" fr=\"Béatrice\" ja=\"ベアトリーチェ\" cn=\"贝雅特丽齐\" ko=\"베아트리체\" />\n  <loc type=\"3\" id=\"362\" de=\"Byregot\" en=\"Byregot\" fr=\"Byregot\" ja=\"ビエルゴ\" cn=\"比尔格\" ko=\"비레고\" />\n  <loc type=\"3\" id=\"363\" de=\"Zeless Gah\" en=\"Shadowcaster Zeless Gah\" fr=\"Zeless Gah\" ja=\"影火のゼレズ・ガー\" cn=\"鬼阴火泽雷斯·夏\" ko=\"그림자불 젤레즈 가\" />\n  <loc type=\"3\" id=\"364\" de=\"Scarmiglione\" en=\"Scarmiglione\" fr=\"Scarmiglione\" ja=\"スカルミリョーネ\" cn=\"斯卡米留尼\" ko=\"스카르밀리오네\" />\n  <loc type=\"3\" id=\"365\" de=\"Barbarizia\" en=\"Barbariccia\" fr=\"Barbariccia\" ja=\"バルバリシア\" cn=\"巴尔巴莉希娅\" ko=\"바르바리차\" />\n  <loc type=\"3\" id=\"366\" de=\"Leiter Lahabrea\" en=\"Chief Keyward Lahabrea\" fr=\"Lahabrea (passé)\" ja=\"ラハブレア\" cn=\"拉哈布雷亚\" ko=\"라하브레아\" />\n  <loc type=\"3\" id=\"367\" de=\"Hephaistos\" en=\"Hephaistos\" fr=\"Héphaïstos\" ja=\"ヘファイストス\" cn=\"赫淮斯托斯\" ko=\"헤파이스토스\" />\n  <loc type=\"3\" id=\"368\" de=\"Mechanik-Onmyoji &amp; -Yojinbo\" en=\"Clockwork Onmyoji &amp; Clockwork Yojimbo\" fr=\"prêtre onmyo mécanique &amp; garde du corps mécanique\" ja=\"からくり陰陽師＆からくり用心棒\" cn=\"机关阴阳师＆机关保镖\" ko=\"꼭두각시 음양사 ＆ 꼭두각시 경호원\" />\n  <loc type=\"3\" id=\"369\" de=\"Dreamingway\" en=\"Dreamingway\" fr=\"Dreamingway\" ja=\"ドリーミングウェイ\" cn=\"梦明威\" ko=\"드리밍웨이\" />\n  <loc type=\"3\" id=\"370\" de=\"Miaurizius\" en=\"Felicitous Furball\" fr=\"Chamoiselle\" ja=\"ねこみみさん\" cn=\"猫耳小员\" ko=\"야옹 선생\" />\n  <loc type=\"3\" id=\"371\" de=\"Anden\" en=\"Anden\" fr=\"Anden\" ja=\"アンデン\" cn=\"安登\" ko=\"안덴\" />\n  <loc type=\"3\" id=\"372\" de=\"Suprae-Lugae\" en=\"Suprae-Lugae\" fr=\"Super Lugae\" ja=\"スーパールゲイエ\" cn=\"超威卢格\" ko=\"슈퍼 루게이에\" />\n  <loc type=\"3\" id=\"373\" de=\"Gancanagh\" en=\"Gancanagh\" fr=\"Gancanagh\" ja=\"ガンカナグー\" cn=\"蜜言妖\" ko=\"간카나\" />\n  <loc type=\"3\" id=\"374\" de=\"Menphina\" en=\"Menphina\" fr=\"Menphina\" ja=\"メネフィナ\" cn=\"梅茵菲娜\" ko=\"메느피나\" />\n  <loc type=\"3\" id=\"375\" de=\"Cagnazzo\" en=\"Cagnazzo\" fr=\"Cagnazzo\" ja=\"カイナッツォ\" cn=\"凯纳槽\" ko=\"카냐초\" />\n  <loc type=\"3\" id=\"376\" de=\"Rubicante\" en=\"Rubicante\" fr=\"Rubicante\" ja=\"ルビカンテ\" cn=\"卢比坎特\" ko=\"루비칸테\" />\n  <loc type=\"3\" id=\"377\" de=\"Venat\" en=\"Venat\" fr=\"Venat\" ja=\"ヴェーネス\" cn=\"维涅斯\" ko=\"베네스\" />\n  <loc type=\"3\" id=\"378\" de=\"Kleiner Laternengeist\" en=\"Okuri Chochin\" fr=\"bake-chôchin\" ja=\"バケチョウチン\" cn=\"妖怪提灯\" ko=\"요괴등\" />\n  <loc type=\"3\" id=\"379\" de=\"Gorai der Entfesselte\" en=\"Gorai the Uncaged\" fr=\"Gôrai le fureteur\" ja=\"鉄鼠ゴウライ\" cn=\"铁鼠豪雷\" ko=\"무쇠쥐 고우라이\" />\n  <loc type=\"3\" id=\"380\" de=\"Moko der Rastlose\" en=\"Moko the Restless\" fr=\"Môko le tourmenté\" ja=\"怨霊モウコ\" cn=\"怨灵猛虎\" ko=\"원령 모우코\" />\n  <loc type=\"3\" id=\"381\" de=\"Oktomammut\" en=\"Octomammoth\" fr=\"octomammouth\" ja=\"オクトマンモス\" cn=\"八足巨妖\" ko=\"옥토매머드\" />\n  <loc type=\"3\" id=\"382\" de=\"Proto-Karfunkel\" en=\"Proto-Carbuncle\" fr=\"Proto-Carbuncle\" ja=\"プロトカーバンクル\" cn=\"原型宝石兽\" ko=\"프로토 카벙클\" />\n  <loc type=\"3\" id=\"383\" de=\"Goldor der Güldene\" en=\"Goldor\" fr=\"Goldor\" ja=\"ゴールドル\" cn=\"金德尔\" ko=\"골도르\" />\n  <loc type=\"3\" id=\"384\" de=\"Shishio\" en=\"Shishio\" fr=\"Shishiô\" ja=\"獅子王\" cn=\"狮子王\" ko=\"사자왕\" />\n  <loc type=\"3\" id=\"385\" de=\"Themis\" en=\"Themis\" fr=\"Thémis\" ja=\"テミス\" cn=\"特弥斯\" ko=\"테미스\" />\n  <loc type=\"3\" id=\"386\" de=\"Enenra\" en=\"Enenra\" fr=\"Enenra\" ja=\"煙々羅\" cn=\"烟烟罗\" ko=\"엔엔라\" />\n  <loc type=\"3\" id=\"387\" de=\"Golbez\" en=\"Golbez\" fr=\"Golbez\" ja=\"ゴルベーザ\" cn=\"高贝扎\" ko=\"골베자\" />\n  <loc type=\"3\" id=\"388\" de=\"Athena\" en=\"Athena\" fr=\"Athéna\" ja=\"アテナ\" cn=\"雅典娜\" ko=\"아테나\" />\n  <loc type=\"3\" id=\"389\" de=\"Ketuduke\" en=\"Ketuduke\" fr=\"Ketuduke\" ja=\"ケトゥドゥケ\" cn=\"凯图嘟凯\" ko=\"케투두케\" />\n  <loc type=\"3\" id=\"390\" de=\"Lala\" en=\"Lala\" fr=\"Lala\" ja=\"ララ\" cn=\"拉拉鲁\" ko=\"랄랄\" />\n  <loc type=\"3\" id=\"391\" de=\"Statice\" en=\"Statice\" fr=\"Statice\" ja=\"スターチス\" cn=\"斯塔缇丝\" ko=\"스타티스\" />\n  <loc type=\"3\" id=\"392\" de=\"Koyo-Koyo\" en=\"PuPu\" fr=\"Koyo K\" ja=\"コヨコヨ\" cn=\"可幽可幽\" ko=\"코요코요\" />\n  <loc type=\"3\" id=\"393\" de=\"Loquloqui\" en=\"Loquloqui\" fr=\"Loquloqui\" ja=\"ロクロクイ\" cn=\"罗库罗库伊\" ko=\"로쿠로쿠이\" />\n  <loc type=\"3\" id=\"394\" de=\"Nophica\" en=\"Nophica\" fr=\"Nophica\" ja=\"ノフィカ\" cn=\"诺菲卡\" ko=\"노피카\" />\n  <loc type=\"3\" id=\"395\" de=\"Althyk\" en=\"Althyk\" fr=\"Althyk\" ja=\"アルジク\" cn=\"阿尔基克\" ko=\"알디크\" />\n  <loc type=\"3\" id=\"396\" de=\"Nymeia\" en=\"Nymeia\" fr=\"Nymeia\" ja=\"ニメーヤ\" cn=\"妮美雅\" ko=\"니메이아\" />\n  <loc type=\"3\" id=\"397\" de=\"Halone\" en=\"Halone\" fr=\"Halone\" ja=\"ハルオーネ\" cn=\"哈罗妮\" ko=\"할로네\" />\n  <loc type=\"3\" id=\"398\" de=\"Thaliak\" en=\"Thaliak\" fr=\"Thaliak\" ja=\"サリャク\" cn=\"沙利亚克\" ko=\"살리아크\" />\n  <loc type=\"3\" id=\"399\" de=\"Llymlaen\" en=\"Llymlaen\" fr=\"Llymlaen\" ja=\"リムレーン\" cn=\"利姆莱茵\" ko=\"리믈렌\" />\n  <loc type=\"3\" id=\"400\" de=\"Oschon\" en=\"Oschon\" fr=\"Oschon\" ja=\"オシュオン\" cn=\"奥修昂\" ko=\"오쉬온\" />\n  <loc type=\"3\" id=\"401\" de=\"Durante\" en=\"Durante\" fr=\"Durante\" ja=\"ドゥランテ\" cn=\"杜兰特\" ko=\"두란테\" />\n  <loc type=\"3\" id=\"402\" de=\"Eulogia\" en=\"Eulogia\" fr=\"Eulogie\" ja=\"エウロギア\" cn=\"欧罗基亚\" ko=\"에울로기아\" />\n  <loc type=\"3\" id=\"403\" de=\"Asura\" en=\"Asura\" fr=\"Asura\" ja=\"アスラ\" cn=\"阿修罗\" ko=\"아수라\" />\n  <loc type=\"3\" id=\"404\" de=\"Zeromus\" en=\"Zeromus\" fr=\"Zeromus\" ja=\"ゼロムス\" cn=\"泽罗姆斯\" ko=\"제로무스\" />\n  <loc type=\"3\" id=\"405\" de=\"Clive Rosfield\" en=\"Clive Rosfield\" fr=\"Clive Rosfield\" ja=\"クライヴ・ロズフィールド\" cn=\"克莱夫·罗兹菲尔德\" ko=\"클라이브 로즈필드\" />\n  <loc type=\"3\" id=\"406\" de=\"Pelupelu\" en=\"Pelupelu\" fr=\"Pelupelu\" ja=\"ペルペル\" cn=\"佩鲁佩鲁族\" ko=\"펠루펠루\" />\n  <loc type=\"3\" id=\"407\" de=\"Alpaka\" en=\"Alpaca\" fr=\"alpaga\" ja=\"アルパカ\" cn=\"羊驼\" ko=\"알파카\" />\n  <loc type=\"3\" id=\"408\" de=\"Moblin\" en=\"Moblin\" fr=\"Mobelin\" ja=\"モブリン\" cn=\"莫布林\" ko=\"모블린\" />\n  <loc type=\"3\" id=\"409\" de=\"Zweigträger\" en=\"Branchbearer\" fr=\"sylveplume\" ja=\"ブランチベアラー\" cn=\"拟鸟枝\" ko=\"나무전령\" />\n  <loc type=\"3\" id=\"410\" de=\"Rroneek\" en=\"Rroneek\" fr=\"rroneek\" ja=\"ロネーク\" cn=\"犎牛\" ko=\"로네크\" />\n  <loc type=\"3\" id=\"411\" de=\"Einheit R8\" en=\"Sentry R8\" fr=\"sentinelle R\" ja=\"セントリーR\" cn=\"哨兵R\" ko=\"보초병 R\" />\n  <loc type=\"3\" id=\"412\" de=\"Auskunftsdrohne\" en=\"Outrunner\" fr=\"trékhoïde\" ja=\"アウトランナー\" cn=\"外勤机\" ko=\"아웃러너\" />\n  <loc type=\"3\" id=\"413\" de=\"Gulool Ja Ja\" en=\"Gulool Ja Ja\" fr=\"Gulool Ja Ja\" ja=\"グルージャジャ\" cn=\"古鲁加加\" ko=\"굴루쟈쟈\" />\n  <loc type=\"3\" id=\"414\" de=\"Schläfrich\" en=\"Drowsie\" fr=\"Somnolent\" ja=\"ドラウジー\" cn=\"瞌睡怪\" ko=\"졸음보\" />\n  <loc type=\"3\" id=\"415\" de=\"Ryoqor Terteh\" en=\"Ryoqor Terteh\" fr=\"Ryoqor Terteh\" ja=\"リョコー・テーテ\" cn=\"辽刻特特雪精\" ko=\"료코 테테\" />\n  <loc type=\"3\" id=\"416\" de=\"Hammerschädel\" en=\"Maulskull\" fr=\"Cabosseur\" ja=\"モールスカル\" cn=\"锤颅巨兵\" ko=\"망치머리 거병\" />\n  <loc type=\"3\" id=\"417\" de=\"Ambrose\" en=\"Ambrose\" fr=\"Ambrose\" ja=\"アンブローズ\" cn=\"安布罗斯\" ko=\"앰브로즈\" />\n  <loc type=\"3\" id=\"418\" de=\"Valigarmanda\" en=\"Valigarmanda\" fr=\"Valigarmanda\" ja=\"ヴァリガルマンダ\" cn=\"艳翼蛇鸟\" ko=\"발리가르만다\" />\n  <loc type=\"3\" id=\"419\" de=\"Zoraal Ja\" en=\"Zoraal Ja\" fr=\"Zoraal Ja\" ja=\"ゾラージャ\" cn=\"佐拉加\" ko=\"조라쟈\" />\n  <loc type=\"3\" id=\"420\" de=\"Ewige Königin\" en=\"Queen Eternal\" fr=\"Reine Éternité\" ja=\"エターナルクイーン\" cn=\"永恒女王\" ko=\"이터널 퀸\" />\n  <loc type=\"3\" id=\"421\" de=\"Schwarze Katze\" en=\"Black Cat\" fr=\"Black Cat\" ja=\"ブラックキャット\" cn=\"黑猫\" ko=\"블랙 캣\" />\n  <loc type=\"3\" id=\"422\" de=\"Otis\" en=\"Otis\" fr=\"Otis\" ja=\"オーティス\" cn=\"欧提斯\" ko=\"오티스\" />\n  <loc type=\"3\" id=\"423\" de=\"Cahciua\" en=\"Cahciua\" fr=\"Cahciua\" ja=\"カフキワ\" cn=\"喀赫奇瓦\" ko=\"카흐키와\" />\n  <loc type=\"3\" id=\"424\" de=\"Wuk Lamat\" en=\"Wuk Lamat\" fr=\"Wuk Lamat\" ja=\"ウクラマト\" cn=\"乌克·拉玛特\" ko=\"우크라마트\" />\n  <loc type=\"3\" id=\"425\" de=\"Sphene\" en=\"Sphene\" fr=\"Sphene\" ja=\"スフェーン\" cn=\"斯菲因\" ko=\"스펜\" />\n  <loc type=\"3\" id=\"426\" de=\"Forschungsleiter Kanilokka\" en=\"Overseer Kanilokka\" fr=\"Kanilokka\" ja=\"所長カニロッカ\" cn=\"卡尼洛喀站长\" ko=\"소장 카닐로카\" />\n  <loc type=\"3\" id=\"427\" de=\"Erzengel MR\" en=\"Ark Angel MR\" fr=\"Ark Angel MR\" ja=\"Ark Angel MR\" cn=\"方舟天使MR\" ko=\"아크 엔젤 MR\" />\n  <loc type=\"3\" id=\"428\" de=\"Erzengel TT\" en=\"Ark Angel TT\" fr=\"Ark Angel TT\" ja=\"Ark Angel TT\" cn=\"方舟天使TT\" ko=\"아크 엔젤 TT\" />\n  <loc type=\"3\" id=\"429\" de=\"Erzengel GK\" en=\"Ark Angel GK\" fr=\"Ark Angel GK\" ja=\"Ark Angel GK\" cn=\"方舟天使GK\" ko=\"아크 엔젤 GK\" />\n  <loc type=\"3\" id=\"430\" de=\"Erzengel HM\" en=\"Ark Angel HM\" fr=\"Ark Angel HM\" ja=\"Ark Angel HM\" cn=\"方舟天使HM\" ko=\"아크 엔젤 HM\" />\n  <loc type=\"3\" id=\"431\" de=\"Erzengel EV\" en=\"Ark Angel EV\" fr=\"Ark Angel EV\" ja=\"Ark Angel EV\" cn=\"方舟天使EV\" ko=\"아크 엔젤 EV\" />\n  <loc type=\"3\" id=\"432\" de=\"Ttokrrone\" en=\"Ttokrrone\" fr=\"Ttokrrone\" ja=\"トクローネ\" cn=\"得酷热涅\" ko=\"토크로네\" />\n  <loc type=\"3\" id=\"433\" de=\"Mica das Magische Mu\" en=\"Mica the Magical Mu\" fr=\"Mica le mu\" ja=\"マイカ・ザ・ムー\" cn=\"亩鼠米卡\" ko=\"마술다람쥐 마이카\" />\n  <loc type=\"3\" id=\"434\" de=\"Prishe\" en=\"Prishe of the Distant Chains\" fr=\"Prishe of the Distant Chains\" ja=\"Prishe of the Distant Chains\" cn=\"遥远的咒缚普利修\" ko=\"아득한 주박의 프리슈\" />\n  <loc type=\"3\" id=\"435\" de=\"Schattenlord\" en=\"Shadow Lord\" fr=\"Shadow Lord\" ja=\"Shadow Lord\" cn=\"暗之王\" ko=\"어둠의 왕\" />\n  <loc type=\"3\" id=\"436\" de=\"Mechalith\" en=\"Valia Pira\" fr=\"Obélisk\" ja=\"ヴァリア・ピラ\" />\n  <loc type=\"3\" id=\"437\" de=\"Ollier\" en=\"Ollier\" fr=\"Ollier\" ja=\"オリアー\" />\n  <loc type=\"3\" id=\"438\" de=\"Zelenia\" en=\"Zelenia\" fr=\"Zelenia\" ja=\"ゼレニア\" />\n  <loc type=\"3\" id=\"439\" de=\"Heulende Klinge\" en=\"Howling Blade\" fr=\"Howling Blade\" ja=\"ハウリングブレード\" />\n  <loc type=\"3\" id=\"440\" de=\"Magitaurus\" en=\"Magitaur\" fr=\"Magitaure\" ja=\"マギタウロス\" />\n  <loc type=\"3\" id=\"441\" de=\"Blauer Blättrich\" en=\"Blue Leafkin\" fr=\"sangfeuille bleu\" ja=\"ブルーリフキン\" />\n  <loc type=\"3\" id=\"442\" de=\"Wivre\" en=\"Wivre\" fr=\"wivre\" ja=\"ウィヴル\" />\n  <loc type=\"3\" id=\"443\" de=\"Doppro\" en=\"Doppro\" fr=\"Doppro\" ja=\"ドプロ族\" />\n  <loc type=\"3\" id=\"444\" de=\"König Elmer III.\" en=\"King Elmer III\" fr=\"Elmer le Roi\" ja=\"キングのエルマー\" />\n  <loc type=\"4\" id=\"1\" de=\"Aurifort Kreuz\" en=\"Aurifort of the Three Clubs\" fr=\"Aurifort l'As de trèfle\" ja=\"クラブのオリフォル\" cn=\"梅花天王欧里福\" ko=\"오리포르\" />\n  <loc type=\"4\" id=\"2\" de=\"Baderon\" en=\"Baderon\" fr=\"Baderon\" ja=\"バデロン\" cn=\"巴德龙\" ko=\"바데론\" />\n  <loc type=\"4\" id=\"3\" de=\"Botan\" en=\"Botan\" fr=\"Botan\" ja=\"ボタン\" cn=\"牡丹\" ko=\"보탄\" />\n  <loc type=\"4\" id=\"4\" de=\"Buscarron\" en=\"Buscarron\" fr=\"Buscarron\" ja=\"バスカロン\" cn=\"巴斯卡隆\" ko=\"버스카론\" />\n  <loc type=\"4\" id=\"5\" de=\"Dominiac\" en=\"Dominiac\" fr=\"Dominiac\" ja=\"ドミニアク\" cn=\"多米尼亚克\" ko=\"도미니아크\" />\n  <loc type=\"4\" id=\"6\" de=\"Elaisse\" en=\"Elaisse\" fr=\"Elaisse\" ja=\"エレイズ\" cn=\"艾蕾兹\" ko=\"엘레이스\" />\n  <loc type=\"4\" id=\"7\" de=\"Ercanbald\" en=\"Ercanbald\" fr=\"Ercanbald\" ja=\"アーカンバルド\" cn=\"阿昆巴尔德\" ko=\"어칸발드\" />\n  <loc type=\"4\" id=\"8\" de=\"F'hobhas\" en=\"F'hobhas\" fr=\"F'hobhas\" ja=\"フ・ホバス\" cn=\"芙·霍芭丝\" ko=\"프호바스\" />\n  <loc type=\"4\" id=\"9\" de=\"Flichoirel [t] Hochnäsig[a]\" en=\"Flichoirel the Lordling\" fr=\"Flichoirel le Seigneurial\" ja=\"高貴なるフリショワレル\" cn=\"高贵的弗里休瓦雷尔\" ko=\"고귀한 플리슈아렐\" />\n  <loc type=\"4\" id=\"10\" de=\"Fufulupa\" en=\"Fufulupa\" fr=\"Fufulupa\" ja=\"フフルパ\" cn=\"弗弗鲁帕\" ko=\"푸푸루파\" />\n  <loc type=\"4\" id=\"11\" de=\"Garima\" en=\"Garima\" fr=\"Garima\" ja=\"ガリマ\" cn=\"伽利摩\" ko=\"가리마\" />\n  <loc type=\"4\" id=\"12\" de=\"Gegeruju\" en=\"Gegeruju\" fr=\"Gegeruju\" ja=\"ゲゲルジュ\" cn=\"格格鲁玖\" ko=\"게게루주\" />\n  <loc type=\"4\" id=\"13\" de=\"Gibrillont\" en=\"Gibrillont\" fr=\"Gibrillont\" ja=\"ジブリオン\" cn=\"吉布里隆\" ko=\"지브리옹\" />\n  <loc type=\"4\" id=\"14\" de=\"Guhtwint Karo\" en=\"Guhtwint of the Three Diamonds\" fr=\"Guhtwint l'As de carreau\" ja=\"ダイヤのグートウィント\" cn=\"方块天王古特文特\" ko=\"구트빈트\" />\n  <loc type=\"4\" id=\"15\" de=\"Gyoei\" en=\"Gyoei\" fr=\"Gyoei\" ja=\"ギョエイ\" cn=\"鱼影\" ko=\"교에이\" />\n  <loc type=\"4\" id=\"16\" de=\"Hab\" en=\"Hab\" fr=\"Hab\" ja=\"ハブ\" cn=\"哈布\" ko=\"하브\" />\n  <loc type=\"4\" id=\"17\" de=\"Hachinan\" en=\"Hachinan\" fr=\"Hachinan\" ja=\"ハチナン\" cn=\"八难\" ko=\"하치난\" />\n  <loc type=\"4\" id=\"18\" de=\"Salon-Meister\" en=\"hall overseer\" fr=\"croupier de l'arène\" ja=\"ルームマスター\" cn=\"对局室管理员\" ko=\"카드 대결장 관리자\" />\n  <loc type=\"4\" id=\"19\" de=\"Helmhart\" en=\"Helmhart\" fr=\"Helmhart\" ja=\"ヘルムハート\" cn=\"海尔姆哈特\" ko=\"헬름하트\" />\n  <loc type=\"4\" id=\"20\" de=\"Hetsukaze\" en=\"Hetsukaze\" fr=\"Hetsukaze\" ja=\"ヘツカゼ\" cn=\"边风\" ko=\"헤츠카제\" />\n  <loc type=\"4\" id=\"21\" de=\"Hokushin\" en=\"Hokushin\" fr=\"Hokushin\" ja=\"ホクシン\" cn=\"北辰\" ko=\"호쿠신\" />\n  <loc type=\"4\" id=\"22\" de=\"Sekretär[p] der Fortemps\" en=\"House Fortemps manservant\" fr=\"serviteur des Fortemps\" ja=\"フォルタン家の執事\" cn=\"福尔唐家的管家\" ko=\"포르탕 가 집사\" />\n  <loc type=\"4\" id=\"23\" de=\"gelangweilt[a] Garlear\" en=\"idle imperial\" fr=\"impérial désœuvré\" ja=\"退屈そうな帝国兵\" cn=\"无所事事的帝国兵\" ko=\"지루해 보이는 제국병\" />\n  <loc type=\"4\" id=\"24\" de=\"flüchtig[a] Garlear\" en=\"imperial deserter\" fr=\"soldat impérial\" ja=\"帝国軍の脱走兵\" cn=\"帝国军逃兵\" ko=\"제국군 탈영병\" />\n  <loc type=\"4\" id=\"25\" de=\"arbeitsfaul[a] Garlear\" en=\"indolent imperial\" fr=\"impérial paresseux\" ja=\"気だるそうな帝国兵\" cn=\"懒散的帝国兵\" ko=\"나른해 보이는 제국 병사\" />\n  <loc type=\"4\" id=\"26\" de=\"Garlond-Mitarbeiter\" en=\"Ironworks hand\" fr=\"employé des Forges\" ja=\"ガーロンド社の調査員\" cn=\"加隆德炼铁厂调查员\" ko=\"갈론드 아이언웍스 조사원\" />\n  <loc type=\"4\" id=\"27\" de=\"Isobe\" en=\"Isobe\" fr=\"Isobe\" ja=\"イソベ\" cn=\"矶边\" ko=\"이소베\" />\n  <loc type=\"4\" id=\"28\" de=\"Joellaut\" en=\"Joellaut\" fr=\"Joellaut\" ja=\"ジョエロー\" cn=\"若约\" ko=\"조엘로\" />\n  <loc type=\"4\" id=\"29\" de=\"Jonas Pik\" en=\"Jonas of the Three Spades\" fr=\"Jonas le Valet de pique\" ja=\"スペードのジョナス\" cn=\"黑桃天王乔纳斯\" ko=\"조나스\" />\n  <loc type=\"4\" id=\"30\" de=\"Kaizan\" en=\"Kaizan\" fr=\"Kaizan\" ja=\"カイザン\" cn=\"海山\" ko=\"카이잔\" />\n  <loc type=\"4\" id=\"31\" de=\"Kikimo\" en=\"Kikimo\" fr=\"Kikimo\" ja=\"キキモ\" cn=\"琪琪茉\" ko=\"키키모\" />\n  <loc type=\"4\" id=\"32\" de=\"König Elmer III.\" en=\"King Elmer III\" fr=\"Elmer le Roi\" ja=\"キングのエルマー\" cn=\"幻卡王埃尔默\" ko=\"엘머\" />\n  <loc type=\"4\" id=\"33\" de=\"Kiuka\" en=\"Kiuka\" fr=\"Kiuka\" ja=\"キウカ\" cn=\"九夏\" ko=\"키우카\" />\n  <loc type=\"4\" id=\"34\" de=\"Klynthota\" en=\"Klynthota\" fr=\"Klynthota\" ja=\"クリントータ\" cn=\"克林托塔\" ko=\"클륀토타\" />\n  <loc type=\"4\" id=\"35\" de=\"Kotokaze\" en=\"Kotokaze\" fr=\"Kotokaze\" ja=\"コトカゼ\" cn=\"琴风\" ko=\"코토카제\" />\n  <loc type=\"4\" id=\"36\" de=\"Landenel\" en=\"Landenel\" fr=\"Landenel\" ja=\"ランドゥネル\" cn=\"朗德内尔\" ko=\"랑드넬\" />\n  <loc type=\"4\" id=\"37\" de=\"Laniaitte\" en=\"Laniaitte\" fr=\"Laniaitte\" ja=\"ラニエット\" cn=\"拉妮艾特\" ko=\"라니에트\" />\n  <loc type=\"4\" id=\"38\" de=\"Lewena\" en=\"Lewena\" fr=\"Lewena\" ja=\"レウェナ\" cn=\"莱维娜\" ko=\"르웨나\" />\n  <loc type=\"4\" id=\"39\" de=\"Linu Vali\" en=\"Linu Vali\" fr=\"Linu Vali\" ja=\"リヌバリ\" cn=\"利努瓦利\" ko=\"리누바리\" />\n  <loc type=\"4\" id=\"40\" de=\"Maisenta\" en=\"Maisenta\" fr=\"Maisenta\" ja=\"黒兎堂 マイセンタ\" cn=\"麦森塔\" ko=\"마이센타\" />\n  <loc type=\"4\" id=\"41\" de=\"Marcechamp\" en=\"Marcechamp\" fr=\"Marcechamp\" ja=\"マルスシャン\" cn=\"马塞尚\" ko=\"마르스샹\" />\n  <loc type=\"4\" id=\"42\" de=\"Marcette\" en=\"Marcette\" fr=\"Marcette\" ja=\"マルセット\" cn=\"马尔塞特\" ko=\"마르세트\" />\n  <loc type=\"4\" id=\"43\" de=\"Marielle\" en=\"Marielle\" fr=\"Marielle\" ja=\"マリエル\" cn=\"玛丽埃勒\" ko=\"마리엘\" />\n  <loc type=\"4\" id=\"44\" de=\"Masatsuchi\" en=\"Masatsuchi\" fr=\"Masatsuchi\" ja=\"マサツチ\" cn=\"正土\" ko=\"마사츠치\" />\n  <loc type=\"4\" id=\"45\" de=\"Vorarbeiter Mogzin\" en=\"Master Mogzin\" fr=\"Mogzin\" ja=\"団長のモグジン\" cn=\"团长莫古京\" ko=\"단장 모그진\" />\n  <loc type=\"4\" id=\"46\" de=\"Memeroon\" en=\"Memeroon\" fr=\"Memeroon\" ja=\"メメルン\" cn=\"梅梅卢恩\" ko=\"메메룬\" />\n  <loc type=\"4\" id=\"47\" de=\"Mero Roggo\" en=\"Mero Roggo\" fr=\"Mero Roggo\" ja=\"メロ・ロッゴ\" cn=\"梅罗·罗格\" ko=\"메로 록고\" />\n  <loc type=\"4\" id=\"48\" de=\"Nächtlicher Tau\" en=\"Midnight Dew\" fr=\"Midnight Dew\" ja=\"ミッドナイト・デュー\" cn=\"米德奈特·迪尤\" ko=\"한밤의 이슬\" />\n  <loc type=\"4\" id=\"49\" de=\"Mimidoa\" en=\"Mimidoa\" fr=\"Mimidoa\" ja=\"ミミドア\" cn=\"弥弥德亚\" ko=\"미미도아\" />\n  <loc type=\"4\" id=\"50\" de=\"Mogmill\" en=\"Mogmill\" fr=\"Mogmill\" ja=\"モグミル\" cn=\"莫古咪尔\" ko=\"모그밀\" />\n  <loc type=\"4\" id=\"51\" de=\"Momodi\" en=\"Momodi\" fr=\"Momodi\" ja=\"モモディ\" cn=\"莫莫蒂\" ko=\"모모디\" />\n  <loc type=\"4\" id=\"52\" de=\"Mordyn\" en=\"Mordyn\" fr=\"Mordyn\" ja=\"モーディン\" cn=\"莫丁\" ko=\"모딘\" />\n  <loc type=\"4\" id=\"53\" de=\"Miounne\" en=\"Mother Miounne\" fr=\"Miounne\" ja=\"ミューヌ\" cn=\"缪恩\" ko=\"뮨\" />\n  <loc type=\"4\" id=\"54\" de=\"Munglig\" en=\"Munglig\" fr=\"Munglig\" ja=\"ムングリグ\" cn=\"蒙力克\" ko=\"문글리그\" />\n  <loc type=\"4\" id=\"55\" de=\"Nell [t] Beschwipst[a]\" en=\"Nell Half-full\" fr=\"Nell la Boit-sans-soif\" ja=\"酔いどれのネル\" cn=\"醉鬼涅尔\" ko=\"고주망태 넬\" />\n  <loc type=\"4\" id=\"56\" de=\"Nigen\" en=\"Nigen\" fr=\"Nigen\" ja=\"ニゲン\" cn=\"尼坚\" ko=\"니겐\" />\n  <loc type=\"4\" id=\"57\" de=\"Noes\" en=\"Noes\" fr=\"Noes\" ja=\"ノエス\" cn=\"诺埃斯\" ko=\"노에스\" />\n  <loc type=\"4\" id=\"58\" de=\"O'kalkaya\" en=\"O'kalkaya\" fr=\"O'kalkaya\" ja=\"オ・カルカヤ\" cn=\"奥·卡尔卡雅\" ko=\"오칼카야\" />\n  <loc type=\"4\" id=\"59\" de=\"Ogodei\" en=\"Ogodei\" fr=\"Ogodei\" ja=\"オゴデイ\" cn=\"窝哥台\" ko=\"오고데이\" />\n  <loc type=\"4\" id=\"60\" de=\"Ourdilic\" en=\"Ourdilic\" fr=\"Ourdilic\" ja=\"オルディリク\" cn=\"奥尔蒂里克\" ko=\"아우어딜릭\" />\n  <loc type=\"4\" id=\"61\" de=\"Piralnaut\" en=\"Piralnaut\" fr=\"Piralnaut\" ja=\"ピラルノー\" cn=\"皮拉尔诺\" ko=\"피랄노\" />\n  <loc type=\"4\" id=\"62\" de=\"Stolzer Hirsch [t] Neureich[a]\" en=\"Prideful Stag\" fr=\"Prideful Stag le Parvenu\" ja=\"成金のプライド・フルスタグ\" cn=\"暴发户普莱多·福尔斯塔格\" ko=\"졸부 거만한 수사슴\" />\n  <loc type=\"4\" id=\"63\" de=\"R'ashaht Rhiki\" en=\"R'ashaht Rhiki\" fr=\"R'ashaht Rhiki\" ja=\"ル・アシャ大甲佐\" cn=\"尔·阿夏\" ko=\"흑와단 대령 르아샤\" />\n  <loc type=\"4\" id=\"64\" de=\"Rotschnabel-Materialverwalter\" en=\"Redbill storeboy\" fr=\"intendant des Becs-rouges\" ja=\"レッドビルの資材係\" cn=\"赤喙的资材负责人\" ko=\"붉은부리 조달원\" />\n  <loc type=\"4\" id=\"65\" de=\"Roger\" en=\"Roger\" fr=\"Roger\" ja=\"ロジャー\" cn=\"罗杰\" ko=\"로저\" />\n  <loc type=\"4\" id=\"66\" de=\"Rowena\" en=\"Rowena\" fr=\"Rowena\" ja=\"ロウェナ\" cn=\"罗薇娜\" ko=\"로웨나\" />\n  <loc type=\"4\" id=\"67\" de=\"Ruhtwyda Herz\" en=\"Ruhtwyda of the Three Hearts\" fr=\"Ruhtwyda la Dame de cœur\" ja=\"ハートのルヒトウィダ\" cn=\"红心天王鲁希特威达\" ko=\"루트뷔다\" />\n  <loc type=\"4\" id=\"68\" de=\"Seika\" en=\"Seika\" fr=\"Seika\" ja=\"セイカ\" cn=\"青花\" ko=\"세이카\" />\n  <loc type=\"4\" id=\"69\" de=\"Sezul Totoloq\" en=\"Sezul Totoloc\" fr=\"Sezul Totoloc\" ja=\"セズル・トトロック\" cn=\"赛兹尔·托托罗克\" ko=\"세즐 토톨록\" />\n  <loc type=\"4\" id=\"70\" de=\"Swift\" en=\"Swift\" fr=\"Swift Ryder\" ja=\"スウィフト大闘佐\" cn=\"斯威夫特\" ko=\"불멸대 대령 스위프트\" />\n  <loc type=\"4\" id=\"71\" de=\"Tappklix\" en=\"Tapklix\" fr=\"Tapklix\" ja=\"タップクリクス\" cn=\"塔普克里克斯\" ko=\"탭클릭스\" />\n  <loc type=\"4\" id=\"72\" de=\"Trachtoum\" en=\"Trachtoum\" fr=\"Trachtoum\" ja=\"トラッハトゥーム\" cn=\"特拉哈涂姆\" ko=\"트라흐툼\" />\n  <loc type=\"4\" id=\"73\" de=\"Triple Triad-Meisterin\" en=\"Triple Triad master\" fr=\"croupière\" ja=\"トリプルトライアドマスター\" cn=\"幻卡大师\" ko=\"트리플 트라이어드 마스터\" />\n  <loc type=\"4\" id=\"74\" de=\"Tsuzura\" en=\"Tsuzura\" fr=\"Tsuzura\" ja=\"ツヅラ\" cn=\"葛笼\" ko=\"츠즈라\" />\n  <loc type=\"4\" id=\"75\" de=\"Erdiger Strom\" en=\"Umber Torrent\" fr=\"Umber Torrent\" ja=\"アンバー・トレント\" cn=\"安博·托伦特\" ko=\"암갈색 급류\" />\n  <loc type=\"4\" id=\"76\" de=\"Ushiogi\" en=\"Ushiogi\" fr=\"Ushiogi\" ja=\"ウシオギ\" cn=\"潮木\" ko=\"우시오기\" />\n  <loc type=\"4\" id=\"77\" de=\"Macher\" en=\"Vath deftarm\" fr=\"Futé\" ja=\"ウデキキ\" cn=\"好身手\" ko=\"으뜸이\" />\n  <loc type=\"4\" id=\"78\" de=\"Nanka-Mampfa\" en=\"voracious Vath\" fr=\"Vathe vorace\" ja=\"ナンカイーター\" cn=\"啥都吃\" ko=\"낭카 먹보\" />\n  <loc type=\"4\" id=\"79\" de=\"Vorsaile Heuloix\" en=\"Vorsaile Heuloix\" fr=\"Vorsaile Heuloix\" ja=\"ボルセル大牙佐\" cn=\"沃尔赛勒\" ko=\"쌍사당 대령 보르셀\" />\n  <loc type=\"4\" id=\"80\" de=\"Wawalago\" en=\"Wawalago\" fr=\"Wawalago\" ja=\"ワワラゴ\" cn=\"瓦瓦拉哥\" ko=\"와와라고\" />\n  <loc type=\"4\" id=\"81\" de=\"Wymond\" en=\"Wymond\" fr=\"Wymond\" ja=\"ワイモンド\" cn=\"维蒙德\" ko=\"와이먼드\" />\n  <loc type=\"4\" id=\"82\" de=\"Wyra Lyehga [t] Geschickt[a]\" en=\"Wyra “Greenhands” Lyehga\" fr=\"Wyra Lyehga la Main verte\" ja=\"緑の手のウィラ・リエーガ\" cn=\"绿之手维拉·莉艾佳\" ko=\"초록손 위라 리에가\" />\n  <loc type=\"4\" id=\"83\" de=\"Yayake\" en=\"Yayake\" fr=\"Yayake\" ja=\"呪術士ギルド受付 ヤヤケ\" cn=\"雅雅凯\" ko=\"야야케\" />\n  <loc type=\"4\" id=\"84\" de=\"Gelber Mond\" en=\"Yellow Moon\" fr=\"Yellow Moon\" ja=\"イエロー・ムーン\" cn=\"耶萝·姆恩\" ko=\"노란 달\" />\n  <loc type=\"4\" id=\"85\" de=\"Yusui\" en=\"Yusui\" fr=\"Yusui\" ja=\"ユウスイ\" cn=\"涌水\" ko=\"유우스이\" />\n  <loc type=\"4\" id=\"86\" de=\"Glynard\" en=\"Glynard\" fr=\"Glynard\" ja=\"グリナード\" cn=\"格林纳德\" ko=\"글리나드\" />\n  <loc type=\"4\" id=\"87\" de=\"Gyuf Uin\" en=\"Gyuf Uin\" fr=\"Gyuf Uin\" ja=\"ギュフ＝ウィン\" cn=\"久付·乌因\" ko=\"규프 윈\" />\n  <loc type=\"4\" id=\"88\" de=\"Hargra\" en=\"Hargra\" fr=\"Hargra\" ja=\"ハルグラ\" cn=\"哈格菈\" ko=\"하르그라\" />\n  <loc type=\"4\" id=\"89\" de=\"Drery\" en=\"Drery\" fr=\"Drery\" ja=\"ドレーリー\" cn=\"德雷利\" ko=\"드레리\" />\n  <loc type=\"4\" id=\"90\" de=\"Ibenart\" en=\"Ibenart\" fr=\"Ibenart\" ja=\"イベナート\" cn=\"伊贝纳特\" ko=\"이베나트\" />\n  <loc type=\"4\" id=\"91\" de=\"Lamlyn\" en=\"Lamlyn\" fr=\"Lamlyn\" ja=\"ラムリン\" cn=\"拉姆林\" ko=\"라믈린\" />\n  <loc type=\"4\" id=\"92\" de=\"Saushs Koal\" en=\"Saushs Koal\" fr=\"Saushs Koal\" ja=\"サウスィー・コール\" cn=\"萨乌嘶·涳尔\" ko=\"사우쉬 콜\" />\n  <loc type=\"4\" id=\"93\" de=\"Grewenn\" en=\"Grewenn\" fr=\"Grewenn\" ja=\"グレウェン\" cn=\"格雷文\" ko=\"그레웬\" />\n  <loc type=\"4\" id=\"94\" de=\"Eo Sigun\" en=\"Eo Sigun\" fr=\"Eo Sigun\" ja=\"エオ＝シグン\" cn=\"艾奥·希坤\" ko=\"에오 시군\" />\n  <loc type=\"4\" id=\"95\" de=\"Redard\" en=\"Redard\" fr=\"Redard\" ja=\"レダード\" cn=\"瑞达德\" ko=\"레다드\" />\n  <loc type=\"4\" id=\"96\" de=\"Hanagasa\" en=\"Hanagasa\" fr=\"Hanagasa\" ja=\"ハナガサ\" cn=\"花笠\" ko=\"하나가사\" />\n  <loc type=\"4\" id=\"97\" de=\"Cobleva\" en=\"Cobleva\" fr=\"Cobleva\" ja=\"コブレヴァ\" cn=\"科布蕾瓦\" ko=\"코블레바\" />\n  <loc type=\"4\" id=\"98\" de=\"desertiert[a] Legionär\" en=\"furtive former imperial\" fr=\"soldat impérial plein d'entrain\" ja=\"威勢のいい帝国兵\" cn=\"很有气势的帝国兵\" ko=\"주눅들지 않는 제국병\" />\n  <loc type=\"4\" id=\"99\" de=\"Arsieu\" en=\"Arsieu\" fr=\"Arsieu\" ja=\"アルシウ\" cn=\"阿尔希乌\" ko=\"아르시우\" />\n  <loc type=\"4\" id=\"100\" de=\"Lewto-Sue\" en=\"Lewto-Sue\" fr=\"Lewto-Sue\" ja=\"リュート・スー\" cn=\"琉托·素\" ko=\"류토 수\" />\n  <loc type=\"4\" id=\"101\" de=\"Droyn\" en=\"Droyn\" fr=\"Droyn\" ja=\"ドロイン\" cn=\"德罗因\" ko=\"드로인\" />\n  <loc type=\"4\" id=\"102\" de=\"Sladkey\" en=\"Sladkey\" fr=\"Sladkey\" ja=\"スラッドキー\" cn=\"斯莱德基\" ko=\"슬라드키\" />\n  <loc type=\"4\" id=\"103\" de=\"Aiglephine\" en=\"Aiglephine\" fr=\"Aiglephine\" ja=\"エグルフィーヌ\" cn=\"艾格勒菲娜\" ko=\"애글핀\" />\n  <loc type=\"4\" id=\"104\" de=\"Qetanur\" en=\"Qetanur\" fr=\"Qetanur\" ja=\"ケタヌール\" cn=\"凯塔奴尔\" ko=\"케타누르\" />\n  <loc type=\"4\" id=\"105\" de=\"erfolgreich[a] Legionär\" en=\"worldly imperial\" fr=\"Garlemaldais avec de l'entregent\" ja=\"世渡り上手な帝国兵\" cn=\"懂世故的帝国兵\" ko=\"처세에 능한 제국 병사\" />\n  <loc type=\"4\" id=\"106\" de=\"Mehryde\" en=\"Mehryde\" fr=\"Mehryde\" ja=\"メリード\" cn=\"梅丽德\" ko=\"메리드\" />\n  <loc type=\"4\" id=\"107\" de=\"Cheatingway\" en=\"Cheatingway\" fr=\"Cheatingway\" ja=\"チーティングウェイ\" cn=\"诡行威\" ko=\"치팅웨이\" />\n  <loc type=\"4\" id=\"108\" de=\"Celia\" en=\"Celia\" fr=\"Celia\" ja=\"セリア\" cn=\"赛丽亚\" ko=\"셀리아\" />\n  <loc type=\"4\" id=\"109\" de=\"Prudence\" en=\"Prudence\" fr=\"Prudence\" ja=\"プルーデンス\" cn=\"普鲁登斯\" ko=\"프루덴스\" />\n  <loc type=\"4\" id=\"110\" de=\"Ghasa\" en=\"Ghasa\" fr=\"Ghasa\" ja=\"ガーサ\" cn=\"伽娑\" ko=\"가사\" />\n  <loc type=\"4\" id=\"111\" de=\"Kilfufu\" en=\"Kilfufu\" fr=\"Kilfufu\" ja=\"キルフフ\" cn=\"\" />\n  <loc type=\"4\" id=\"112\" de=\"Kilfufu\" en=\"Kilfufu\" fr=\"Kilfufu\" ja=\"キルフフ\" cn=\"奇尔芙芙\" ko=\"킬푸푸\" />\n  <loc type=\"4\" id=\"113\" de=\"Gamingway\" en=\"Gamingway\" fr=\"Gamingway\" ja=\"ゲーミングウェイ\" cn=\"游兴威\" ko=\"게이밍웨이\" />\n  <loc type=\"4\" id=\"114\" de=\"Ruissenaud\" en=\"Ruissenaud\" fr=\"Ruissenaud\" ja=\"ルイスノ\" cn=\"吕斯诺\" ko=\"루이스노\" />\n  <loc type=\"4\" id=\"115\" de=\"Tokimori\" en=\"Tokimori\" fr=\"Tokimori\" ja=\"トキモリ\" cn=\"时守\" ko=\"토키모리\" />\n  <loc type=\"4\" id=\"116\" de=\"Ylaire\" en=\"Ylaire\" fr=\"Ylaire\" ja=\"イレアー\" cn=\"伊莱尔\" ko=\"일레어\" />\n  <loc type=\"4\" id=\"117\" de=\"Maillart\" en=\"Maillart\" fr=\"Maillart\" ja=\"マリアルト\" cn=\"马利亚尔特\" ko=\"마야르\" />\n  <loc type=\"4\" id=\"118\" de=\"Nyikweni\" en=\"Nyikweni\" fr=\"Nyikweni\" ja=\"ニーケニ\" cn=\"尼凯尼\" ko=\"니퀘니\" />\n  <loc type=\"4\" id=\"119\" de=\"Wopli\" en=\"Wopli\" fr=\"Wopli\" ja=\"ウォーペリ\" cn=\"沃皮利\" ko=\"워펠리\" />\n  <loc type=\"4\" id=\"120\" de=\"Warsowok\" en=\"Warsowok\" fr=\"Warsowok\" ja=\"ワーソウォク\" cn=\"瓦索沃坷\" ko=\"와소워크\" />\n  <loc type=\"4\" id=\"121\" de=\"Br'uk Noq\" en=\"Br'uk Noq'\" fr=\"Br'uk Noq\" ja=\"ブルクノック\" cn=\"布鲁克·诺可\" ko=\"부르크노크\" />\n  <loc type=\"4\" id=\"122\" de=\"Luwyawa\" en=\"Luwyawa\" fr=\"Luwyawa\" ja=\"ルーヤワ\" cn=\"鲁亚瓦\" ko=\"루야와\" />\n  <loc type=\"4\" id=\"123\" de=\"Uataaye\" en=\"Uataaye\" fr=\"Uataaye\" ja=\"ワターエ\" cn=\"瓦塔艾\" ko=\"와타아예\" />\n  <loc type=\"4\" id=\"124\" de=\"Larisa\" en=\"Larisa\" fr=\"Larisa\" ja=\"ラリサ\" cn=\"菈莉萨\" ko=\"라리사\" />\n  <loc type=\"4\" id=\"125\" de=\"Gavoll Ja\" en=\"Gavoll Ja\" fr=\"Gavoll Ja\" ja=\"ガヴォージャ\" cn=\"嘎乌加\" ko=\"가보쟈\" />\n  <loc type=\"4\" id=\"126\" de=\"Pawkukwe\" en=\"Pawkukwe\" fr=\"Pawkukwe\" ja=\"パークケ\" cn=\"帕库凯\" ko=\"파쿠퀘\" />\n  <loc type=\"4\" id=\"127\" de=\"Miitso\" en=\"Miitso\" fr=\"Miitso\" ja=\"ミーゾ\" cn=\"密泽\" ko=\"미이조\" />\n  <loc type=\"4\" id=\"128\" de=\"Heimtückisches Wiesel\" en=\"Malevolent Weasel\" fr=\"Malevolent Weasel\" ja=\"マレヴォレント・ウィーゼル\" cn=\"\" ko=\"\" />\n  <loc type=\"4\" id=\"129\" de=\"Pudeel Ja\" en=\"Pudeel Ja\" fr=\"Pudeel Ja\" ja=\"プデージャ\" />\n  <loc type=\"5\" id=\"1\" de=\"Gold Saucer\" en=\"The Gold Saucer\" fr=\"Gold Saucer\" ja=\"ゴールドソーサー\" cn=\"金碟游乐场\" ko=\"골드 소서\" />\n  <loc type=\"5\" id=\"2\" de=\"Obere Decks\" en=\"Limsa Lominsa Upper Decks\" fr=\"Limsa Lominsa - Le Tillac\" ja=\"リムサ・ロミンサ：上甲板層\" cn=\"利姆萨·罗敏萨上层甲板\" ko=\"림사 로민사 상층 갑판\" />\n  <loc type=\"5\" id=\"3\" de=\"Kugane\" en=\"Kugane\" fr=\"Kugane\" ja=\"クガネ市街\" cn=\"黄金港城区\" ko=\"쿠가네\" />\n  <loc type=\"5\" id=\"4\" de=\"Südwald\" en=\"South Shroud\" fr=\"Forêt du sud\" ja=\"黒衣森：南部森林\" cn=\"黑衣森林南部林区\" ko=\"검은장막 숲 남부삼림\" />\n  <loc type=\"5\" id=\"5\" de=\"Westliches Hochland von Coerthas\" en=\"Coerthas Western Highlands\" fr=\"Hautes terres du Coerthas occidental\" ja=\"クルザス西部高地\" cn=\"库尔札斯西部高地\" ko=\"커르다스 서부고지\" />\n  <loc type=\"5\" id=\"6\" de=\"Strebewerk\" en=\"The Pillars\" fr=\"Ishgard - Les Contreforts\" ja=\"イシュガルド：上層\" cn=\"伊修加德砥柱层\" ko=\"이슈가르드 상층\" />\n  <loc type=\"5\" id=\"7\" de=\"Rhalgrs Wacht\" en=\"Rhalgr's Reach\" fr=\"L'Étendue de Rhalgr\" ja=\"ラールガーズリーチ\" cn=\"神拳痕\" ko=\"랄거의 손길\" />\n  <loc type=\"5\" id=\"8\" de=\"Zentrales Thanalan\" en=\"Central Thanalan\" fr=\"Thanalan central\" ja=\"中央ザナラーン\" cn=\"中萨纳兰\" ko=\"중부 다날란\" />\n  <loc type=\"5\" id=\"9\" de=\"Weltensalon\" en=\"The Battlehall\" fr=\"Arène Triple Triade\" ja=\"カードバトルルーム\" cn=\"幻卡对局室\" ko=\"카드 대결장\" />\n  <loc type=\"5\" id=\"10\" de=\"Westliches Thanalan\" en=\"Western Thanalan\" fr=\"Thanalan occidental\" ja=\"西ザナラーン\" cn=\"西萨纳兰\" ko=\"서부 다날란\" />\n  <loc type=\"5\" id=\"11\" de=\"Abanisches Grenzland\" en=\"The Fringes\" fr=\"Les Marges\" ja=\"ギラバニア辺境地帯\" cn=\"基拉巴尼亚边区\" ko=\"기라바니아 변방지대\" />\n  <loc type=\"5\" id=\"12\" de=\"Östliches La Noscea\" en=\"Eastern La Noscea\" fr=\"Noscea orientale\" ja=\"東ラノシア\" cn=\"东拉诺西亚\" ko=\"동부 라노시아\" />\n  <loc type=\"5\" id=\"13\" de=\"Fundamente\" en=\"Foundation\" fr=\"Ishgard - L'Assise\" ja=\"イシュガルド：下層\" cn=\"伊修加德基础层\" ko=\"이슈가르드 하층\" />\n  <loc type=\"5\" id=\"14\" de=\"Gold Saucer\" en=\"The Gold Saucer\" fr=\"Gold Saucer\" ja=\"ゴールドソーサー\" cn=\"金碟游乐场\" ko=\"골드 소서\" />\n  <loc type=\"5\" id=\"15\" de=\"Yanxia\" en=\"Yanxia\" fr=\"Yanxia\" ja=\"ヤンサ\" cn=\"延夏\" ko=\"얀샤\" />\n  <loc type=\"5\" id=\"16\" de=\"Südliches Thanalan\" en=\"Southern Thanalan\" fr=\"Thanalan méridional\" ja=\"南ザナラーン\" cn=\"南萨纳兰\" ko=\"남부 다날란\" />\n  <loc type=\"5\" id=\"17\" de=\"Yanxia\" en=\"Yanxia\" fr=\"Yanxia\" ja=\"ヤンサ\" cn=\"延夏\" ko=\"얀샤\" />\n  <loc type=\"5\" id=\"18\" de=\"Weltensalon\" en=\"The Battlehall\" fr=\"Arène Triple Triade\" ja=\"カードバトルルーム\" cn=\"幻卡对局室\" ko=\"카드 대결장\" />\n  <loc type=\"5\" id=\"19\" de=\"Östliches Thanalan\" en=\"Eastern Thanalan\" fr=\"Thanalan oriental\" ja=\"東ザナラーン\" cn=\"东萨纳兰\" ko=\"동부 다날란\" />\n  <loc type=\"5\" id=\"20\" de=\"Kugane\" en=\"Kugane\" fr=\"Kugane\" ja=\"クガネ市街\" cn=\"黄金港城区\" ko=\"쿠가네\" />\n  <loc type=\"5\" id=\"21\" de=\"Kugane\" en=\"Kugane\" fr=\"Kugane\" ja=\"クガネ市街\" cn=\"黄金港城区\" ko=\"쿠가네\" />\n  <loc type=\"5\" id=\"22\" de=\"Anwesen der Fortemps\" en=\"Fortemps Manor\" fr=\"Manoir des Fortemps\" ja=\"フォルタン伯爵邸\" cn=\"福尔唐伯爵府\" ko=\"포르탕 저택\" />\n  <loc type=\"5\" id=\"23\" de=\"Azys Lla\" en=\"Azys Lla\" fr=\"Azys Lla\" ja=\"アジス・ラー\" cn=\"魔大陆阿济兹拉\" ko=\"아지스 라\" />\n  <loc type=\"5\" id=\"24\" de=\"Das Fenn\" en=\"The Lochs\" fr=\"Les Lacs\" ja=\"ギラバニア湖畔地帯\" cn=\"基拉巴尼亚湖区\" ko=\"기라바니아 호반지대\" />\n  <loc type=\"5\" id=\"25\" de=\"Mor Dhona\" en=\"Mor Dhona\" fr=\"Mor Dhona\" ja=\"モードゥナ\" cn=\"摩杜纳\" ko=\"모르도나\" />\n  <loc type=\"5\" id=\"26\" de=\"Rhalgrs Wacht\" en=\"Rhalgr's Reach\" fr=\"L'Étendue de Rhalgr\" ja=\"ラールガーズリーチ\" cn=\"神拳痕\" ko=\"랄거의 손길\" />\n  <loc type=\"5\" id=\"27\" de=\"Rubinsee\" en=\"The Ruby Sea\" fr=\"Mer de Rubis\" ja=\"紅玉海\" cn=\"红玉海\" ko=\"홍옥해\" />\n  <loc type=\"5\" id=\"28\" de=\"Zentrales Hochland von Coerthas\" en=\"Coerthas Central Highlands\" fr=\"Hautes terres du Coerthas central\" ja=\"クルザス中央高地\" cn=\"库尔札斯中央高地\" ko=\"커르다스 중앙고지\" />\n  <loc type=\"5\" id=\"29\" de=\"Gold Saucer\" en=\"The Gold Saucer\" fr=\"Gold Saucer\" ja=\"ゴールドソーサー\" cn=\"金碟游乐场\" ko=\"골드 소서\" />\n  <loc type=\"5\" id=\"30\" de=\"Rubinsee\" en=\"The Ruby Sea\" fr=\"Mer de Rubis\" ja=\"紅玉海\" cn=\"红玉海\" ko=\"홍옥해\" />\n  <loc type=\"5\" id=\"31\" de=\"Kugane\" en=\"Kugane\" fr=\"Kugane\" ja=\"クガネ市街\" cn=\"黄金港城区\" ko=\"쿠가네\" />\n  <loc type=\"5\" id=\"32\" de=\"Gold Saucer\" en=\"The Gold Saucer\" fr=\"Gold Saucer\" ja=\"ゴールドソーサー\" cn=\"金碟游乐场\" ko=\"골드 소서\" />\n  <loc type=\"5\" id=\"33\" de=\"Domanische Enklave\" en=\"The Doman Enclave\" fr=\"Quartier enclavé de Doma\" ja=\"ドマ町人地\" cn=\"多玛飞地\" ko=\"도마 도읍지\" />\n  <loc type=\"5\" id=\"34\" de=\"Mor Dhona\" en=\"Mor Dhona\" fr=\"Mor Dhona\" ja=\"モードゥナ\" cn=\"摩杜纳\" ko=\"모르도나\" />\n  <loc type=\"5\" id=\"35\" de=\"Kugane\" en=\"Kugane\" fr=\"Kugane\" ja=\"クガネ市街\" cn=\"黄金港城区\" ko=\"쿠가네\" />\n  <loc type=\"5\" id=\"36\" de=\"Südwald\" en=\"South Shroud\" fr=\"Forêt du sud\" ja=\"黒衣森：南部森林\" cn=\"黑衣森林南部林区\" ko=\"검은장막 숲 남부삼림\" />\n  <loc type=\"5\" id=\"37\" de=\"Abalathisches Wolkenmeer\" en=\"The Sea of Clouds\" fr=\"L'Écume des cieux d'Abalathia\" ja=\"アバラシア雲海\" cn=\"阿巴拉提亚云海\" ko=\"아발라시아 구름바다\" />\n  <loc type=\"5\" id=\"38\" de=\"Gold Saucer\" en=\"The Gold Saucer\" fr=\"Gold Saucer\" ja=\"ゴールドソーサー\" cn=\"金碟游乐场\" ko=\"골드 소서\" />\n  <loc type=\"5\" id=\"39\" de=\"Abalathisches Wolkenmeer\" en=\"The Sea of Clouds\" fr=\"L'Écume des cieux d'Abalathia\" ja=\"アバラシア雲海\" cn=\"阿巴拉提亚云海\" ko=\"아발라시아 구름바다\" />\n  <loc type=\"5\" id=\"40\" de=\"Neu-Gridania\" en=\"New Gridania\" fr=\"Nouvelle Gridania\" ja=\"グリダニア：新市街\" cn=\"格里达尼亚新街\" ko=\"그리다니아 신시가지\" />\n  <loc type=\"5\" id=\"41\" de=\"Dravanisches Vorland\" en=\"The Dravanian Forelands\" fr=\"Avant-pays dravanien\" ja=\"高地ドラヴァニア\" cn=\"龙堡参天高地\" ko=\"고지 드라바니아\" />\n  <loc type=\"5\" id=\"42\" de=\"Tiefer Wald\" en=\"Central Shroud\" fr=\"Forêt centrale\" ja=\"黒衣森：中央森林\" cn=\"黑衣森林中央林区\" ko=\"검은장막 숲 중부삼림\" />\n  <loc type=\"5\" id=\"43\" de=\"Abalathisches Wolkenmeer\" en=\"The Sea of Clouds\" fr=\"L'Écume des cieux d'Abalathia\" ja=\"アバラシア雲海\" cn=\"阿巴拉提亚云海\" ko=\"아발라시아 구름바다\" />\n  <loc type=\"5\" id=\"44\" de=\"Yanxia\" en=\"Yanxia\" fr=\"Yanxia\" ja=\"ヤンサ\" cn=\"延夏\" ko=\"얀샤\" />\n  <loc type=\"5\" id=\"45\" de=\"Wallende Nebel\" en=\"The Churning Mists\" fr=\"L'Écume des cieux de Dravania\" ja=\"ドラヴァニア雲海\" cn=\"翻云雾海\" ko=\"드라바니아 구름바다\" />\n  <loc type=\"5\" id=\"46\" de=\"Oberes La Noscea\" en=\"Upper La Noscea\" fr=\"Haute-Noscea\" ja=\"高地ラノシア\" cn=\"拉诺西亚高地\" ko=\"고지 라노시아\" />\n  <loc type=\"5\" id=\"47\" de=\"Dravanisches Hinterland\" en=\"The Dravanian Hinterlands\" fr=\"Arrière-pays dravanien\" ja=\"低地ドラヴァニア\" cn=\"龙堡内陆低地\" ko=\"저지 드라바니아\" />\n  <loc type=\"5\" id=\"48\" de=\"Frohehalde\" en=\"Idyllshire\" fr=\"Idyllée\" ja=\"イディルシャイア\" cn=\"田园郡\" ko=\"이딜샤이어\" />\n  <loc type=\"5\" id=\"49\" de=\"Unteres La Noscea\" en=\"Lower La Noscea\" fr=\"Basse-Noscea\" ja=\"低地ラノシア\" cn=\"拉诺西亚低地\" ko=\"저지 라노시아\" />\n  <loc type=\"5\" id=\"50\" de=\"Wallende Nebel\" en=\"The Churning Mists\" fr=\"L'Écume des cieux de Dravania\" ja=\"ドラヴァニア雲海\" cn=\"翻云雾海\" ko=\"드라바니아 구름바다\" />\n  <loc type=\"5\" id=\"51\" de=\"Nald-Kreuzgang\" en=\"Ul'dah - Steps of Nald\" fr=\"Ul'dah - Faubourg de Nald\" ja=\"ウルダハ：ナル回廊\" cn=\"乌尔达哈现世回廊\" ko=\"울다하 날 회랑\" />\n  <loc type=\"5\" id=\"52\" de=\"Obere Decks\" en=\"Limsa Lominsa Upper Decks\" fr=\"Limsa Lominsa - Le Tillac\" ja=\"リムサ・ロミンサ：上甲板層\" cn=\"利姆萨·罗敏萨上层甲板\" ko=\"림사 로민사 상층 갑판\" />\n  <loc type=\"5\" id=\"53\" de=\"Neu-Gridania\" en=\"New Gridania\" fr=\"Nouvelle Gridania\" ja=\"グリダニア：新市街\" cn=\"格里达尼亚新街\" ko=\"그리다니아 신시가지\" />\n  <loc type=\"5\" id=\"54\" de=\"Azim-Steppe\" en=\"The Azim Steppe\" fr=\"Steppe d'Azim\" ja=\"アジムステップ\" cn=\"太阳神草原\" ko=\"아짐 대초원\" />\n  <loc type=\"5\" id=\"55\" de=\"Weltensalon\" en=\"The Battlehall\" fr=\"Arène Triple Triade\" ja=\"カードバトルルーム\" cn=\"幻卡对局室\" ko=\"카드 대결장\" />\n  <loc type=\"5\" id=\"56\" de=\"Azim-Steppe\" en=\"The Azim Steppe\" fr=\"Steppe d'Azim\" ja=\"アジムステップ\" cn=\"太阳神草原\" ko=\"아짐 대초원\" />\n  <loc type=\"5\" id=\"57\" de=\"Alt-Gridania\" en=\"Old Gridania\" fr=\"Vieille Gridania\" ja=\"グリダニア：旧市街\" cn=\"格里达尼亚旧街\" ko=\"그리다니아 구시가지\" />\n  <loc type=\"5\" id=\"58\" de=\"Obere Decks\" en=\"Limsa Lominsa Upper Decks\" fr=\"Limsa Lominsa - Le Tillac\" ja=\"リムサ・ロミンサ：上甲板層\" cn=\"利姆萨·罗敏萨上层甲板\" ko=\"림사 로민사 상층 갑판\" />\n  <loc type=\"5\" id=\"59\" de=\"Azim-Steppe\" en=\"The Azim Steppe\" fr=\"Steppe d'Azim\" ja=\"アジムステップ\" cn=\"太阳神草原\" ko=\"아짐 대초원\" />\n  <loc type=\"5\" id=\"60\" de=\"Zentrales Hochland von Coerthas\" en=\"Coerthas Central Highlands\" fr=\"Hautes terres du Coerthas central\" ja=\"クルザス中央高地\" cn=\"库尔札斯中央高地\" ko=\"커르다스 중앙고지\" />\n  <loc type=\"5\" id=\"61\" de=\"Ostwald\" en=\"East Shroud\" fr=\"Forêt de l'est\" ja=\"黒衣森：東部森林\" cn=\"黑衣森林东部林区\" ko=\"검은장막 숲 동부삼림\" />\n  <loc type=\"5\" id=\"62\" de=\"Weltensalon\" en=\"The Battlehall\" fr=\"Arène Triple Triade\" ja=\"カードバトルルーム\" cn=\"幻卡对局室\" ko=\"카드 대결장\" />\n  <loc type=\"5\" id=\"63\" de=\"Obere Decks\" en=\"Limsa Lominsa Upper Decks\" fr=\"Limsa Lominsa - Le Tillac\" ja=\"リムサ・ロミンサ：上甲板層\" cn=\"利姆萨·罗敏萨上层甲板\" ko=\"림사 로민사 상층 갑판\" />\n  <loc type=\"5\" id=\"64\" de=\"Frohehalde\" en=\"Idyllshire\" fr=\"Idyllée\" ja=\"イディルシャイア\" cn=\"田园郡\" ko=\"이딜샤이어\" />\n  <loc type=\"5\" id=\"65\" de=\"Zentrales Thanalan\" en=\"Central Thanalan\" fr=\"Thanalan central\" ja=\"中央ザナラーン\" cn=\"中萨纳兰\" ko=\"중부 다날란\" />\n  <loc type=\"5\" id=\"66\" de=\"Mor Dhona\" en=\"Mor Dhona\" fr=\"Mor Dhona\" ja=\"モードゥナ\" cn=\"摩杜纳\" ko=\"모르도나\" />\n  <loc type=\"5\" id=\"67\" de=\"Gold Saucer\" en=\"The Gold Saucer\" fr=\"Gold Saucer\" ja=\"ゴールドソーサー\" cn=\"金碟游乐场\" ko=\"골드 소서\" />\n  <loc type=\"5\" id=\"68\" de=\"Frohehalde\" en=\"Idyllshire\" fr=\"Idyllée\" ja=\"イディルシャイア\" cn=\"田园郡\" ko=\"이딜샤이어\" />\n  <loc type=\"5\" id=\"69\" de=\"Nordwald\" en=\"North Shroud\" fr=\"Forêt du nord\" ja=\"黒衣森：北部森林\" cn=\"黑衣森林北部林区\" ko=\"검은장막 숲 북부삼림\" />\n  <loc type=\"5\" id=\"70\" de=\"Nald-Kreuzgang\" en=\"Ul'dah - Steps of Nald\" fr=\"Ul'dah - Faubourg de Nald\" ja=\"ウルダハ：ナル回廊\" cn=\"乌尔达哈现世回廊\" ko=\"울다하 날 회랑\" />\n  <loc type=\"5\" id=\"71\" de=\"Dravanisches Hinterland\" en=\"The Dravanian Hinterlands\" fr=\"Arrière-pays dravanien\" ja=\"低地ドラヴァニア\" cn=\"龙堡内陆低地\" ko=\"저지 드라바니아\" />\n  <loc type=\"5\" id=\"72\" de=\"Unteres La Noscea\" en=\"Lower La Noscea\" fr=\"Basse-Noscea\" ja=\"低地ラノシア\" cn=\"拉诺西亚低地\" ko=\"저지 라노시아\" />\n  <loc type=\"5\" id=\"73\" de=\"Gold Saucer\" en=\"The Gold Saucer\" fr=\"Gold Saucer\" ja=\"ゴールドソーサー\" cn=\"金碟游乐场\" ko=\"골드 소서\" />\n  <loc type=\"5\" id=\"74\" de=\"Rubinsee\" en=\"The Ruby Sea\" fr=\"Mer de Rubis\" ja=\"紅玉海\" cn=\"红玉海\" ko=\"홍옥해\" />\n  <loc type=\"5\" id=\"75\" de=\"Das Fenn\" en=\"The Lochs\" fr=\"Les Lacs\" ja=\"ギラバニア湖畔地帯\" cn=\"基拉巴尼亚湖区\" ko=\"기라바니아 호반지대\" />\n  <loc type=\"5\" id=\"76\" de=\"Rubinsee\" en=\"The Ruby Sea\" fr=\"Mer de Rubis\" ja=\"紅玉海\" cn=\"红玉海\" ko=\"홍옥해\" />\n  <loc type=\"5\" id=\"77\" de=\"Dravanisches Vorland\" en=\"The Dravanian Forelands\" fr=\"Avant-pays dravanien\" ja=\"高地ドラヴァニア\" cn=\"龙堡参天高地\" ko=\"고지 드라바니아\" />\n  <loc type=\"5\" id=\"78\" de=\"Dravanisches Vorland\" en=\"The Dravanian Forelands\" fr=\"Avant-pays dravanien\" ja=\"高地ドラヴァニア\" cn=\"龙堡参天高地\" ko=\"고지 드라바니아\" />\n  <loc type=\"5\" id=\"79\" de=\"Neu-Gridania\" en=\"New Gridania\" fr=\"Nouvelle Gridania\" ja=\"グリダニア：新市街\" cn=\"格里达尼亚新街\" ko=\"그리다니아 신시가지\" />\n  <loc type=\"5\" id=\"80\" de=\"Untere Decks\" en=\"Limsa Lominsa Lower Decks\" fr=\"Limsa Lominsa - L'Entrepont\" ja=\"リムサ・ロミンサ：下甲板層\" cn=\"利姆萨·罗敏萨下层甲板\" ko=\"림사 로민사 하층 갑판\" />\n  <loc type=\"5\" id=\"81\" de=\"Nald-Kreuzgang\" en=\"Ul'dah - Steps of Nald\" fr=\"Ul'dah - Faubourg de Nald\" ja=\"ウルダハ：ナル回廊\" cn=\"乌尔达哈现世回廊\" ko=\"울다하 날 회랑\" />\n  <loc type=\"5\" id=\"82\" de=\"Weltensalon\" en=\"The Battlehall\" fr=\"Arène Triple Triade\" ja=\"カードバトルルーム\" cn=\"幻卡对局室\" ko=\"카드 대결장\" />\n  <loc type=\"5\" id=\"83\" de=\"Nald-Kreuzgang\" en=\"Ul'dah - Steps of Nald\" fr=\"Ul'dah - Faubourg de Nald\" ja=\"ウルダハ：ナル回廊\" cn=\"乌尔达哈现世回廊\" ko=\"울다하 날 회랑\" />\n  <loc type=\"5\" id=\"84\" de=\"Thal-Kreuzgang\" en=\"Ul'dah - Steps of Thal\" fr=\"Ul'dah - Faubourg de Thal\" ja=\"ウルダハ：ザル回廊\" cn=\"乌尔达哈来生回廊\" ko=\"울다하 달 회랑\" />\n  <loc type=\"5\" id=\"85\" de=\"Rubinsee\" en=\"The Ruby Sea\" fr=\"Mer de Rubis\" ja=\"紅玉海\" cn=\"红玉海\" ko=\"홍옥해\" />\n  <loc type=\"5\" id=\"86\" de=\"Crystarium\" en=\"The Crystarium\" fr=\"Cristarium\" ja=\"クリスタリウム市街\" cn=\"水晶都\" ko=\"크리스타리움\" />\n  <loc type=\"5\" id=\"87\" de=\"Il Mheg\" en=\"Il Mheg\" fr=\"Il Mheg\" ja=\"イル・メグ\" cn=\"伊尔美格\" ko=\"일 메그\" />\n  <loc type=\"5\" id=\"88\" de=\"Der Große Wald Rak'tika\" en=\"The Rak'tika Greatwood\" fr=\"Rak'tika\" ja=\"ラケティカ大森林\" cn=\"拉凯提卡大森林\" ko=\"라케티카 대삼림\" />\n  <loc type=\"5\" id=\"89\" de=\"Amh Araeng\" en=\"Amh Araeng\" fr=\"Amh Araeng\" ja=\"アム・アレーン\" cn=\"安穆·艾兰\" ko=\"아므 아랭\" />\n  <loc type=\"5\" id=\"90\" de=\"Kholusia\" en=\"Kholusia\" fr=\"Kholusia\" ja=\"コルシア島\" cn=\"珂露西亚岛\" ko=\"콜루시아 섬\" />\n  <loc type=\"5\" id=\"91\" de=\"Seenland\" en=\"Lakeland\" fr=\"Grand-Lac\" ja=\"レイクランド\" cn=\"雷克兰德\" ko=\"레이크랜드\" />\n  <loc type=\"5\" id=\"92\" de=\"Tempest\" en=\"The Tempest\" fr=\"La Tempête\" ja=\"テンペスト\" cn=\"黑风海\" ko=\"템페스트\" />\n  <loc type=\"5\" id=\"93\" de=\"Eulmore\" en=\"Eulmore\" fr=\"Eulmore\" ja=\"ユールモア市街\" cn=\"游末邦\" ko=\"율모어\" />\n  <loc type=\"5\" id=\"94\" de=\"Il Mheg\" en=\"Il Mheg\" fr=\"Il Mheg\" ja=\"イル・メグ\" cn=\"伊尔美格\" ko=\"일 메그\" />\n  <loc type=\"5\" id=\"95\" de=\"Der Große Wald Rak'tika\" en=\"The Rak'tika Greatwood\" fr=\"Rak'tika\" ja=\"ラケティカ大森林\" cn=\"拉凯提卡大森林\" ko=\"라케티카 대삼림\" />\n  <loc type=\"5\" id=\"96\" de=\"Kugane\" en=\"Kugane\" fr=\"Kugane\" ja=\"クガネ市街\" cn=\"黄金港城区\" ko=\"쿠가네\" />\n  <loc type=\"5\" id=\"97\" de=\"Seenland\" en=\"Lakeland\" fr=\"Grand-Lac\" ja=\"レイクランド\" cn=\"雷克兰德\" ko=\"레이크랜드\" />\n  <loc type=\"5\" id=\"98\" de=\"Wendeklipp\" en=\"Terncliff\" fr=\"Rocasternes\" ja=\"ターンクリフ\" cn=\"燕鸥崖\" ko=\"턴클리프\" />\n  <loc type=\"5\" id=\"99\" de=\"Gangos\" en=\"Gangos\" fr=\"Gangos\" ja=\"ガンゴッシュ\" cn=\"甘戈斯\" ko=\"강고스\" />\n  <loc type=\"5\" id=\"100\" de=\"Amh Araeng\" en=\"Amh Araeng\" fr=\"Amh Araeng\" ja=\"アム・アレーン\" cn=\"安穆·艾兰\" ko=\"아므 아랭\" />\n  <loc type=\"5\" id=\"101\" de=\"Thal-Kreuzgang\" en=\"Ul'dah - Steps of Thal\" fr=\"Ul'dah - Faubourg de Thal\" ja=\"ウルダハ：ザル回廊\" cn=\"乌尔达哈来生回廊\" ko=\"울다하 달 회랑\" />\n  <loc type=\"5\" id=\"102\" de=\"Gangos\" en=\"Gangos\" fr=\"Gangos\" ja=\"ガンゴッシュ\" cn=\"甘戈斯\" ko=\"강고스\" />\n  <loc type=\"5\" id=\"103\" de=\"Labyrinthos\" en=\"Labyrinthos\" fr=\"Le Labyrinthos\" ja=\"ラヴィリンソス\" cn=\"迷津\" ko=\"라비린토스\" />\n  <loc type=\"5\" id=\"104\" de=\"Thavnair\" en=\"Thavnair\" fr=\"Thavnair\" ja=\"サベネア島\" cn=\"萨维奈岛\" ko=\"사베네어 섬\" />\n  <loc type=\"5\" id=\"105\" de=\"Garlemald\" en=\"Garlemald\" fr=\"Garlemald\" ja=\"ガレマルド\" cn=\"加雷马\" ko=\"갈레말드\" />\n  <loc type=\"5\" id=\"106\" de=\"Radz-at-Han\" en=\"Radz-at-Han\" fr=\"Radz-at-Han\" ja=\"ラザハン\" cn=\"拉札罕\" ko=\"라자한\" />\n  <loc type=\"5\" id=\"107\" de=\"Alt-Sharlayan\" en=\"Old Sharlayan\" fr=\"Vieille Sharlayan\" ja=\"オールド・シャーレアン\" cn=\"旧萨雷安\" ko=\"올드 샬레이안\" />\n  <loc type=\"5\" id=\"108\" de=\"Alt-Sharlayan\" en=\"Old Sharlayan\" fr=\"Vieille Sharlayan\" ja=\"オールド・シャーレアン\" cn=\"旧萨雷安\" ko=\"올드 샬레이안\" />\n  <loc type=\"5\" id=\"109\" de=\"Mor Dhona\" en=\"Mor Dhona\" fr=\"Mor Dhona\" ja=\"モードゥナ\" cn=\"摩杜纳\" ko=\"모르도나\" />\n  <loc type=\"5\" id=\"110\" de=\"Thavnair\" en=\"Thavnair\" fr=\"Thavnair\" ja=\"サベネア島\" cn=\"萨维奈岛\" ko=\"사베네어 섬\" />\n  <loc type=\"5\" id=\"111\" de=\"Nald-Kreuzgang\" en=\"Ul'dah - Steps of Nald\" fr=\"Ul'dah - Faubourg de Nald\" ja=\"ウルダハ：ナル回廊\" cn=\"乌尔达哈现世回廊\" ko=\"울다하 날 회랑\" />\n  <loc type=\"5\" id=\"112\" de=\"Nald-Kreuzgang\" en=\"Ul'dah - Steps of Nald\" fr=\"Ul'dah - Faubourg de Nald\" ja=\"ウルダハ：ナル回廊\" cn=\"乌尔达哈现世回廊\" ko=\"울다하 날 회랑\" />\n  <loc type=\"5\" id=\"113\" de=\"Mare Lamentorum\" en=\"Mare Lamentorum\" fr=\"Mare Lamentorum\" ja=\"嘆きの海\" cn=\"叹息海\" ko=\"비탄의 바다\" />\n  <loc type=\"5\" id=\"114\" de=\"Labyrinthos\" en=\"Labyrinthos\" fr=\"Le Labyrinthos\" ja=\"ラヴィリンソス\" cn=\"迷津\" ko=\"라비린토스\" />\n  <loc type=\"5\" id=\"115\" de=\"Kugane\" en=\"Kugane\" fr=\"Kugane\" ja=\"クガネ市街\" cn=\"黄金港城区\" ko=\"쿠가네\" />\n  <loc type=\"5\" id=\"116\" de=\"Alt-Gridania\" en=\"Old Gridania\" fr=\"Vieille Gridania\" ja=\"グリダニア：旧市街\" cn=\"格里达尼亚旧街\" ko=\"그리다니아 구시가지\" />\n  <loc type=\"5\" id=\"117\" de=\"Alt-Sharlayan\" en=\"Old Sharlayan\" fr=\"Vieille Sharlayan\" ja=\"オールド・シャーレアン\" cn=\"旧萨雷安\" ko=\"올드 샬레이안\" />\n  <loc type=\"5\" id=\"118\" de=\"Tuliyollal\" en=\"Tuliyollal\" fr=\"Tuliyollal\" ja=\"トライヨラ\" cn=\"图莱尤拉\" ko=\"툴라이욜라\" />\n  <loc type=\"5\" id=\"119\" de=\"Urqopacha\" en=\"Urqopacha\" fr=\"Urqopacha\" ja=\"オルコ・パチャ\" cn=\"奥阔帕恰山\" ko=\"오르코 파차\" />\n  <loc type=\"5\" id=\"120\" de=\"Kozama'uka\" en=\"Kozama'uka\" fr=\"Kozama'uka\" ja=\"コザマル・カ\" cn=\"克扎玛乌卡湿地\" ko=\"코자말루 카\" />\n  <loc type=\"5\" id=\"121\" de=\"Yak T'el\" en=\"Yak T'el\" fr=\"Yak T'el\" ja=\"ヤクテル樹海\" cn=\"亚克特尔树海\" ko=\"야크텔 밀림\" />\n  <loc type=\"5\" id=\"122\" de=\"Shaaloani\" en=\"Shaaloani\" fr=\"Shaaloani\" ja=\"シャーローニ荒野\" cn=\"夏劳尼荒野\" ko=\"샬로니 황야\" />\n  <loc type=\"5\" id=\"123\" de=\"Ewiges Erbe\" en=\"Heritage Found\" fr=\"L'Hoirie recouvrée\" ja=\"ヘリテージファウンド\" cn=\"遗产之地\" ko=\"헤리티지 파운드\" />\n  <loc type=\"5\" id=\"124\" de=\"Lösung Neun\" en=\"Solution Nine\" fr=\"Solution Neuf\" ja=\"ソリューション・ナイン\" cn=\"九号解决方案\" ko=\"솔루션 나인\" />\n  <loc type=\"5\" id=\"125\" de=\"Tuliyollal\" en=\"Tuliyollal\" fr=\"Tuliyollal\" ja=\"トライヨラ\" cn=\"图莱尤拉\" ko=\"툴라이욜라\" />\n  <loc type=\"5\" id=\"126\" de=\"Unter-Jeuno\" en=\"Lower Jeuno\" fr=\"Bas Jeuno\" ja=\"ジュノ下層\" cn=\"朱诺下层\" ko=\"쥬노 하층\" />\n  <loc type=\"5\" id=\"127\" de=\"Unter-Jeuno\" en=\"Lower Jeuno\" fr=\"Bas Jeuno\" ja=\"ジュノ下層\" cn=\"朱诺下层\" ko=\"쥬노 하층\" />\n  <loc type=\"5\" id=\"128\" de=\"Gold Saucer\" en=\"The Gold Saucer\" fr=\"Gold Saucer\" ja=\"ゴールドソーサー\" cn=\"金碟游乐场\" ko=\"골드 소서\" />\n  <loc type=\"5\" id=\"129\" de=\"Yak T'el\" en=\"Yak T'el\" fr=\"Yak T'el\" ja=\"ヤクテル樹海\" cn=\"亚克特尔树海\" ko=\"야크텔 밀림\" />\n  <loc type=\"6\" id=\"0\" de=\"Manderville-Masters\" en=\"the Manderville Tournament of Champions\" fr=\"le Championnat des Manderville\" ja=\"マンダヴィル・チャンピオンシップ\" cn=\"曼德维尔锦标赛\" ko=\"맨더빌배 선수권 대회\" />\n  <loc type=\"6\" id=\"1\" de=\"Großer Preis von Nymeia\" en=\"the Spinner's Pull\" fr=\"le Grand Prix de la Fileuse\" ja=\"星神ニメーヤ賞典\" cn=\"命运神大奖赛\" ko=\"별의 신 니메이아배\" />\n  <loc type=\"6\" id=\"2\" de=\"Orran-Durai-Ehrenturnier\" en=\"the Durai Memorial\" fr=\"le Jubilé Orran Durai\" ja=\"オーラン記念\" cn=\"奥兰纪念赛\" ko=\"오란 기념 대회\" />\n  <loc type=\"6\" id=\"3\" de=\"Rowena-Pokal\" en=\"the Rowena Cup Classic\" fr=\"la Coupe Rowena\" ja=\"ロウェナ商会杯\" cn=\"罗薇娜杯\" ko=\"로웨나 상회배\" />\n</root>"
  },
  {
    "path": "assets/data/npcs.xml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <npc id=\"1\" mx=\"4\" my=\"8\">\n    <deckA id0=\"51\" id1=\"24\" id2=\"37\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"27\" id1=\"20\" id2=\"16\" id3=\"12\" id4=\"0\" />\n    <rule id=\"3\" />\n    <rule id=\"11\" />\n    <reward id=\"27\" />\n    <reward id=\"12\" />\n  </npc>\n  <npc id=\"2\" mx=\"12\" my=\"11\">\n    <deckA id0=\"28\" id1=\"29\" id2=\"30\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"49\" id1=\"21\" id2=\"27\" id3=\"18\" id4=\"17\" />\n    <rule id=\"3\" />\n    <reward id=\"29\" />\n    <reward id=\"18\" />\n  </npc>\n  <npc id=\"3\" mx=\"8\" my=\"15\">\n    <deckA id0=\"227\" id1=\"236\" id2=\"199\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"226\" id1=\"223\" id2=\"34\" id3=\"0\" id4=\"0\" />\n    <rule id=\"8\" />\n    <rule id=\"1\" />\n    <reward id=\"227\" />\n    <reward id=\"236\" />\n    <reward id=\"223\" />\n    <reward id=\"226\" />\n  </npc>\n  <npc id=\"4\" mx=\"18\" my=\"20\">\n    <deckA id0=\"66\" id1=\"52\" id2=\"43\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"48\" id1=\"42\" id2=\"39\" id3=\"44\" id4=\"0\" />\n    <rule id=\"11\" />\n    <reward id=\"48\" />\n    <reward id=\"44\" />\n    <reward id=\"23\" />\n  </npc>\n  <npc id=\"5\" mx=\"17\" my=\"23\">\n    <deckA id0=\"101\" id1=\"96\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"89\" id1=\"88\" id2=\"86\" id3=\"38\" id4=\"93\" />\n    <rule id=\"11\" />\n    <reward id=\"101\" />\n    <reward id=\"86\" />\n  </npc>\n  <npc id=\"6\" mx=\"8\" my=\"11\">\n    <deckA id0=\"102\" id1=\"0\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"91\" id1=\"65\" id2=\"66\" id3=\"63\" id4=\"67\" />\n    <rule id=\"12\" />\n    <rule id=\"8\" />\n    <reward id=\"102\" />\n    <reward id=\"142\" />\n  </npc>\n  <npc id=\"7\" mx=\"10\" my=\"10\">\n    <deckA id0=\"48\" id1=\"186\" id2=\"170\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"34\" id1=\"14\" id2=\"5\" id3=\"0\" id4=\"0\" />\n    <reward id=\"186\" />\n    <reward id=\"86\" />\n  </npc>\n  <npc id=\"8\" mx=\"24\" my=\"14\">\n    <deckA id0=\"45\" id1=\"36\" id2=\"14\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"23\" id1=\"13\" id2=\"18\" id3=\"5\" id4=\"6\" />\n    <rule id=\"3\" />\n    <reward id=\"36\" />\n  </npc>\n  <npc id=\"9\" mx=\"4\" my=\"4\">\n    <deckA id0=\"167\" id1=\"139\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"94\" id1=\"38\" id2=\"86\" id3=\"95\" id4=\"45\" />\n    <reward id=\"167\" />\n    <reward id=\"155\" />\n  </npc>\n  <npc id=\"10\" mx=\"23\" my=\"17\">\n    <deckA id0=\"33\" id1=\"28\" id2=\"2\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"67\" id1=\"46\" id2=\"19\" id3=\"20\" id4=\"13\" />\n    <rule id=\"3\" />\n    <reward id=\"46\" />\n    <reward id=\"25\" />\n  </npc>\n  <npc id=\"11\" mx=\"28\" my=\"20\">\n    <deckA id0=\"138\" id1=\"185\" id2=\"48\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"108\" id1=\"145\" id2=\"19\" id3=\"205\" id4=\"0\" />\n    <rule id=\"8\" />\n    <rule id=\"9\" />\n    <reward id=\"185\" />\n    <reward id=\"205\" />\n  </npc>\n  <npc id=\"12\" mx=\"34\" my=\"31\">\n    <deckA id0=\"63\" id1=\"56\" id2=\"49\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"65\" id1=\"66\" id2=\"55\" id3=\"31\" id4=\"30\" />\n    <rule id=\"13\" />\n    <rule id=\"4\" />\n    <reward id=\"56\" />\n    <reward id=\"49\" />\n  </npc>\n  <npc id=\"13\" mx=\"13\" my=\"12\">\n    <deckA id0=\"89\" id1=\"56\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"88\" id1=\"62\" id2=\"50\" id3=\"58\" id4=\"19\" />\n    <rule id=\"1\" />\n    <reward id=\"88\" />\n  </npc>\n  <npc id=\"14\" mx=\"4\" my=\"7\">\n    <deckA id0=\"38\" id1=\"26\" id2=\"6\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"27\" id1=\"13\" id2=\"11\" id3=\"8\" id4=\"0\" />\n    <rule id=\"3\" />\n    <rule id=\"8\" />\n    <reward id=\"27\" />\n    <reward id=\"13\" />\n  </npc>\n  <npc id=\"15\" mx=\"16\" my=\"32\">\n    <deckA id0=\"92\" id1=\"141\" id2=\"183\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"169\" id1=\"17\" id2=\"5\" id3=\"248\" id4=\"0\" />\n    <rule id=\"5\" />\n    <rule id=\"8\" />\n    <reward id=\"183\" />\n    <reward id=\"248\" />\n  </npc>\n  <npc id=\"16\" mx=\"25\" my=\"41\">\n    <deckA id0=\"62\" id1=\"45\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"67\" id1=\"52\" id2=\"46\" id3=\"34\" id4=\"3\" />\n    <rule id=\"10\" />\n    <reward id=\"62\" />\n    <reward id=\"46\" />\n    <reward id=\"45\" />\n  </npc>\n  <npc id=\"17\" mx=\"27\" my=\"13\">\n    <deckA id0=\"238\" id1=\"192\" id2=\"180\" id3=\"176\" id4=\"0\" />\n    <deckV id0=\"175\" id1=\"198\" id2=\"201\" id3=\"156\" id4=\"171\" />\n    <rule id=\"9\" />\n    <rule id=\"6\" />\n    <reward id=\"238\" />\n    <reward id=\"201\" />\n  </npc>\n  <npc id=\"18\" mx=\"4\" my=\"4\">\n    <deckA id0=\"73\" id1=\"120\" id2=\"59\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"65\" id1=\"58\" id2=\"95\" id3=\"0\" id4=\"0\" />\n    <rule id=\"6\" />\n    <rule id=\"12\" />\n    <reward id=\"73\" />\n    <reward id=\"160\" />\n  </npc>\n  <npc id=\"19\" mx=\"20\" my=\"21\">\n    <deckA id0=\"45\" id1=\"34\" id2=\"11\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"60\" id1=\"41\" id2=\"22\" id3=\"27\" id4=\"2\" />\n    <rule id=\"3\" />\n    <reward id=\"22\" />\n    <reward id=\"34\" />\n  </npc>\n  <npc id=\"20\" mx=\"8\" my=\"14\">\n    <deckA id0=\"243\" id1=\"237\" id2=\"242\" id3=\"245\" id4=\"0\" />\n    <deckV id0=\"226\" id1=\"236\" id2=\"227\" id3=\"223\" id4=\"0\" />\n    <rule id=\"8\" />\n    <rule id=\"7\" />\n    <reward id=\"243\" />\n    <reward id=\"237\" />\n    <reward id=\"242\" />\n    <reward id=\"245\" />\n  </npc>\n  <npc id=\"21\" mx=\"14\" my=\"11\">\n    <deckA id0=\"231\" id1=\"239\" id2=\"211\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"183\" id1=\"187\" id2=\"62\" id3=\"184\" id4=\"0\" />\n    <rule id=\"8\" />\n    <rule id=\"13\" />\n    <reward id=\"239\" />\n    <reward id=\"231\" />\n  </npc>\n  <npc id=\"22\" mx=\"6\" my=\"6\">\n    <deckA id0=\"121\" id1=\"133\" id2=\"134\" id3=\"95\" id4=\"93\" />\n    <deckV id0=\"0\" id1=\"0\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <rule id=\"3\" />\n    <rule id=\"8\" />\n    <reward id=\"121\" />\n    <reward id=\"133\" />\n    <reward id=\"134\" />\n  </npc>\n  <npc id=\"23\" mx=\"28\" my=\"31\">\n    <deckA id0=\"83\" id1=\"97\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"47\" id1=\"40\" id2=\"91\" id3=\"32\" id4=\"92\" />\n    <rule id=\"13\" />\n    <rule id=\"12\" />\n    <reward id=\"91\" />\n    <reward id=\"119\" />\n    <reward id=\"151\" />\n    <reward id=\"148\" />\n  </npc>\n  <npc id=\"24\" mx=\"33\" my=\"30\">\n    <deckA id0=\"66\" id1=\"167\" id2=\"198\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"180\" id1=\"88\" id2=\"48\" id3=\"207\" id4=\"0\" />\n    <rule id=\"6\" />\n    <reward id=\"198\" />\n    <reward id=\"207\" />\n    <reward id=\"192\" />\n  </npc>\n  <npc id=\"25\" mx=\"12\" my=\"17\">\n    <deckA id0=\"64\" id1=\"61\" id2=\"47\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"42\" id1=\"31\" id2=\"32\" id3=\"37\" id4=\"27\" />\n    <rule id=\"9\" />\n    <reward id=\"64\" />\n    <reward id=\"47\" />\n    <reward id=\"31\" />\n    <reward id=\"32\" />\n  </npc>\n  <npc id=\"26\" mx=\"14\" my=\"11\">\n    <deckA id0=\"247\" id1=\"197\" id2=\"240\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"13\" id1=\"85\" id2=\"33\" id3=\"58\" id4=\"100\" />\n    <rule id=\"1\" />\n    <rule id=\"1\" />\n    <reward id=\"247\" />\n    <reward id=\"240\" />\n    <reward id=\"197\" />\n  </npc>\n  <npc id=\"27\" mx=\"21\" my=\"20\">\n    <deckA id0=\"220\" id1=\"60\" id2=\"62\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"212\" id1=\"140\" id2=\"167\" id3=\"151\" id4=\"152\" />\n    <rule id=\"8\" />\n    <rule id=\"13\" />\n    <reward id=\"212\" />\n    <reward id=\"220\" />\n  </npc>\n  <npc id=\"28\" mx=\"13\" my=\"16\">\n    <deckA id0=\"38\" id1=\"33\" id2=\"2\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"59\" id1=\"26\" id2=\"14\" id3=\"12\" id4=\"15\" />\n    <rule id=\"3\" />\n    <reward id=\"59\" />\n    <reward id=\"15\" />\n  </npc>\n  <npc id=\"29\" mx=\"5\" my=\"7\">\n    <deckA id0=\"23\" id1=\"3\" id2=\"4\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"26\" id1=\"11\" id2=\"15\" id3=\"20\" id4=\"0\" />\n    <rule id=\"2\" />\n    <rule id=\"7\" />\n    <reward id=\"15\" />\n    <reward id=\"20\" />\n  </npc>\n  <npc id=\"30\" mx=\"22\" my=\"9\">\n    <deckA id0=\"193\" id1=\"91\" id2=\"25\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"149\" id1=\"23\" id2=\"121\" id3=\"94\" id4=\"0\" />\n    <rule id=\"13\" />\n    <rule id=\"10\" />\n    <reward id=\"193\" />\n  </npc>\n  <npc id=\"31\" mx=\"9\" my=\"14\">\n    <deckA id0=\"219\" id1=\"139\" id2=\"64\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"58\" id1=\"129\" id2=\"113\" id3=\"163\" id4=\"0\" />\n    <rule id=\"13\" />\n    <rule id=\"6\" />\n    <reward id=\"219\" />\n  </npc>\n  <npc id=\"32\" mx=\"4\" my=\"8\">\n    <deckA id0=\"61\" id1=\"50\" id2=\"45\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"62\" id1=\"41\" id2=\"39\" id3=\"49\" id4=\"0\" />\n    <rule id=\"8\" />\n    <reward id=\"61\" />\n    <reward id=\"45\" />\n  </npc>\n  <npc id=\"33\" mx=\"6\" my=\"5\">\n    <deckA id0=\"202\" id1=\"153\" id2=\"59\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"56\" id1=\"92\" id2=\"84\" id3=\"0\" id4=\"0\" />\n    <rule id=\"12\" />\n    <reward id=\"202\" />\n  </npc>\n  <npc id=\"34\" mx=\"30\" my=\"14\">\n    <deckA id0=\"140\" id1=\"135\" id2=\"132\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"58\" id1=\"33\" id2=\"47\" id3=\"0\" id4=\"0\" />\n    <rule id=\"7\" />\n    <rule id=\"8\" />\n    <reward id=\"132\" />\n    <reward id=\"135\" />\n    <reward id=\"140\" />\n    <reward id=\"150\" />\n  </npc>\n  <npc id=\"35\" mx=\"10\" my=\"10\">\n    <deckA id0=\"87\" id1=\"187\" id2=\"2\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"28\" id1=\"29\" id2=\"30\" id3=\"0\" id4=\"0\" />\n    <rule id=\"7\" />\n    <reward id=\"187\" />\n  </npc>\n  <npc id=\"36\" mx=\"17\" my=\"28\">\n    <deckA id0=\"52\" id1=\"41\" id2=\"66\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"40\" id1=\"32\" id2=\"13\" id3=\"0\" id4=\"0\" />\n    <rule id=\"6\" />\n    <rule id=\"12\" />\n    <reward id=\"52\" />\n    <reward id=\"41\" />\n  </npc>\n  <npc id=\"37\" mx=\"17\" my=\"37\">\n    <deckA id0=\"103\" id1=\"98\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"104\" id1=\"93\" id2=\"58\" id3=\"89\" id4=\"96\" />\n    <rule id=\"7\" />\n    <rule id=\"8\" />\n    <reward id=\"103\" />\n    <reward id=\"104\" />\n  </npc>\n  <npc id=\"38\" mx=\"5\" my=\"6\">\n    <deckA id0=\"73\" id1=\"70\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"74\" id1=\"52\" id2=\"42\" id3=\"40\" id4=\"41\" />\n    <rule id=\"6\" />\n    <rule id=\"12\" />\n    <reward id=\"70\" />\n    <reward id=\"72\" />\n    <reward id=\"73\" />\n  </npc>\n  <npc id=\"39\" mx=\"7\" my=\"14\">\n    <deckA id0=\"98\" id1=\"82\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"123\" id1=\"103\" id2=\"81\" id3=\"86\" id4=\"104\" />\n    <rule id=\"5\" />\n    <rule id=\"12\" />\n    <reward id=\"123\" />\n    <reward id=\"98\" />\n    <reward id=\"147\" />\n  </npc>\n  <npc id=\"40\" mx=\"12\" my=\"11\">\n    <deckA id0=\"36\" id1=\"7\" id2=\"10\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"24\" id1=\"16\" id2=\"8\" id3=\"0\" id4=\"0\" />\n    <rule id=\"2\" />\n    <reward id=\"16\" />\n    <reward id=\"8\" />\n  </npc>\n  <npc id=\"41\" mx=\"32\" my=\"23\">\n    <deckA id0=\"14\" id1=\"81\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"90\" id1=\"3\" id2=\"55\" id3=\"43\" id4=\"84\" />\n    <rule id=\"5\" />\n    <rule id=\"8\" />\n    <reward id=\"90\" />\n  </npc>\n  <npc id=\"42\" mx=\"17\" my=\"19\">\n    <deckA id0=\"52\" id1=\"53\" id2=\"57\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"44\" id1=\"47\" id2=\"42\" id3=\"38\" id4=\"35\" />\n    <rule id=\"12\" />\n    <reward id=\"42\" />\n    <reward id=\"35\" />\n  </npc>\n  <npc id=\"43\" mx=\"15\" my=\"38\">\n    <deckA id0=\"63\" id1=\"86\" id2=\"65\" id3=\"93\" id4=\"96\" />\n    <deckV id0=\"0\" id1=\"0\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <rule id=\"11\" />\n    <reward id=\"93\" />\n    <reward id=\"143\" />\n  </npc>\n  <npc id=\"44\" mx=\"30\" my=\"20\">\n    <deckA id0=\"218\" id1=\"136\" id2=\"97\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"211\" id1=\"115\" id2=\"123\" id3=\"0\" id4=\"0\" />\n    <rule id=\"7\" />\n    <reward id=\"211\" />\n    <reward id=\"218\" />\n  </npc>\n  <npc id=\"45\" mx=\"16\" my=\"29\">\n    <deckA id0=\"153\" id1=\"43\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"90\" id1=\"99\" id2=\"108\" id3=\"124\" id4=\"20\" />\n    <rule id=\"7\" />\n    <rule id=\"8\" />\n    <reward id=\"153\" />\n    <reward id=\"146\" />\n    <reward id=\"43\" />\n    <reward id=\"124\" />\n  </npc>\n  <npc id=\"46\" mx=\"15\" my=\"24\">\n    <deckA id0=\"37\" id1=\"14\" id2=\"1\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"21\" id1=\"2\" id2=\"7\" id3=\"12\" id4=\"0\" />\n    <rule id=\"2\" />\n    <reward id=\"37\" />\n    <reward id=\"14\" />\n    <reward id=\"2\" />\n    <reward id=\"128\" />\n  </npc>\n  <npc id=\"47\" mx=\"13\" my=\"37\">\n    <deckA id0=\"251\" id1=\"94\" id2=\"161\" id3=\"130\" id4=\"0\" />\n    <deckV id0=\"92\" id1=\"107\" id2=\"106\" id3=\"111\" id4=\"233\" />\n    <rule id=\"8\" />\n    <rule id=\"1\" />\n    <reward id=\"251\" />\n    <reward id=\"94\" />\n    <reward id=\"161\" />\n    <reward id=\"130\" />\n  </npc>\n  <npc id=\"48\" mx=\"6\" my=\"7\">\n    <deckA id0=\"88\" id1=\"100\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"92\" id1=\"97\" id2=\"86\" id3=\"99\" id4=\"49\" />\n    <rule id=\"1\" />\n    <rule id=\"1\" />\n    <reward id=\"92\" />\n    <reward id=\"111\" />\n  </npc>\n  <npc id=\"49\" mx=\"25\" my=\"35\">\n    <deckA id0=\"54\" id1=\"33\" id2=\"19\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"37\" id1=\"25\" id2=\"15\" id3=\"13\" id4=\"20\" />\n    <rule id=\"3\" />\n    <reward id=\"33\" />\n    <reward id=\"21\" />\n  </npc>\n  <npc id=\"50\" mx=\"28\" my=\"35\">\n    <deckA id0=\"66\" id1=\"98\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"82\" id1=\"83\" id2=\"97\" id3=\"100\" id4=\"43\" />\n    <rule id=\"1\" />\n    <rule id=\"1\" />\n    <reward id=\"82\" />\n    <reward id=\"83\" />\n    <reward id=\"115\" />\n    <reward id=\"108\" />\n  </npc>\n  <npc id=\"51\" mx=\"12\" my=\"10\">\n    <deckA id0=\"28\" id1=\"30\" id2=\"14\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"36\" id1=\"29\" id2=\"13\" id3=\"10\" id4=\"8\" />\n    <rule id=\"3\" />\n    <reward id=\"28\" />\n  </npc>\n  <npc id=\"52\" mx=\"11\" my=\"8\">\n    <deckA id0=\"149\" id1=\"145\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"29\" id1=\"65\" id2=\"54\" id3=\"126\" id4=\"7\" />\n    <rule id=\"13\" />\n    <rule id=\"7\" />\n    <reward id=\"65\" />\n    <reward id=\"145\" />\n    <reward id=\"126\" />\n  </npc>\n  <npc id=\"53\" mx=\"12\" my=\"14\">\n    <deckA id0=\"30\" id1=\"29\" id2=\"15\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"35\" id1=\"28\" id2=\"11\" id3=\"12\" id4=\"5\" />\n    <rule id=\"3\" />\n    <reward id=\"30\" />\n    <reward id=\"12\" />\n  </npc>\n  <npc id=\"54\" mx=\"12\" my=\"34\">\n    <deckA id0=\"53\" id1=\"58\" id2=\"196\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"42\" id1=\"89\" id2=\"93\" id3=\"0\" id4=\"0\" />\n    <rule id=\"7\" />\n    <rule id=\"12\" />\n    <reward id=\"196\" />\n    <reward id=\"188\" />\n  </npc>\n  <npc id=\"55\" mx=\"3\" my=\"4\">\n    <deckA id0=\"54\" id1=\"164\" id2=\"110\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"50\" id1=\"145\" id2=\"103\" id3=\"29\" id4=\"0\" />\n    <rule id=\"7\" />\n    <rule id=\"8\" />\n    <reward id=\"164\" />\n  </npc>\n  <npc id=\"56\" mx=\"32\" my=\"12\">\n    <deckA id0=\"56\" id1=\"98\" id2=\"194\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"125\" id1=\"161\" id2=\"45\" id3=\"0\" id4=\"0\" />\n    <rule id=\"7\" />\n    <rule id=\"8\" />\n    <reward id=\"194\" />\n    <reward id=\"188\" />\n  </npc>\n  <npc id=\"57\" mx=\"11\" my=\"6\">\n    <deckA id0=\"52\" id1=\"42\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"81\" id1=\"36\" id2=\"43\" id3=\"30\" id4=\"35\" />\n    <rule id=\"10\" />\n    <rule id=\"8\" />\n    <reward id=\"81\" />\n    <reward id=\"131\" />\n  </npc>\n  <npc id=\"58\" mx=\"12\" my=\"14\">\n    <deckA id0=\"149\" id1=\"144\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"29\" id1=\"65\" id2=\"54\" id3=\"49\" id4=\"12\" />\n    <rule id=\"1\" />\n    <rule id=\"8\" />\n    <reward id=\"65\" />\n    <reward id=\"144\" />\n    <reward id=\"149\" />\n  </npc>\n  <npc id=\"59\" mx=\"22\" my=\"21\">\n    <deckA id0=\"57\" id1=\"179\" id2=\"195\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"117\" id1=\"110\" id2=\"147\" id3=\"0\" id4=\"0\" />\n    <rule id=\"6\" />\n    <rule id=\"8\" />\n    <reward id=\"195\" />\n    <reward id=\"188\" />\n  </npc>\n  <npc id=\"60\" mx=\"6\" my=\"23\">\n    <deckA id0=\"55\" id1=\"51\" id2=\"50\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"44\" id1=\"38\" id2=\"26\" id3=\"33\" id4=\"0\" />\n    <rule id=\"11\" />\n    <reward id=\"38\" />\n    <reward id=\"26\" />\n    <reward id=\"11\" />\n  </npc>\n  <npc id=\"61\" mx=\"17\" my=\"27\">\n    <deckA id0=\"56\" id1=\"24\" id2=\"35\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"44\" id1=\"30\" id2=\"10\" id3=\"18\" id4=\"14\" />\n    <rule id=\"3\" />\n    <reward id=\"35\" />\n    <reward id=\"24\" />\n  </npc>\n  <npc id=\"62\" mx=\"4\" my=\"3\">\n    <deckA id0=\"45\" id1=\"158\" id2=\"26\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"28\" id1=\"82\" id2=\"8\" id3=\"13\" id4=\"0\" />\n    <rule id=\"8\" />\n    <reward id=\"158\" />\n  </npc>\n  <npc id=\"63\" mx=\"13\" my=\"13\">\n    <deckA id0=\"65\" id1=\"54\" id2=\"41\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"58\" id1=\"50\" id2=\"49\" id3=\"38\" id4=\"0\" />\n    <rule id=\"7\" />\n    <rule id=\"8\" />\n    <reward id=\"65\" />\n    <reward id=\"54\" />\n    <reward id=\"49\" />\n  </npc>\n  <npc id=\"64\" mx=\"4\" my=\"5\">\n    <deckA id0=\"112\" id1=\"152\" id2=\"179\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"129\" id1=\"115\" id2=\"82\" id3=\"103\" id4=\"81\" />\n    <rule id=\"12\" />\n    <reward id=\"112\" />\n    <reward id=\"152\" />\n    <reward id=\"179\" />\n    <reward id=\"129\" />\n  </npc>\n  <npc id=\"65\" mx=\"20\" my=\"21\">\n    <deckA id0=\"26\" id1=\"9\" id2=\"13\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"25\" id1=\"15\" id2=\"5\" id3=\"6\" id4=\"0\" />\n    <rule id=\"2\" />\n    <reward id=\"9\" />\n    <reward id=\"5\" />\n  </npc>\n  <npc id=\"66\" mx=\"22\" my=\"5\">\n    <deckA id0=\"34\" id1=\"61\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"29\" id1=\"60\" id2=\"47\" id3=\"39\" id4=\"45\" />\n    <rule id=\"1\" />\n    <rule id=\"1\" />\n    <reward id=\"60\" />\n    <reward id=\"34\" />\n  </npc>\n  <npc id=\"67\" mx=\"4\" my=\"8\">\n    <deckA id0=\"59\" id1=\"56\" id2=\"47\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"57\" id1=\"58\" id2=\"43\" id3=\"50\" id4=\"45\" />\n    <rule id=\"9\" />\n    <rule id=\"4\" />\n    <reward id=\"45\" />\n    <reward id=\"50\" />\n  </npc>\n  <npc id=\"68\" mx=\"7\" my=\"7\">\n    <deckA id0=\"107\" id1=\"106\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"117\" id1=\"18\" id2=\"15\" id3=\"14\" id4=\"17\" />\n    <rule id=\"5\" />\n    <rule id=\"12\" />\n    <reward id=\"106\" />\n    <reward id=\"107\" />\n    <reward id=\"117\" />\n  </npc>\n  <npc id=\"69\" mx=\"24\" my=\"23\">\n    <deckA id0=\"58\" id1=\"51\" id2=\"33\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"45\" id1=\"38\" id2=\"47\" id3=\"0\" id4=\"0\" />\n    <rule id=\"7\" />\n    <rule id=\"8\" />\n    <reward id=\"58\" />\n    <reward id=\"38\" />\n    <reward id=\"33\" />\n    <reward id=\"162\" />\n  </npc>\n  <npc id=\"70\" mx=\"8\" my=\"9\">\n    <deckA id0=\"67\" id1=\"28\" id2=\"22\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"59\" id1=\"46\" id2=\"40\" id3=\"33\" id4=\"19\" />\n    <rule id=\"12\" />\n    <reward id=\"67\" />\n    <reward id=\"59\" />\n    <reward id=\"40\" />\n    <reward id=\"113\" />\n  </npc>\n  <npc id=\"71\" mx=\"22\" my=\"19\">\n    <deckA id0=\"136\" id1=\"117\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"107\" id1=\"106\" id2=\"49\" id3=\"59\" id4=\"0\" />\n    <rule id=\"13\" />\n    <rule id=\"7\" />\n    <reward id=\"136\" />\n    <reward id=\"117\" />\n    <reward id=\"163\" />\n  </npc>\n  <npc id=\"72\" mx=\"36\" my=\"16\">\n    <deckA id0=\"41\" id1=\"34\" id2=\"2\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"49\" id1=\"26\" id2=\"22\" id3=\"20\" id4=\"0\" />\n    <rule id=\"3\" />\n    <reward id=\"41\" />\n    <reward id=\"20\" />\n  </npc>\n  <npc id=\"73\" mx=\"4\" my=\"7\">\n    <deckA id0=\"13\" id1=\"7\" id2=\"2\" id3=\"5\" id4=\"4\" />\n    <deckV id0=\"0\" id1=\"0\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <rule id=\"2\" />\n    <reward id=\"4\" />\n  </npc>\n  <npc id=\"74\" mx=\"29\" my=\"16\">\n    <deckA id0=\"89\" id1=\"184\" id2=\"38\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"54\" id1=\"21\" id2=\"18\" id3=\"155\" id4=\"0\" />\n    <rule id=\"12\" />\n    <rule id=\"1\" />\n    <reward id=\"184\" />\n  </npc>\n  <npc id=\"75\" mx=\"16\" my=\"25\">\n    <deckA id0=\"217\" id1=\"96\" id2=\"137\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"109\" id1=\"162\" id2=\"174\" id3=\"0\" id4=\"0\" />\n    <rule id=\"7\" />\n    <rule id=\"8\" />\n    <reward id=\"217\" />\n    <reward id=\"189\" />\n  </npc>\n  <npc id=\"76\" mx=\"22\" my=\"8\">\n    <deckA id0=\"221\" id1=\"222\" id2=\"225\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"212\" id1=\"184\" id2=\"214\" id3=\"193\" id4=\"0\" />\n    <rule id=\"13\" />\n    <rule id=\"11\" />\n    <reward id=\"225\" />\n    <reward id=\"222\" />\n    <reward id=\"221\" />\n  </npc>\n  <npc id=\"77\" mx=\"24\" my=\"19\">\n    <deckA id0=\"97\" id1=\"83\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"109\" id1=\"105\" id2=\"90\" id3=\"59\" id4=\"88\" />\n    <rule id=\"1\" />\n    <reward id=\"97\" />\n    <reward id=\"85\" />\n    <reward id=\"109\" />\n  </npc>\n  <npc id=\"78\" mx=\"24\" my=\"20\">\n    <deckA id0=\"105\" id1=\"97\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"90\" id1=\"88\" id2=\"59\" id3=\"55\" id4=\"109\" />\n    <rule id=\"9\" />\n    <rule id=\"13\" />\n    <reward id=\"105\" />\n    <reward id=\"109\" />\n  </npc>\n  <npc id=\"79\" mx=\"10\" my=\"11\">\n    <deckA id0=\"66\" id1=\"56\" id2=\"53\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"63\" id1=\"58\" id2=\"48\" id3=\"43\" id4=\"0\" />\n    <rule id=\"9\" />\n    <reward id=\"66\" />\n    <reward id=\"53\" />\n    <reward id=\"48\" />\n    <reward id=\"43\" />\n  </npc>\n  <npc id=\"80\" mx=\"8\" my=\"15\">\n    <deckA id0=\"63\" id1=\"49\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"89\" id1=\"48\" id2=\"42\" id3=\"73\" id4=\"28\" />\n    <rule id=\"13\" />\n    <rule id=\"12\" />\n    <reward id=\"89\" />\n  </npc>\n  <npc id=\"81\" mx=\"10\" my=\"9\">\n    <deckA id0=\"22\" id1=\"28\" id2=\"13\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"26\" id1=\"4\" id2=\"7\" id3=\"8\" id4=\"0\" />\n    <rule id=\"3\" />\n    <reward id=\"8\" />\n    <reward id=\"141\" />\n  </npc>\n  <npc id=\"82\" mx=\"3\" my=\"3\">\n    <deckA id0=\"35\" id1=\"11\" id2=\"154\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"30\" id1=\"5\" id2=\"9\" id3=\"13\" id4=\"0\" />\n    <rule id=\"2\" />\n    <rule id=\"7\" />\n    <reward id=\"154\" />\n  </npc>\n  <npc id=\"83\" mx=\"7\" my=\"12\">\n    <deckA id0=\"61\" id1=\"55\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"96\" id1=\"22\" id2=\"40\" id3=\"46\" id4=\"81\" />\n    <rule id=\"12\" />\n    <rule id=\"1\" />\n    <reward id=\"96\" />\n    <reward id=\"127\" />\n  </npc>\n  <npc id=\"84\" mx=\"14\" my=\"13\">\n    <deckA id0=\"64\" id1=\"81\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"84\" id1=\"34\" id2=\"32\" id3=\"77\" id4=\"55\" />\n    <rule id=\"9\" />\n    <rule id=\"13\" />\n    <reward id=\"84\" />\n  </npc>\n  <npc id=\"85\" mx=\"32\" my=\"37\">\n    <deckA id0=\"214\" id1=\"193\" id2=\"108\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"37\" id1=\"32\" id2=\"122\" id3=\"0\" id4=\"0\" />\n    <rule id=\"8\" />\n    <reward id=\"214\" />\n  </npc>\n  <npc id=\"86\" mx=\"11\" my=\"15\">\n    <deckA id0=\"253\" id1=\"25\" id2=\"170\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"8\" id1=\"265\" id2=\"155\" id3=\"1\" id4=\"0\" />\n    <rule id=\"3\" />\n    <reward id=\"253\" />\n  </npc>\n  <npc id=\"87\" mx=\"16\" my=\"31\">\n    <deckA id0=\"257\" id1=\"111\" id2=\"261\" id3=\"103\" id4=\"0\" />\n    <deckV id0=\"155\" id1=\"7\" id2=\"253\" id3=\"0\" id4=\"0\" />\n    <rule id=\"3\" />\n    <rule id=\"13\" />\n    <reward id=\"257\" />\n  </npc>\n  <npc id=\"88\" mx=\"19\" my=\"26\">\n    <deckA id0=\"258\" id1=\"268\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"10\" id1=\"262\" id2=\"170\" id3=\"105\" id4=\"38\" />\n    <rule id=\"7\" />\n    <rule id=\"1\" />\n    <reward id=\"268\" />\n    <reward id=\"258\" />\n  </npc>\n  <npc id=\"89\" mx=\"12\" my=\"17\">\n    <deckA id0=\"254\" id1=\"256\" id2=\"189\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"7\" id1=\"38\" id2=\"8\" id3=\"170\" id4=\"0\" />\n    <rule id=\"8\" />\n    <rule id=\"7\" />\n    <reward id=\"256\" />\n    <reward id=\"254\" />\n  </npc>\n  <npc id=\"90\" mx=\"19\" my=\"17\">\n    <deckA id0=\"255\" id1=\"264\" id2=\"38\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"12\" id1=\"7\" id2=\"256\" id3=\"21\" id4=\"0\" />\n    <rule id=\"3\" />\n    <rule id=\"6\" />\n    <reward id=\"255\" />\n  </npc>\n  <npc id=\"91\" mx=\"36\" my=\"20\">\n    <deckA id0=\"265\" id1=\"0\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"38\" id1=\"260\" id2=\"170\" id3=\"110\" id4=\"25\" />\n    <rule id=\"12\" />\n    <reward id=\"265\" />\n  </npc>\n  <npc id=\"92\" mx=\"32\" my=\"17\">\n    <deckA id0=\"259\" id1=\"253\" id2=\"254\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"255\" id1=\"265\" id2=\"8\" id3=\"155\" id4=\"258\" />\n    <rule id=\"2\" />\n    <reward id=\"259\" />\n  </npc>\n  <npc id=\"93\" mx=\"13\" my=\"10\">\n    <deckA id0=\"266\" id1=\"270\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"260\" id1=\"262\" id2=\"263\" id3=\"38\" id4=\"267\" />\n    <rule id=\"8\" />\n    <rule id=\"13\" />\n    <reward id=\"270\" />\n    <reward id=\"266\" />\n  </npc>\n  <npc id=\"94\" mx=\"22\" my=\"4\">\n    <deckA id0=\"274\" id1=\"276\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"257\" id1=\"111\" id2=\"261\" id3=\"103\" id4=\"38\" />\n    <rule id=\"8\" />\n    <reward id=\"276\" />\n    <reward id=\"274\" />\n  </npc>\n  <npc id=\"95\" mx=\"20\" my=\"27\">\n    <deckA id0=\"284\" id1=\"287\" id2=\"275\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"109\" id1=\"262\" id2=\"86\" id3=\"0\" id4=\"0\" />\n    <rule id=\"7\" />\n    <rule id=\"5\" />\n    <reward id=\"287\" />\n    <reward id=\"284\" />\n  </npc>\n  <npc id=\"96\" mx=\"10\" my=\"12\">\n    <deckA id0=\"209\" id1=\"208\" id2=\"20\" id3=\"226\" id4=\"0\" />\n    <deckV id0=\"250\" id1=\"229\" id2=\"58\" id3=\"0\" id4=\"0\" />\n    <rule id=\"8\" />\n    <rule id=\"10\" />\n    <reward id=\"250\" />\n    <reward id=\"229\" />\n    <reward id=\"209\" />\n    <reward id=\"208\" />\n  </npc>\n  <npc id=\"97\" mx=\"14\" my=\"8\">\n    <deckA id0=\"296\" id1=\"294\" id2=\"264\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"278\" id1=\"253\" id2=\"260\" id3=\"265\" id4=\"285\" />\n    <rule id=\"8\" />\n    <rule id=\"5\" />\n    <reward id=\"296\" />\n    <reward id=\"294\" />\n  </npc>\n  <npc id=\"98\" mx=\"5\" my=\"6\">\n    <deckA id0=\"302\" id1=\"244\" id2=\"180\" id3=\"175\" id4=\"0\" />\n    <deckV id0=\"291\" id1=\"176\" id2=\"238\" id3=\"192\" id4=\"51\" />\n    <rule id=\"13\" />\n    <rule id=\"6\" />\n    <reward id=\"302\" />\n  </npc>\n  <npc id=\"99\" mx=\"6\" my=\"7\">\n    <deckA id0=\"298\" id1=\"299\" id2=\"208\" id3=\"180\" id4=\"0\" />\n    <deckV id0=\"250\" id1=\"229\" id2=\"209\" id3=\"0\" id4=\"0\" />\n    <rule id=\"8\" />\n    <rule id=\"9\" />\n    <reward id=\"298\" />\n    <reward id=\"299\" />\n  </npc>\n  <npc id=\"100\" mx=\"27\" my=\"16\">\n    <deckA id0=\"313\" id1=\"314\" id2=\"263\" id3=\"288\" id4=\"0\" />\n    <deckV id0=\"301\" id1=\"270\" id2=\"254\" id3=\"256\" id4=\"0\" />\n    <rule id=\"8\" />\n    <rule id=\"6\" />\n    <reward id=\"313\" />\n    <reward id=\"314\" />\n  </npc>\n  <npc id=\"101\" mx=\"12\" my=\"13\">\n    <deckA id0=\"309\" id1=\"310\" id2=\"136\" id3=\"39\" id4=\"0\" />\n    <deckV id0=\"305\" id1=\"9\" id2=\"22\" id3=\"25\" id4=\"0\" />\n    <rule id=\"7\" />\n    <rule id=\"10\" />\n    <reward id=\"309\" />\n    <reward id=\"310\" />\n  </npc>\n  <npc id=\"102\" mx=\"6\" my=\"7\">\n    <deckA id0=\"321\" id1=\"325\" id2=\"208\" id3=\"322\" id4=\"0\" />\n    <deckV id0=\"250\" id1=\"229\" id2=\"209\" id3=\"299\" id4=\"0\" />\n    <rule id=\"7\" />\n    <rule id=\"13\" />\n    <reward id=\"321\" />\n    <reward id=\"322\" />\n  </npc>\n  <npc id=\"103\" mx=\"29\" my=\"13\">\n    <deckA id0=\"327\" id1=\"234\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"233\" id1=\"169\" id2=\"111\" id3=\"130\" id4=\"0\" />\n    <rule id=\"3\" />\n    <reward id=\"327\" />\n  </npc>\n  <npc id=\"104\" mx=\"20\" my=\"9\">\n    <deckA id0=\"328\" id1=\"1\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"188\" id1=\"232\" id2=\"104\" id3=\"45\" id4=\"102\" />\n    <rule id=\"7\" />\n    <reward id=\"328\" />\n  </npc>\n  <npc id=\"105\" mx=\"32\" my=\"17\">\n    <deckA id0=\"340\" id1=\"293\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"238\" id1=\"244\" id2=\"180\" id3=\"176\" id4=\"175\" />\n    <rule id=\"1\" />\n    <reward id=\"340\" />\n  </npc>\n  <npc id=\"106\" mx=\"11\" my=\"8\">\n    <deckA id0=\"330\" id1=\"336\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"224\" id1=\"191\" id2=\"185\" id3=\"170\" id4=\"328\" />\n    <rule id=\"8\" />\n    <reward id=\"330\" />\n  </npc>\n  <npc id=\"107\" mx=\"11\" my=\"9\">\n    <deckA id0=\"331\" id1=\"332\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"130\" id1=\"94\" id2=\"46\" id3=\"269\" id4=\"268\" />\n    <rule id=\"13\" />\n    <rule id=\"8\" />\n    <reward id=\"331\" />\n    <reward id=\"332\" />\n  </npc>\n  <npc id=\"108\" mx=\"16\" my=\"7\">\n    <deckA id0=\"338\" id1=\"92\" id2=\"233\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"341\" id1=\"251\" id2=\"59\" id3=\"60\" id4=\"0\" />\n    <rule id=\"8\" />\n    <rule id=\"4\" />\n    <reward id=\"338\" />\n    <reward id=\"341\" />\n  </npc>\n  <npc id=\"109\" mx=\"31\" my=\"12\">\n    <deckA id0=\"350\" id1=\"351\" id2=\"140\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"352\" id1=\"353\" id2=\"132\" id3=\"135\" id4=\"0\" />\n    <rule id=\"7\" />\n    <rule id=\"13\" />\n    <reward id=\"351\" />\n    <reward id=\"350\" />\n  </npc>\n  <npc id=\"110\" mx=\"20\" my=\"28\">\n    <deckA id0=\"355\" id1=\"348\" id2=\"336\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"349\" id1=\"330\" id2=\"188\" id3=\"0\" id4=\"0\" />\n    <rule id=\"1\" />\n    <rule id=\"8\" />\n    <reward id=\"348\" />\n  </npc>\n  <npc id=\"112\" mx=\"13\" my=\"8\">\n    <deckA id0=\"358\" id1=\"63\" id2=\"118\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"28\" id1=\"22\" id2=\"45\" id3=\"178\" id4=\"0\" />\n    <rule id=\"3\" />\n    <rule id=\"8\" />\n    <reward id=\"358\" />\n  </npc>\n  <npc id=\"113\" mx=\"18\" my=\"16\">\n    <deckA id0=\"369\" id1=\"331\" id2=\"269\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"344\" id1=\"4\" id2=\"125\" id3=\"173\" id4=\"0\" />\n    <rule id=\"13\" />\n    <reward id=\"369\" />\n  </npc>\n  <npc id=\"114\" mx=\"9\" my=\"27\">\n    <deckA id0=\"382\" id1=\"385\" id2=\"335\" id3=\"338\" id4=\"0\" />\n    <deckV id0=\"388\" id1=\"367\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <rule id=\"7\" />\n    <rule id=\"10\" />\n    <reward id=\"382\" />\n    <reward id=\"385\" />\n  </npc>\n  <npc id=\"115\" mx=\"9\" my=\"9\">\n    <deckA id0=\"378\" id1=\"239\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"221\" id1=\"222\" id2=\"235\" id3=\"246\" id4=\"0\" />\n    <rule id=\"6\" />\n    <rule id=\"8\" />\n    <reward id=\"378\" />\n  </npc>\n  <npc id=\"116\" mx=\"8\" my=\"11\">\n    <deckA id0=\"394\" id1=\"395\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"396\" id1=\"208\" id2=\"340\" id3=\"21\" id4=\"0\" />\n    <rule id=\"13\" />\n    <rule id=\"7\" />\n    <reward id=\"394\" />\n    <reward id=\"395\" />\n    <reward id=\"396\" />\n  </npc>\n  <npc id=\"117\" mx=\"9\" my=\"15\">\n    <deckA id0=\"398\" id1=\"399\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"112\" id1=\"209\" id2=\"55\" id3=\"198\" id4=\"0\" />\n    <rule id=\"12\" />\n    <rule id=\"8\" />\n    <reward id=\"398\" />\n    <reward id=\"399\" />\n  </npc>\n  <npc id=\"118\" mx=\"15\" my=\"14\">\n    <deckA id0=\"406\" id1=\"10\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"86\" id1=\"82\" id2=\"107\" id3=\"156\" id4=\"183\" />\n    <rule id=\"2\" />\n    <reward id=\"406\" />\n  </npc>\n  <npc id=\"119\" mx=\"29\" my=\"13\">\n    <deckA id0=\"407\" id1=\"8\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"158\" id1=\"191\" id2=\"306\" id3=\"2\" id4=\"11\" />\n    <rule id=\"3\" />\n    <reward id=\"407\" />\n  </npc>\n  <npc id=\"120\" mx=\"12\" my=\"27\">\n    <deckA id0=\"408\" id1=\"298\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"38\" id1=\"347\" id2=\"259\" id3=\"195\" id4=\"0\" />\n    <rule id=\"7\" />\n    <reward id=\"408\" />\n  </npc>\n  <npc id=\"121\" mx=\"13\" my=\"15\">\n    <deckA id0=\"409\" id1=\"162\" id2=\"175\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"205\" id1=\"27\" id2=\"192\" id3=\"171\" id4=\"0\" />\n    <rule id=\"9\" />\n    <reward id=\"409\" />\n  </npc>\n  <npc id=\"122\" mx=\"29\" my=\"30\">\n    <deckA id0=\"410\" id1=\"263\" id2=\"0\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"289\" id1=\"141\" id2=\"310\" id3=\"102\" id4=\"0\" />\n    <rule id=\"3\" />\n    <rule id=\"8\" />\n    <reward id=\"410\" />\n  </npc>\n  <npc id=\"123\" mx=\"20\" my=\"9\">\n    <deckA id0=\"411\" id1=\"245\" id2=\"207\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"192\" id1=\"128\" id2=\"48\" id3=\"320\" id4=\"0\" />\n    <rule id=\"13\" />\n    <rule id=\"10\" />\n    <reward id=\"411\" />\n  </npc>\n  <npc id=\"124\" mx=\"6\" my=\"14\">\n    <deckA id0=\"412\" id1=\"149\" id2=\"225\" id3=\"0\" id4=\"0\" />\n    <deckV id0=\"319\" id1=\"114\" id2=\"278\" id3=\"0\" id4=\"0\" />\n    <rule id=\"8\" />\n    <rule id=\"2\" />\n    <reward id=\"412\" />\n  </npc>\n  <npc id=\"125\" mx=\"16\" my=\"3\">\n    <deckA id0=\"413\" id1=\"291\" id2=\"96\" id3=\"312\" id4=\"0\" />\n    <deckV id0=\"239\" id1=\"120\" id2=\"271\" id3=\"250\" id4=\"0\" />\n    <rule id=\"12\" />\n    <rule id=\"6\" />\n    <reward id=\"413\" />\n  </npc>\n  <npc id=\"126\" mx=\"7\" my=\"7\">\n    <deckA id0=\"428\" id1=\"429\" id2=\"197\" id3=\"66\" id4=\"0\" />\n    <deckV id0=\"193\" id1=\"318\" id2=\"236\" id3=\"0\" id4=\"0\" />\n    <rule id=\"7\" />\n    <rule id=\"13\" />\n    <reward id=\"428\" />\n    <reward id=\"429\" />\n  </npc>\n  <npc id=\"127\" mx=\"6\" my=\"6\">\n    <deckA id0=\"430\" id1=\"431\" id2=\"269\" id3=\"65\" id4=\"0\" />\n    <deckV id0=\"93\" id1=\"146\" id2=\"190\" id3=\"0\" id4=\"0\" />\n    <rule id=\"8\" />\n    <rule id=\"6\" />\n    <reward id=\"430\" />\n    <reward id=\"431\" />\n  </npc>\n  <npc id=\"128\" mx=\"5\" my=\"6\">\n    <deckA id0=\"437\" id1=\"49\" id2=\"53\" id3=\"62\" id4=\"0\" />\n    <deckV id0=\"46\" id1=\"45\" id2=\"38\" id3=\"0\" id4=\"0\" />\n    <rule id=\"8\" />\n    <rule id=\"12\" />\n    <reward id=\"437\" />\n  </npc>\n  <npc id=\"129\" mx=\"33\" my=\"37\">\n    <deckA id0=\"442\" id1=\"443\" id2=\"98\" id3=\"119\" id4=\"0\" />\n    <deckV id0=\"217\" id1=\"318\" id2=\"394\" id3=\"0\" id4=\"0\" />\n    <rule id=\"9\" />\n    <rule id=\"7\" />\n    <reward id=\"442\" />\n    <reward id=\"443\" />\n  </npc>\n</root>"
  },
  {
    "path": "assets/data/tournaments.xml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <tournament id=\"0\">\n    <rule id=\"2\" />\n    <rule id=\"8\" />\n  </tournament>\n  <tournament id=\"1\">\n    <rule id=\"3\" />\n    <rule id=\"13\" />\n  </tournament>\n  <tournament id=\"2\">\n    <rule id=\"11\" />\n    <rule id=\"7\" />\n  </tournament>\n  <tournament id=\"3\">\n    <rule id=\"1\" />\n    <rule id=\"1\" />\n  </tournament>\n</root>"
  },
  {
    "path": "datasource/!Start.bat",
    "content": "@echo off\nsetlocal\n\nif not exist SaintCoinach goto MISSING_SAINT\nif not exist SetUserVars.bat goto MISSING_PATH\ncall SetUserVars.bat\nif %GamePath%==\"\" goto MISSING_PATH \n\necho Game path: %GamePath%\necho.\necho Export data using commands:\necho   ui 87000 88999\necho   allexd\necho   exit\necho.\n\npushd SaintCoinach\nif exist SaintCoinach.History.zip ( del SaintCoinach.History.zip > nul )\nSaintCoinach.Cmd.exe %GamePath%\npopd\n\nFOR /F \"tokens=* USEBACKQ\" %%F IN (`dir SaintCoinach\\2* /A:D /B`) DO (\nSET DataPath=%%F\n)\n\necho.\nrmdir export /s /q > nul\necho Copying exported data from: %DataPath%\n\nxcopy SaintCoinach\\%DataPath%\\*.* export\\ /e > nul\n\ndel ..\\assets\\icons\\*.png\nxcopy export\\ui\\icon\\087000\\*.png ..\\assets\\icons\\ /s > nul\nxcopy export\\ui\\icon\\088000\\*.png ..\\assets\\icons\\ /s > nul\n\nrmdir SaintCoinach\\%DataPath% /s /q > nul\n\n:GIT_MIRRORS\nSET /P CANDOWNLOAD=Do you want to download from github mirrors (curl required) (Y/[N])? \nIF /I \"%CANDOWNLOAD%\" NEQ \"Y\" GOTO EXPORTED\n\nrem raw blobs with URL like:\nrem https://raw.githubusercontent.com/thewakingsands/ffxiv-datamining-cn/master/ENpcResident.csv\ncall :CURL_WORKER cn thewakingsands/ffxiv-datamining-cn master\ncall :CURL_WORKER ko Ra-Workspace/ffxiv-datamining-ko master/csv\ngoto EXPORTED\n\n:CURL_WORKER\necho Downloading from: %2...\nfor %%F in (ENpcResident Item PlaceName TripleTriadCard TripleTriadCardType TripleTriadRule TripleTriadCompetition TripleTriadCardResident TripleTriadResident) do ( \n\tcurl https://raw.githubusercontent.com/%2/%3/%%F.csv --output export\\exd-all\\%%F.%1.csv --silent\n)\nexit /b\n\n:EXPORTED\necho Done! \necho.\necho Run DEBUG build with -dataConvert cmdline to process data tables.\necho Output logs will show all needed information\ngoto FINISHED\n\n:MISSING_PATH\necho Game path not set, edit SetUserVars.bat and update GamePath variable\necho.\necho Example: \"C:\\Games\\SquareEnix\\FINAL FANTASY XIV - A Realm Reborn\"\necho set GamePath=\"C:\\Games\\SquareEnix\\FINAL FANTASY XIV - A Realm Reborn\" > SetUserVars.bat\ngoto FINISHED\n\n:MISSING_SAINT\necho Can't find SaintCoinach binaries!\necho.\necho Grab latest release of SaintCoinach.Cmd from github:\necho    https://github.com/xivapi/SaintCoinach/releases\necho.\necho Expected path: datasource/SaintCoinach/SaintCoinach.Cmd.exe\ngoto FINISHED\n\n:FINISHED\necho.\npause"
  },
  {
    "path": "ml/patternMatch/nn.py",
    "content": "import tensorflow as tf\nimport numpy as np\nimport json\nimport textwrap\n\nfrom tensorflow import keras\nfrom tqdm.keras import TqdmCallback\n\nclass NNTraining():\n    def __init__(self, inputFile, outputFile, labelKey=\"output\"):\n        path = 'data/'\n        self.inputFile = path + inputFile\n        self.outputFile = path + outputFile\n        self.labelKey = labelKey\n        pass\n        \n    def printToLines(self, prefix, values, suffix):\n        longstr = prefix + ', '.join(('%ff' % v) for v in values) + suffix\n        return textwrap.wrap(longstr, 250)\n\n    def loadData(self):\n        with open(self.inputFile) as file:\n            training_sets = json.load(file)\n\n        inputs = []\n        outputs = []\n        \n        for elem in training_sets[\"dataset\"]:\n            inputs.append(elem[\"input\"])\n            outputs.append(elem[self.labelKey])\n\n        return inputs, outputs\n        \n\n    def writeCodeFile(self, model, codeSuffix):\n        lines = []\n\n        for i in range(len(model.layers)):\n            layer = model.layers[i]\n            print('Layer[%i]:' % i)\n            print('  input_shape:', layer.input_shape)\n            print('  output_shape:', layer.output_shape)\n            weights = layer.get_weights()\n            for w in weights:\n                print('  w.shape:', w.shape)\n            print('  use_bias:', layer.use_bias)\n            print('  activation:', layer.activation)\n\n            if (len(weights) == 2 and layer.use_bias):\n                listWeights = np.reshape(weights[0], -1)\n                listBias = np.reshape(weights[1], -1)\n                lines += self.printToLines('Layer%s%iW = new float[]{' % (codeSuffix, i), listWeights, '};')\n                lines += self.printToLines('Layer%s%iB = new float[]{' % (codeSuffix, i), listBias, '};')\n\n        with open(self.outputFile, \"w\") as file:\n            for line in lines:\n                file.write(line)\n                file.write(\"\\n\")\n\n\n    def run(self, numHidden1, numHidden2=0, numEpochs=20, batchSize=512, codeSuffix=''):\n        x_train, y_train = self.loadData()\n        x_train = np.array(x_train, np.float32)\n        numClasses = max(y_train) + 1\n\n        train_data = tf.data.Dataset.from_tensor_slices((x_train, y_train))\n        train_data = train_data.repeat().shuffle(5000).batch(batchSize).prefetch(1)\n\n        layers = []\n        if (numHidden1 > 0):\n            layers += [ tf.keras.layers.Dense(numHidden1, activation='relu') ]\n        if (numHidden2 > 0):\n            layers += [ tf.keras.layers.Dense(numHidden2, activation='relu') ]\n        \n        layers += [ tf.keras.layers.Dense(numClasses) ];\n        model = tf.keras.Sequential(layers)\n        model.compile(optimizer='adam',\n                      loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),\n                      metrics=['accuracy'])\n\n        model.fit(train_data,\n          epochs=numEpochs,\n          steps_per_epoch=100,\n          verbose=0, callbacks=[TqdmCallback(verbose=2)])\n\n        self.writeCodeFile(model, codeSuffix)\n"
  },
  {
    "path": "ml/patternMatch/run-everything.bat",
    "content": "@echo off\n\nset ToolPath=%localappdata%/Programs/Python/Python38\n\nfor %%i in (train-*.py) do %ToolPath%/python %%i\n\npause"
  },
  {
    "path": "ml/patternMatch/train-cactpot.py",
    "content": "from nn import NNTraining\n\ntraining = NNTraining(inputFile='ml-cactpot.json', outputFile='ml-cactpot.txt')\ntraining.run(numHidden1=80, numEpochs=500)\n"
  },
  {
    "path": "ml/patternMatch/train-triad.py",
    "content": "from nn import NNTraining\n\ntraining = NNTraining(inputFile='ml-triad.json', outputFile='ml-triad.txt')\ntraining.run(numHidden1=64, numEpochs=200)\n"
  },
  {
    "path": "ml/solver/agents/agent.py",
    "content": "class Agent:\n    def __init__(self, game):\n        pass\n\n    def findAction(self, game, state):\n        raise RuntimeError()\n\n    def findTrainingAction(self, game, state):\n        raise RuntimeError()\n\n    def onTrainingGameStart(self, game, playerId):\n        pass\n\n    def onTrainingGameEnd(self, game, playerId):\n        pass\n\n    def onTrainingStep(self, game, playerId, state, action, nextState, reward):\n        pass\n\n    def train(self):\n        pass\n\n    def save(self, name):\n        pass\n\n    def load(self, name):\n        pass\n\n    def getTrainingDetails(self):\n        return {}\n\n    def generateModelCode(self, name):\n        pass\n"
  },
  {
    "path": "ml/solver/agents/agentDQN.py",
    "content": "import os\nimport math\nimport numpy as np\nfrom .agent import Agent\nfrom utils.trainingMemory import TrainingMemoryCircular\nfrom utils.estimatorTorch import EstimatorModel\n\n# Deep Q Network\n#\nclass AgentDQN(Agent):\n    def __init__(self, game):\n        self.memorySize = 1 * 1000 * 1000\n        self.batchSize = 256\n        self.numLayersHidden = [500, 500]\n        self.learningRate = 0.0001\n        self.discountFactor = 0.99\n        self.epsilonStart = 1\n        self.epsilonEnd = 0.1\n        self.epsilonDecay = 2000\n\n        self.numActions = game.getMaxActions()\n        numInputs = len(game.getState(0))\n\n        self.estimatorQ = EstimatorModel(numInputs, self.numLayersHidden, self.numActions, self.learningRate)\n        self.estimatorTarget = EstimatorModel(numInputs, self.numLayersHidden, self.numActions, self.learningRate)\n        self.replayMemory = TrainingMemoryCircular(self.memorySize)\n        self.epsilon = self.epsilonStart\n        self.numSteps = 0\n        self.historyLoss = []\n        self.historyReward = []\n        self.trainedOnce = False\n\n\n    def findAction(self, game, state):\n        probs = self.estimatorQ.predict(np.expand_dims(state, 0))[0]\n        probs = self.sanitizeActions(game, state, probs)\n        return np.argmax(probs)\n\n    def findTrainingAction(self, game, state):\n        useRandom = np.random.random() < self.epsilon\n        if useRandom or not self.trainedOnce:\n            allowedActions = game.getAllowedActions(state)\n            return np.random.choice(allowedActions)\n\n        return self.findAction(game, state)\n\n    def onTrainingStep(self, game, playerId, state, action, nextState, reward):\n        self.numSteps += 1\n        self.epsilon = self.epsilonEnd + ((self.epsilonStart - self.epsilonEnd) * math.exp(-1.0 * self.numSteps / self.epsilonDecay))\n\n        memorySample = (state, action, nextState, reward, game.isFinished())\n        self.historyReward.append(reward)\n        self.replayMemory.add(memorySample)\n        if len(self.replayMemory) >= self.batchSize:\n            self.trainOnBatch(game)\n\n    def train(self):\n        updateTargetCheckpoint = 'updateTarget.tmp'\n        self.estimatorQ.save(updateTargetCheckpoint)\n        self.estimatorTarget.load(updateTargetCheckpoint)\n        os.remove(updateTargetCheckpoint)\n        self.trainAvgLoss = np.average(self.historyLoss)\n        self.trainAvgReward = np.average(self.historyReward)\n        self.historyLoss = []\n        self.historyReward = []\n\n    def save(self, name):\n        self.estimatorQ.save(name)\n\n    def load(self, name):\n        self.estimatorQ.load(name)\n        self.estimatorTarget.load(name)\n\n    def getTrainingDetails(self):\n        return {\n            'epsilon': self.epsilon,\n            'memory': len(self.replayMemory) / self.replayMemory.capacity,\n            'loss': self.trainAvgLoss,\n            'reward': self.trainAvgReward,\n        }\n\n    def generateModelCode(self, name):\n        self.estimatorQ.generateModelCode(name)\n\n    def sanitizeActions(self, game, state, actionValues, badValue = -np.inf):\n        maskedValues = badValue * np.ones(self.numActions, dtype=float)\n        allowedActions = game.getAllowedActions(state)\n        maskedValues[allowedActions] = actionValues[allowedActions]\n        return maskedValues\n\n    def trainOnBatch(self, game):\n        states, actions, nextStates, rewards, dones = self.replayMemory.sample(self.batchSize)\n\n        states = np.array(states)\n        targets = self.estimatorTarget.predict(states)\n        for i in range(self.batchSize):\n            targets[i] = self.sanitizeActions(game, states[i], targets[i], badValue=-1)\n\n        bestActions = np.argmax(targets, axis=1)\n        nextTargets = self.estimatorTarget.predict(nextStates)\n        predictedRewards = rewards + (np.invert(dones).astype(np.float) * self.discountFactor * nextTargets[np.arange(self.batchSize), bestActions])\n\n        loss = self.estimatorQ.fit(states, actions, predictedRewards)\n        self.historyLoss.append(loss)\n\n        self.trainedOnce = True\n"
  },
  {
    "path": "ml/solver/agents/agentRandom.py",
    "content": "import random\nfrom .agent import Agent\n\nclass AgentRandom(Agent):\n    def __init__(self, game):\n        self.randGen = random.Random()\n\n    def setSeed(self, seedValue):\n        self.randGen = random.Random(seedValue)\n\n    def findAction(self, game, state):\n        actions = game.getAllowedActions(state)\n        return self.randGen.choice(actions)\n\n    def findTrainingAction(self, game, state):\n        return self.findAction(game, state)\n"
  },
  {
    "path": "ml/solver/environment.py",
    "content": "class Environment(object):\n    def __init__(self, game, playerAgents):\n        self.game = game\n        self.players = playerAgents\n\n    def runTrainingGame(self):\n        self.game.init()\n\n        for idx in range(len(self.players)):\n            self.players[idx].onTrainingGameStart(self.game, idx)\n\n        while not self.game.isFinished():\n            playerId = self.game.getCurrentPlayer()\n            state = self.game.getState(playerId)\n\n            action = self.players[playerId].findTrainingAction(self.game, state)\n            self.game.step(action)\n\n            # TODO: reuse nextState in next step, it contains relative data relative to player though (board owner, card visibility)\n            nextState = self.game.getState(playerId)\n            reward = self.game.getReward(playerId)\n            self.players[playerId].onTrainingStep(self.game, playerId, state, action, nextState, reward)\n\n        for idx in range(len(self.players)):\n            self.players[idx].onTrainingGameEnd(self.game, idx)\n\n    def runEvalGame(self):\n        self.game.init()\n\n        while not self.game.isFinished():\n            playerId = self.game.getCurrentPlayer()\n            state = self.game.getState(playerId)\n\n            action = self.players[playerId].findAction(self.game, state)\n            self.game.step(action)\n\n        playerRewards = []\n        for idx in range(len(self.players)):\n            playerRewards.append(self.game.getReward(idx))\n\n        return playerRewards\n"
  },
  {
    "path": "ml/solver/gameSession.py",
    "content": "from gamelogic.triadCard import TriadCardDB\nfrom gamelogic.triadDeck import TriadDeck\nfrom gamelogic.triadGame import TriadGame, TriadGameState\nfrom gamelogic.triadMods import TriadMod\nimport random\n\nclass GameSession():\n    def __init__(self, useModifiers = False):\n        self.randomSeed = 0\n        self.useModifiers = useModifiers\n        self.init()\n\n    def init(self):\n        self.game = TriadGame()\n        if self.useModifiers:\n            self.game.mods = TriadMod.generateMods()\n\n        self.game.redDeck = TriadDeck.generateDeckPlayer()\n        self.game.blueDeck = TriadDeck.generateDeckPlayer()\n        self.game.blueDeck.makeAllVisible()\n\n        self.game.state = TriadGameState.BlueTurn if (random.random() < 0.5) else TriadGameState.RedTurn\n        self.game.initModifiers()\n        self.game.onTurnStart()\n\n\n    def step(self, action):\n        cardIdx = int(action / 9)\n        boardIdx = action % 9\n\n        ownerId = TriadGame.OwnerIdBlue if (self.game.state == TriadGameState.BlueTurn) else TriadGame.OwnerIdRed\n        result = self.game.placeCardFromDeck(boardIdx, cardIdx, ownerId)\n        if not result:\n            raise RuntimeError('Step failed, random seed:', self.randomSeed)\n\n    def isFinished(self):\n        return (self.game.state != TriadGameState.BlueTurn) and (self.game.state != TriadGameState.RedTurn)\n\n    def getCurrentPlayer(self):\n        return 0 if self.game.state == TriadGameState.BlueTurn else 1\n\n    @staticmethod\n    def getMaxActions():\n        return 9 * 5\n\n    def getAllowedActions(self, state):\n        actions = []\n        for idxB in range(9):\n            # is board pos available? (avail flag = 1)\n            if state[idxB] == 1:\n                for idxC in range(5):\n                    # is card available? (avail flag = 1)\n                    if state[idxC + 9] == 1:\n                        actions.append((idxC * 9) + idxB)\n        return actions\n\n    def getReward(self, playerId):\n        if self.game.state == TriadGameState.EndBlueWin:\n            return 1 if playerId == 0 else -1\n        if self.game.state == TriadGameState.EndRedWin:\n            return -1 if playerId == 0 else 1\n        if self.game.state == TriadGameState.EndDraw:\n            return 0.1\n        return 0\n\n    def getState(self, playerId):\n        state = []\n\n        # precalc values\n        ownerPlayer = TriadGame.OwnerIdBlue if playerId == 0 else TriadGame.OwnerIdRed\n        deckPlayer = self.game.blueDeck if playerId == 0 else self.game.redDeck\n        deckOpp = self.game.redDeck if playerId == 1 else self.game.blueDeck\n        cardInfoPlayer = deckPlayer.getCardsInfo(True)\n        cardInfoOpp = deckOpp.getCardsInfo()\n\n        # one-hot: valid board placement\n        if self.game.forcedBoardIdx >= 0:\n            state += [1 if (pos == self.game.forcedBoardIdx) else 0 for pos in range(len(self.game.owner))]\n        else:\n            state += [1 if (ownerId == 0) else 0 for ownerId in self.game.owner]\n\n        # one-hot: valid cards for move's owner\n        if self.game.forcedCardIdx >= 0:\n            state += [1 if (cardIdx == self.game.forcedCardIdx) else 0 for cardIdx in range(5)]\n        else:\n            state += [1 if (cardState != TriadDeck.cardNone) else 0 for cardState in deckPlayer.state]\n\n        # one-hot: active modifiers\n        allGameMods = [mod.name for mod in TriadMod.modDB]\n        activeMods = [mod.name for mod in self.game.mods]\n        state += [1 if (s in activeMods) else 0 for s in allGameMods]\n        # value of type modes\n        state += self.game.typeMod\n\n        # board cells: [ relative ownerId, type, sides 0..3]\n        for pos in range(len(self.game.owner)):\n            cellInfo = [0, 0, 0, 0, 0, 0]\n            if self.game.owner[pos] != 0:\n                cardOb = self.game.board[pos]\n                cellInfo = [1 if self.game.owner[pos] == ownerPlayer else -1, cardOb.cardType, cardOb.sides[0], cardOb.sides[1], cardOb.sides[2], cardOb.sides[3]]\n\n            state += cellInfo\n\n        # card data for move owner & opponent\n        for i in range(5):\n            if cardInfoPlayer[i][0] != 0:\n                sides = cardInfoPlayer[i][2].sides\n                state += [ cardInfoPlayer[i][0], sides[0], sides[1], sides[2], sides[3], cardInfoPlayer[i][3] ]\n            else:\n                state += [ 0, 0, 0, 0, 0, 0 ]\n\n            if cardInfoOpp[i][0] != 0:\n                sides = cardInfoOpp[i][2].sides if (cardInfoOpp[i][2] != None) else TriadCardDB.mapAvgSides[cardInfoOpp[i][3]]\n                state += [ cardInfoOpp[i][0], cardInfoOpp[i][1], sides[0], sides[1], sides[2], sides[3], cardInfoOpp[i][3] ]\n            else:\n                state += [ 0, 0, 0, 0, 0, 0, 0 ]\n\n        return state\n"
  },
  {
    "path": "ml/solver/gamelogic/triadCard.py",
    "content": "from xml.dom import minidom\n\nclass TriadCard:\n    def __init__(self, name, rarity, cardType, sideUp, sideLeft, sideDown, sideRight):\n        self.name = name\n        self.rarity = rarity\n        self.cardType = cardType\n        self.sides = [ sideUp, sideLeft, sideDown, sideRight ]\n        self.valid = True\n        self.id = 0\n\n    def __str__(self):\n        if self.valid == False:\n            return \"invalid\"\n        return 'type:%s, sides:[T:%i, L:%i, D:%i, R:%i], name:%s %s' % (\n            self.cardType,\n            self.sides[0], self.sides[1], self.sides[2], self.sides[3],\n            self.name, '*' * (self.rarity + 1))\n\nclass TriadCardDB:\n    typeList = ['None', 'Primal', 'Scion', 'Beastman', 'Garlean']\n    rarityList = ['Common', 'Uncommon', 'Rare', 'Epic', 'Legendary']\n    cards = []\n    # [rarity] = list of cards\n    mapRarity = []\n    mapAvgSides = []\n\n    @staticmethod\n    def load():\n        assetFolder = '../../assets/data/'\n        if (len(TriadCardDB.cards) > 0):\n            return\n\n        unknownCard = TriadCard('<Hidden>', 0, 0, 0, 0, 0, 0)\n        unknownCard.valid = False\n        TriadCardDB.cards = [ unknownCard ]\n\n        TriadCardDB.mapRarity = []\n        TriadCardDB.mapAvgSides = []\n        for i in range(len(TriadCardDB.rarityList)):\n            TriadCardDB.mapRarity.append([])\n            TriadCardDB.mapAvgSides.append([1,1,1,1])\n\n        cardNames = {}\n        locXml = minidom.parse(assetFolder + 'loc.xml')\n        locElems = locXml.getElementsByTagName('loc')\n        for elem in locElems:\n            locType = int(elem.attributes['type'].value)\n            if locType == 3:\n                cardNames[elem.attributes['id'].value] = elem.attributes['en'].value\n\n        cardXml = minidom.parse(assetFolder + 'cards.xml')\n        cardElems = cardXml.getElementsByTagName('card')\n        for elem in cardElems:\n            cardOb = TriadCard(\n                cardNames[elem.attributes['id'].value],\n                int(elem.attributes['rarity'].value),\n                int(elem.attributes['type'].value),\n                int(elem.attributes['up'].value),\n                int(elem.attributes['lt'].value),\n                int(elem.attributes['dn'].value),\n                int(elem.attributes['rt'].value))\n\n            cardOb.id = len(TriadCardDB.cards)\n            TriadCardDB.cards.append(cardOb)\n            TriadCardDB.mapRarity[cardOb.rarity].append(cardOb)\n\n        for i in range(len(TriadCardDB.rarityList)):\n            avgSidesAcc = [0, 0, 0, 0]\n            for card in TriadCardDB.mapRarity[i]:\n                avgSidesAcc[0] += card.sides[0]\n                avgSidesAcc[1] += card.sides[1]\n                avgSidesAcc[2] += card.sides[2]\n                avgSidesAcc[3] += card.sides[3]\n\n            for side in range(4):\n                TriadCardDB.mapAvgSides[i][side] = avgSidesAcc[side] / len(TriadCardDB.mapRarity[i])\n\n        print('Loaded cards:',len(TriadCardDB.cards))\n\n    @staticmethod\n    def find(name):\n        for card in TriadCardDB.cards:\n            if (card.name == name):\n                return card\n        return TriadCardDB.cards[0]\n\n\n# load cards on startup\nTriadCardDB.load()\n"
  },
  {
    "path": "ml/solver/gamelogic/triadDeck.py",
    "content": "from .triadCard import *\nimport random\n\nclass TriadDeck():\n    cardNone = 0\n    cardHidden = 1\n    cardVisible = 2\n\n    def __init__(self):\n        self.cards = [ None, None, None, None, None ]\n        self.state = [ TriadDeck.cardNone, TriadDeck.cardNone, TriadDeck.cardNone, TriadDeck.cardNone, TriadDeck.cardNone ]\n        self.usedRarityCount = [ 0 ] * len(TriadCardDB.rarityList)\n        self.expectedRarityCount = [0, 0, 3, 1, 1]\n        self.numAvail = 0\n\n    def initialize(self, cards, state):\n        self.cards = cards\n        self.numAvail = len(cards)\n        for i in range(len(self.state)):\n            self.state[i] = state\n\n    def hasCard(self, idx):\n        return self.state[idx] != TriadDeck.cardNone\n\n    def useCard(self, idx):\n        card = self.cards[idx]\n        self.cards[idx] = None\n        self.state[idx] = TriadDeck.cardNone\n        self.numAvail -= 1\n        self.usedRarityCount[card.rarity] += 1\n        return card\n\n    def makeAllVisible(self):\n        for i in range(len(self.state)):\n            self.state[i] = TriadDeck.cardVisible\n\n    def onRestart(self):\n        self.numAvail = len(self.cards)\n        self.usedRarityCount = [ 0 ] * len(TriadCardDB.rarityList)\n\n    def getCardsInfo(self, forceVisible = False):\n        result = []\n        remainingRarity = self.expectedRarityCount.copy()\n        hiddenList = []\n        for i in range(len(self.cards)):\n            if (self.state[i] == TriadDeck.cardNone):\n                result.append([0, 0, None, 0])\n            elif (self.state[i] == TriadDeck.cardVisible) or forceVisible:\n                cardOb = self.cards[i]\n                remainingRarity[cardOb.rarity] -= 1\n                result.append([1, 1, cardOb, cardOb.rarity])\n            else:\n                result.append([])\n                hiddenList.append(i)\n\n        if len(hiddenList) > 0:\n            for i in range(len(remainingRarity)):\n                remainingRarity[i] -= self.usedRarityCount[i]\n\n                if remainingRarity[i] < 0:\n                    if i < (len(remainingRarity) - 1):\n                        remainingRarity[i + 1] += remainingRarity[i]\n                    remainingRarity[i] = 0\n                else:\n                    remainingRarity[i] = min(remainingRarity[i], len(hiddenList))\n                    if remainingRarity[i] > 0:\n                        for iR in range(remainingRarity[i]):\n                            cardIdx = hiddenList.pop()\n                            result[cardIdx] = [1, 0, None, i]\n\n        return result\n\n    def __str__(self):\n        stateDesc = ['none','hidden','visible']\n        desc = []\n        for i in range(len(self.state)):\n            desc += [ '[%i]:%s (%i*%s)' % (\n                i,\n                self.cards[i].name if isinstance(self.cards[i], TriadCard) else \"--\",\n                (self.cards[i].rarity + 1) if isinstance(self.cards[i], TriadCard) else 0,\n                \"\" if self.state[i] == TriadDeck.cardVisible else \", \" + stateDesc[self.state[i]]\n            )]\n\n        return ', '.join(desc)\n\n    @staticmethod\n    def generateDeckForRarityRange(state, minRarity, maxRarity):\n        cards = []\n        while len(cards) < 5:\n            rarity = random.randrange(minRarity, maxRarity + 1)\n            cardIdx = random.randrange(len(TriadCardDB.mapRarity[rarity]))\n            testCard = TriadCardDB.mapRarity[rarity][cardIdx]\n            if (testCard in cards) == False:\n                cards.append(testCard)\n\n        deck = TriadDeck()\n        deck.initialize(cards, state)\n        return deck\n\n    @staticmethod\n    def generateDeckRandom(state):\n        return TriadDeck.generateDeckForRarityRange(state, 0, 4)\n\n    @staticmethod\n    def generateDeckNpc(minRarity, maxRarity):\n        return TriadDeck.generateDeckForRarityRange(TriadDeck.cardHidden, minRarity, maxRarity)\n\n    @staticmethod\n    def generateDeckPlayer():\n        randR4 = random.randrange(len(TriadCardDB.mapRarity[4]))\n        randR3 = random.randrange(len(TriadCardDB.mapRarity[3]))\n        cards = [ TriadCardDB.mapRarity[4][randR4], TriadCardDB.mapRarity[3][randR3] ]\n\n        while len(cards) < 5:\n            rarity = random.randrange(3)\n            cardIdx = random.randrange(len(TriadCardDB.mapRarity[rarity]))\n            testCard = TriadCardDB.mapRarity[rarity][cardIdx]\n            if (testCard in cards) == False:\n                cards.append(testCard)\n\n        deck = TriadDeck()\n        deck.initialize(cards, TriadDeck.cardHidden)\n        return deck\n"
  },
  {
    "path": "ml/solver/gamelogic/triadGame.py",
    "content": "from .triadCard import TriadCardDB\nfrom enum import Enum\n\nclass TriadGameState(Enum):\n    Unknown = 0\n    BlueTurn = 1\n    RedTurn = 2\n    EndBlueWin = 3\n    EndDraw = 4\n    EndRedWin = 5\n\nclass TriadGame:\n    OwnerIdBlue = 1\n    OwnerIdRed = 2\n\n    ModTurnStart = 0\n    ModCardPlaced = 1\n    ModAllPlaced = 2\n    ModCaptureNei = 3\n    ModCaptureWeight = 4\n    ModCaptureCondition = 5\n    ModOverrides = 6\n\n    cachedNeis = []\n\n    def __init__(self):\n        self.board = [ None ] * 9\n        self.owner = [ 0 ] * 9\n        self.typeMod = [ 0 ] * len(TriadCardDB.typeList)\n        self.blueDeck = None\n        self.redDeck = None\n        self.mods = []\n        self.modOverrides = [ False ] * TriadGame.ModOverrides\n        self.numRestarts = 0\n        self.mapPlaced = [ 0, 0, 0 ]\n        self.forcedCardIdx = -1\n        self.forcedBoardIdx = -1\n        self.state = TriadGameState.Unknown\n        self.cachedSideValues = [[ 0, 0, 0, 0]] * 9\n        self.debug = False\n\n    def getNumCardsPlaced(self):\n        return sum(self.mapPlaced)\n\n    def getNumCardsByOwner(self, ownerId):\n        return sum([1 if (testId == ownerId) else 0 for testId in self.owner])\n\n    def getCardSideValue(self, card, side):\n        return max(1, min(10, card.sides[side] + self.typeMod[card.cardType]))\n\n    def getCardOppositeSideValue(self, card, side):\n        return self.getCardSideValue(card, (side + 2) % 4)\n\n    def initModifiers(self):\n        self.modOverrides = [ False ] * TriadGame.ModOverrides\n        for mod in self.mods:\n            mod.onMatchStart(self)\n            for idx in mod.overrides:\n                self.modOverrides[idx] = True\n\n    def restartGame(self):\n        self.board = [ None ] * 9\n        self.owner = [ 0 ] * 9\n        self.typeMod = [ 0 ] * len(TriadCardDB.typeList)\n        self.numRestarts += 1\n        self.mapPlaced = [ 0, 0, 0 ]\n        self.cachedSideValues = [[ 0, 0, 0, 0]] * 9\n        if self.debug:\n            print('restartGame',self.numRestarts)\n\n    def placeCardFromDeck(self, pos, idx, ownerId):\n        deck = self.blueDeck if ownerId == TriadGame.OwnerIdBlue else self.redDeck\n        isValid = self.isMoveValid(deck, pos, idx)\n        if isValid:\n            card = deck.useCard(idx)\n            self.mapPlaced[ownerId] += 1\n            self.placeCard(pos, card, ownerId)\n            return True\n\n        return False\n\n    def isMoveValid(self, deck, pos, idx):\n        if (self.forcedBoardIdx >= 0 and self.forcedBoardIdx != pos):\n            return False\n        if (self.forcedCardIdx >= 0 and self.forcedCardIdx != idx):\n            return False\n        if (not deck.hasCard(idx) or self.owner[pos] != 0):\n            return False\n        return True\n\n    def placeCard(self, pos, card, ownerId):\n        if self.debug:\n            print('Place card:%s, pos:%i, ownerId:%i' % (card, pos, ownerId))\n\n        if ((self.owner[pos] == 0) and card != None and card.valid):\n            self.setBoardRaw(pos, card, ownerId)\n\n            allowCombo = False\n            if self.modOverrides[TriadGame.ModCardPlaced]:\n                for mod in self.mods:\n                    mod.onCardPlaced(self, pos)\n                    allowCombo = allowCombo or mod.allowCombo\n\n            comboCounter = 0\n            comboList = self.getCaptures(pos, comboCounter)\n            while (allowCombo and len(comboList) > 0):\n                comboCounter += 1\n                nextCombo = []\n                for pos in comboList:\n                    nextComboPart = self.getCaptures(pos, comboCounter)\n                    nextCombo = nextCombo + nextComboPart\n                comboList = nextCombo\n\n            totalPlaced = sum(self.mapPlaced)\n            if (totalPlaced == len(self.board)):\n                self.onAllCardsPlaced()\n\n            # count again, all placed may trigger sudden death rule\n            totalPlaced = sum(self.mapPlaced)\n            if (totalPlaced < len(self.board)):\n                self.onTurnStart()\n        else:\n            print('ERROR: failed to place card:%s, pos:%i, ownerId:%i' % (card, pos, ownerId))\n            self.showState('DEBUG')\n\n    def setBoardRaw(self, pos, card, ownerId):\n        self.board[pos] = card\n        self.owner[pos] = ownerId\n        self.state = TriadGameState.BlueTurn if (ownerId == TriadGame.OwnerIdRed) else TriadGameState.RedTurn\n        self.updateCachedValuesForCard(pos)\n\n    def updateCachedValuesForCard(self, pos):\n        card = self.board[pos]\n        if card != None:\n            self.cachedSideValues[pos] = [ self.getCardSideValue(card, side) for side in range(4) ]\n        else:\n            self.cachedSideValues[pos] = [0, 0, 0, 0]\n\n    def onTurnStart(self):\n        self.forcedBoardIdx = -1\n        self.forcedCardIdx = -1\n        if (self.state == TriadGameState.BlueTurn or self.state == TriadGameState.RedTurn) and self.modOverrides[TriadGame.ModTurnStart]:\n            for mod in self.mods:\n                mod.onTurnStart(self)\n\n    def onAllCardsPlaced(self):\n        numPlayerCards = self.blueDeck.numAvail + self.getNumCardsByOwner(TriadGame.OwnerIdBlue)\n        numPlayerOwnedToWin = 5\n        if self.debug:\n            print('onAllCardsPlaced, player:%i+%i vs %i' % (self.blueDeck.numAvail, self.getNumCardsByOwner(TriadGame.OwnerIdBlue), numPlayerOwnedToWin))\n\n        if (numPlayerCards > numPlayerOwnedToWin):\n            self.state = TriadGameState.EndBlueWin\n        elif (numPlayerCards == numPlayerOwnedToWin):\n            self.state = TriadGameState.EndDraw\n        else:\n            self.state = TriadGameState.EndRedWin\n\n        if self.modOverrides[TriadGame.ModAllPlaced]:\n            for mod in self.mods:\n                mod.onAllCardsPlaced(self)\n\n\n    @staticmethod\n    def getBoardPos(x, y):\n        return x + (y * 3)\n\n    def getCaptures(self, pos, comboCounter):\n        neiInfo = TriadGame.cachedNeis[pos]\n        if self.debug:\n            print('getCaptures(',pos,', combo:',comboCounter,'), neis:',neiInfo)\n\n        comboList = []\n        allowMods = comboCounter == 0\n\n        if allowMods and self.modOverrides[TriadGame.ModCaptureNei]:\n            for mod in self.mods:\n                comboListPart = mod.getCaptureNeis(self, pos, neiInfo)\n                if self.debug:\n                    print('>> capture(',mod.name,') = ',comboListPart)\n\n                if (len(comboListPart) > 0):\n                    comboList = comboList + comboListPart\n\n        for side, neiPos in neiInfo:\n            if self.debug:\n                print('>> side:%i, neiPos:%i' % (side, neiPos))\n\n            if ((self.owner[neiPos] != 0) and (self.owner[neiPos] != self.owner[pos])):\n                sideV = self.cachedSideValues[pos][side]\n                neiV = self.cachedSideValues[neiPos][(side + 2) % 4] # opposite side\n\n                if allowMods and self.modOverrides[TriadGame.ModCaptureWeight]:\n                    for mod in self.mods:\n                        sideV, neiV = mod.getCaptureWeights(self, sideV, neiV)\n\n                if self.debug:\n                    print('    sideV:%i neiV:%i' % (sideV, neiV))\n\n                canCaptureNei = sideV > neiV\n\n                if allowMods and self.modOverrides[TriadGame.ModCaptureCondition]:\n                    for mod in self.mods:\n                        overrideCapture, newcanCaptureNei = mod.getCaptureCondition(self, neiV, sideV)\n                        if overrideCapture:\n                            canCaptureNei = newcanCaptureNei\n\n                if canCaptureNei:\n                    if self.debug:\n                        print('    captured!')\n\n                    self.owner[neiPos] = self.owner[pos]\n                    if comboCounter > 0:\n                        comboList.append(neiPos)\n\n        return comboList\n\n\n    def showState(self, debugName):\n        print('[%s] ==> State: %s' % (debugName, self.state.name))\n        for i in range(len(self.owner)):\n            if (self.owner[i] == 0):\n                print('  [%i] empty' % i)\n            else:\n                print('  [%i] %s owner:%i' % (i, str(self.board[i]), self.owner[i]))\n\n        print('Type mods:', '(empty)' if (sum(self.typeMod) == 0) else '')\n        for i in range(len(self.typeMod)):\n            if (self.typeMod[i] != 0):\n                print('  [%i] %i' % (i,self.typeMod[i]))\n\n        print('Player hand:', '(empty)' if (self.blueDeck == None or self.blueDeck.numAvail == 0) else '')\n        if self.blueDeck != None:\n            for i in range(5):\n                print('  [%i]: %s' % (i, str(self.blueDeck.cards[i])))\n\n        print('Opponent hand:', '(empty)' if (self.redDeck == None or self.redDeck.numAvail == 0) else '')\n        if self.redDeck != None:\n            for i in range(5):\n                print('  [%i]: %s, state:%s' % (i, str(self.redDeck.cards[i]), self.redDeck.state[i]))\n\n        print('Modifiers:', '(empty)' if (len(self.mods) == 0) else '')\n        for i in range(len(self.mods)):\n            print('  [%i]: %s' % (i, self.mods[i].name))\n\n        if (self.forcedCardIdx >= 0 or self.forcedBoardIdx >= 0):\n            print('Forced card:%i, placement:%i' % (self.forcedCardIdx, self.forcedBoardIdx))\n\n    @staticmethod\n    def staticInit():\n        for i in range(9):\n            boardX = i % 3\n            boardY = int(i / 3)\n            info = []\n            # [up, left, down, right]\n            if boardY > 0:\n                info.append([0, TriadGame.getBoardPos(boardX, boardY - 1)])\n            if boardX < 2:\n                info.append([1, TriadGame.getBoardPos(boardX + 1, boardY)])\n            if boardY < 2:\n                info.append([2, TriadGame.getBoardPos(boardX, boardY + 1)])\n            if boardX > 0:\n                info.append([3, TriadGame.getBoardPos(boardX - 1, boardY)])\n\n            TriadGame.cachedNeis.append(info)\n\nTriadGame.staticInit()\n"
  },
  {
    "path": "ml/solver/gamelogic/triadMods.py",
    "content": "import random\nfrom .triadDeck import TriadDeck\nfrom .triadGame import TriadGame, TriadGameState\nimport numpy as np\n\nclass TriadMod:\n    allowCombo = False\n    name = '??'\n    blockedMods = []\n    modDB = []\n\n    def onMatchStart(self, game):\n        pass\n    def onTurnStart(self, game):\n        pass\n    def onCardPlaced(self, game, pos):\n        pass\n    def onAllCardsPlaced(self, game):\n        pass\n    def getCaptureNeis(self, game, pos, neis):\n        return []\n    def getCaptureWeights(self, game, cardNum, neiNum):\n        return cardNum, neiNum\n    def getCaptureCondition(self, game, cardNum, neiNum):\n        return False, False\n\n    @staticmethod\n    def generateMods(maxMods = 4):\n        numMods = random.randrange(0, maxMods + 1)\n        mods = []\n        blocked = []\n\n        while len(mods) < numMods:\n            idx = random.randrange(0, len(TriadMod.modDB))\n            testMod = TriadMod.modDB[idx]\n            if any(testMod.name in s for s in blocked):\n                continue\n\n            mods.append(testMod)\n            blocked += testMod.blockedMods\n            blocked.append(testMod.name)\n\n        return mods\n\n\n# reverse: capture when number of lower\nclass TriadModReverse(TriadMod):\n    def __init__(self):\n        self.name = 'Reverse'\n        self.overrides = [TriadGame.ModCaptureCondition]\n\n    def getCaptureCondition(self, game, cardNum, neiNum):\n        return True, cardNum < neiNum\n\nTriadMod.modDB.append(TriadModReverse())\n\n# fallen ace: 1 captures 10/A\nclass TriadModFallenAce(TriadMod):\n    def __init__(self):\n        self.name = 'FallenAce'\n        self.overrides = [TriadGame.ModCaptureWeight]\n\n    def getCaptureWeights(self, game, cardNum, neiNum):\n        if (cardNum == 10) and (neiNum == 1):\n            return 0, 1\n        elif (cardNum == 1) and (neiNum == 10):\n            return 1, 0\n        else:\n            return cardNum, neiNum\n\nTriadMod.modDB.append(TriadModFallenAce())\n\n# same: capture when 2+ sides have the same values\nclass TriadModSame(TriadMod):\n    def __init__(self):\n        self.name = 'Same'\n        self.allowCombo = True\n        self.overrides = [TriadGame.ModCaptureNei]\n\n    def getCaptureNeis(self, game, pos, neis):\n        captureNeis = []\n        sameNeis = []\n        for side,neiPos in neis:\n            if (game.owner[neiPos] != 0):\n                sideV = game.cachedSideValues[pos][side]\n                neiV = game.cachedSideValues[neiPos][(side + 2) % 4] # opp side\n                if (sideV == neiV):\n                    sameNeis.append(neiPos)\n                    if (game.owner[neiPos] != game.owner[pos]):\n                        captureNeis.append(neiPos)\n\n        captureList = []\n        if (len(sameNeis) >= 2) and (len(captureNeis) > 0):\n            captureList = captureNeis\n\n        return captureList\n\nTriadMod.modDB.append(TriadModSame())\n\n# plus: capture when 2+ sides have the same sum of values\nclass TriadModPlus(TriadMod):\n    def __init__(self):\n        self.name = 'Plus'\n        self.allowCombo = True\n        self.overrides = [TriadGame.ModCaptureNei]\n\n    def getCaptureNeis(self, game, pos, neis):\n        cardOwner = game.owner[pos]\n        captureList = []\n        for side,neiPos in neis:\n            if game.owner[neiPos] != 0:\n                if (game.owner[neiPos] != cardOwner):\n                    sideV = game.cachedSideValues[pos][side]\n                    neiV = game.cachedSideValues[neiPos][(side + 2) % 4] # opp side\n                    totalV = sideV + neiV\n                    captured = False\n\n                    for vsSide,vsNeiPos in neis:\n                        if ((game.owner[vsNeiPos] != 0) and (vsSide != side)):\n                            vsSideV = game.cachedSideValues[pos][vsSide]\n                            vsSideNeiV = game.cachedSideValues[vsNeiPos][(vsSide + 2) % 4] # opp side\n                            vsSideTotalV = vsSideV + vsSideNeiV\n\n                            if (vsSideTotalV == totalV):\n                                captured = True\n                                if (game.owner[vsNeiPos] != cardOwner):\n                                    game.owner[vsNeiPos] = cardOwner\n                                    captureList.append(vsNeiPos)\n\n                    if captured:\n                        game.owner[neiPos] = cardOwner\n                        captureList.append(neiPos)\n\n        return captureList\n\nTriadMod.modDB.append(TriadModPlus())\n\n# ascention: type mod goes up after each placement\nclass TriadModAscention(TriadMod):\n    def __init__(self):\n        self.name = 'Ascention'\n        self.blockedMods = ['Descention']\n        self.overrides = [TriadGame.ModCardPlaced]\n\n    def onCardPlaced(self, game, pos):\n        cardType = game.board[pos].cardType\n        if (cardType> 0):\n            game.typeMod[cardType] += 1\n\nTriadMod.modDB.append(TriadModAscention())\n\n# descention: type mod goes down after each placement\nclass TriadModDescention(TriadMod):\n    def __init__(self):\n        self.name = 'Descention'\n        self.blockedMods = ['Ascention']\n        self.overrides = [TriadGame.ModCardPlaced]\n\n    def onCardPlaced(self, game, pos):\n        cardType = game.board[pos].cardType\n        if (cardType> 0):\n            game.typeMod[cardType] -= 1\n\nTriadMod.modDB.append(TriadModDescention())\n\n# sudden death: restart game on draw, keeping card ownership, max 3 times\nclass TriadModSuddenDeath(TriadMod):\n    def __init__(self):\n        self.name = 'SuddenDeath'\n        self.overrides = [TriadGame.ModAllPlaced]\n\n    def onAllCardsPlaced(self, game):\n        if ((game.state == TriadGameState.EndDraw) and (game.numRestarts < 3)):\n            for i in range(len(game.owner)):\n                if (game.owner[i] == 0):\n                    continue\n                deck = game.blueDeck if (game.owner[i] == TriadGame.OwnerIdBlue) else game.redDeck\n                for freeIdx in range(5):\n                    if not deck.hasCard(freeIdx):\n                        deck.cards[freeIdx] = game.board[i]\n                        deck.state[freeIdx] = TriadDeck.cardVisible\n                        break\n\n            nextTurn = TriadGameState.BlueTurn if (game.mapPlaced[TriadGame.OwnerIdBlue] < 5) else TriadGameState.RedTurn\n            game.restartGame()\n            game.state = nextTurn\n            game.blueDeck.onRestart()\n            game.redDeck.onRestart()\n\nTriadMod.modDB.append(TriadModSuddenDeath())\n\n# all open: makes all opponent cards on hand visible\nclass TriadModAllOpen(TriadMod):\n    def __init__(self):\n        self.name = 'AllOpen'\n        self.blockedMods = ['ThreeOpen']\n        self.overrides = []\n\n    def onMatchStart(self, game):\n        game.redDeck.makeAllVisible()\n\nTriadMod.modDB.append(TriadModAllOpen())\n\n# three open: makes random 3 opponent cards on hand visible\nclass TriadModThreeOpen(TriadMod):\n    def __init__(self):\n        self.name = 'ThreeOpen'\n        self.blockedMods = ['AllOpen']\n        self.overrides = []\n\n    def onMatchStart(self, game):\n        random3 = np.random.choice(range(5), 3, False)\n        for idx in random3:\n            game.redDeck.state[idx] = TriadDeck.cardVisible\n        pass\n\nTriadMod.modDB.append(TriadModThreeOpen())\n\n# order: forces card selection order (sequential from hand)\nclass TriadModOrder(TriadMod):\n    def __init__(self):\n        self.name = 'Order'\n        self.blockedMods = ['Chaos']\n        self.overrides = [TriadGame.ModTurnStart]\n\n    def onTurnStart(self, game):\n        deck = game.blueDeck if (game.state == TriadGameState.BlueTurn) else game.blueDeck\n        for i in range(len(deck.cards)):\n            if deck.hasCard(i):\n                game.forcedCardIdx = i\n                break\n\nTriadMod.modDB.append(TriadModOrder())\n\n# chaos: forces board placement order (random)\nclass TriadModChaos(TriadMod):\n    def __init__(self):\n        self.name = 'Chaos'\n        self.blockedMods = ['Order']\n        self.overrides = [TriadGame.ModTurnStart]\n\n    def onTurnStart(self, game):\n        availPos = []\n        for i in range(len(game.owner)):\n            if (game.owner[i] == 0):\n                availPos.append(i)\n\n        if (len(availPos) > 0):\n            game.forcedBoardIdx = np.random.choice(availPos)\n\n\nTriadMod.modDB.append(TriadModChaos())\n\n# swap: swaps a card between player and opponent (1 each) at match start\nclass TriadModSwap(TriadMod):\n    def __init__(self):\n        self.name = 'Swap'\n        self.overrides = []\n\n    def onMatchStart(self, game):\n        playerIdx = np.random.randint(0, 5)\n        opponentIdx = np.random.randint(0, 5)\n\n        swapCard = game.redDeck.cards[opponentIdx]\n        game.redDeck.cards[opponentIdx] = game.blueDeck.cards[playerIdx]\n        game.redDeck.state[opponentIdx] = TriadDeck.cardVisible\n        game.blueDeck.cards[playerIdx] = swapCard\n        if game.debug:\n            print('SWAP player[%i] <> opp[%i]' % (playerIdx, opponentIdx))\n\nTriadMod.modDB.append(TriadModSwap())\nprint('Loaded game modifiers:',len(TriadMod.modDB))\n"
  },
  {
    "path": "ml/solver/main.py",
    "content": "from gameSession import GameSession\nfrom environment import Environment\nfrom agents.agentRandom import AgentRandom\nfrom agents.agentDQN import AgentDQN\nfrom tqdm import tqdm\nimport matplotlib.pyplot as plt\nimport time\n\ndef trainModel():\n    numEpochs = 20\n    numGamesToTrain = 200\n    numGamesToEval = 1000\n\n    game = GameSession(useModifiers=True)\n    trainingAgent = AgentDQN(game)\n    randomAgent = AgentRandom(game)\n\n    learningEnv = Environment(game, [trainingAgent, trainingAgent])\n    evalEnv = Environment(game, [trainingAgent, randomAgent])\n\n    # load checkpoint data\n    #trainingAgent.load('data/model.tmp')\n\n    print('Start training, epochs:',numEpochs)\n    timeStart = time.time()\n    history = []\n    for epochIdx in range(numEpochs):\n        # play a lot and learn and stuff\n        for _ in tqdm(range(numGamesToTrain), leave=False, desc='Training'.ljust(15)):\n            learningEnv.runTrainingGame()\n\n        # training update after exploration step\n        trainingAgent.train()\n\n        # eval with same seed for every iteration\n        randomAgent.setSeed(0)\n        score = 0\n        for _ in tqdm(range(numGamesToEval), leave=False, desc='Evaluating'.ljust(15)):\n            rewards = evalEnv.runEvalGame()\n            score += rewards[0]\n\n        # collect info about training to see if it's actually working (lol, who am i kidding, it just fails constantly T.T)\n        stepDetails = trainingAgent.getTrainingDetails()\n        stepDetails['score'] = score\n\n        history.append(stepDetails)\n        print('[%d] %s' % (epochIdx, stepDetails))\n\n    timeElapsed = time.time() - timeStart\n    print('Done. Total time:%s, epoch avg:%.0fs' % (time.strftime(\"%H:%M:%Ss\", time.gmtime(timeElapsed)), timeElapsed / numEpochs))\n\n    trainingAgent.save('data/model.tmp')\n    trainingAgent.generateModelCode('data/model.cs')\n    return history\n\ndef showTrainingData(history):\n    epochs = range(len(history))\n    fig, axs = plt.subplots(2, sharex=True)\n    axs[0].title.set_text('Reward')\n    axs[0].plot(epochs, [stepDetails['reward'] for stepDetails in history])\n    axs[1].title.set_text('Loss')\n    axs[1].plot(epochs, [stepDetails['loss'] for stepDetails in history])\n    plt.xlabel('Epochs')\n    plt.show()\n\nif __name__ == \"__main__\":\n    history = trainModel()\n    showTrainingData(history)\n"
  },
  {
    "path": "ml/solver/perfTests.py",
    "content": "import numpy as np\nimport random\nfrom tqdm import tqdm\n\nfrom gameSession import GameSession\nfrom environment import Environment\nfrom agents.agentRandom import AgentRandom\nfrom agents.agentDQN import AgentDQN\n\ndef runTestPlayRandomGames(numGames, reproSeed):\n    if reproSeed < 0:\n        print('Running',numGames,'random test games...')\n\n        for _ in tqdm(range(numGames)):\n            seed = random.randrange(999999)\n            random.seed(seed)\n            np.random.seed(seed)\n\n            session = GameSession()\n            session.randomSeed = seed\n            while not session.isFinished():\n                playerId = session.getCurrentPlayer()\n                state = session.getState(playerId)\n                actions = session.getAllowedActions(state)\n                action = random.choice(actions)\n                session.step(action)\n    else:\n        random.seed(reproSeed)\n        np.random.seed(reproSeed)\n\n        session = GameSession()\n        session.game.debug = True\n        session.randomSeed = reproSeed\n\n        print('DEBUG game')\n        print('  mods:', [mod.name for mod in session.game.mods])\n        print('  blue:', session.game.blueDeck)\n        print('  red:', session.game.redDeck)\n\n        while not session.isFinished():\n            playerId = session.getCurrentPlayer()\n            state = session.getState(playerId)\n            actions = session.getAllowedActions(state)\n            action = random.choice(actions)\n\n            print('Move, player:%d numActions:%d (board:%d + card:%d) => action:%d (board:%d + card:%d)' %\n                (playerId, len(actions),\n                sum([ state[i] for i in range(9) ]),\n                sum([ state[9 + i] for i in range(5) ]),\n                action, action % 9, int(action / 9)))\n\n            session.step(action)\n\n\ndef runTestAgentEval(numGames, agentName):\n    print('Running',numGames,'eval games for agent',agentName)\n\n    game = GameSession()\n    evalAgent = AgentRandom(game)\n    if agentName == 'dqn':\n        evalAgent = AgentDQN(game)\n\n    env = Environment(game, [evalAgent, AgentRandom(game)])\n\n    score = 0\n    for _ in tqdm(range(numGames)):\n        rewards = env.runEvalGame()\n        score += rewards[0]\n\n\nif __name__ == \"__main__\":\n    runTestPlayRandomGames(10000, reproSeed=-1)\n    runTestAgentEval(10000, 'random')\n    runTestAgentEval(10000, 'dqn')\n"
  },
  {
    "path": "ml/solver/utils/codeGenerator.py",
    "content": "import os\nimport textwrap\n\nclass CodeGenerator():\n    def __init__(self):\n        self.layers = []\n\n    def addLayer(self, weights, biases, activation):\n        self.layers.append((weights, biases, activation))\n\n    def save(self, name):\n        lines = []\n        lines.append('// Auto generated model data')\n        lines.append('private void InitModel()')\n        lines.append('{')\n\n        numInputs = self.layers[0][0].shape[1]\n        lines.append('\\tNumInputs = %d;' % numInputs)\n\n        numOutputs = [str(layer[0].shape[0]) for layer in self.layers]\n        lines.append('\\tNumOutputs = {%s};' % ', '.join(numOutputs))\n\n        for i in range(len(self.layers)):\n            lines.append('\\tLayer%d_W = %s;' % (i, self.convertToCSArray(self.layers[i][0])))\n            lines.append('\\tLayer%d_B = %s;' % (i, self.convertToCSArray(self.layers[i][1])))\n\n        lines.append('}')\n\n        file = open(name, 'w')\n        for line in lines:\n            wrapLines = textwrap.wrap(line, width=120, tabsize=4, subsequent_indent='\\t\\t')\n            for fileLine in wrapLines:\n                file.write(fileLine + '\\n')\n\n        file.close()\n        print('Saved generated code:',name)\n\n    def convertToCSArray(self, arr):\n        values = []\n        if len(arr.shape) == 1:\n            values = [str(x) for x in arr]\n        else:\n            for i in range(arr.shape[0]):\n                values.append(self.convertToCSArray(arr[i]))\n\n        return '{' + ', '.join(values) + '}'\n"
  },
  {
    "path": "ml/solver/utils/estimatorTorch.py",
    "content": "import numpy as np\nimport torch\nimport torch.nn as nn\nfrom .codeGenerator import CodeGenerator\n\ntorchDevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nclass EstimatorNN(nn.Module):\n    def __init__(self, numInputs, numHiddenArr, numOutputs):\n        super(EstimatorNN, self).__init__()\n\n        layerSizes = [ numInputs ] + numHiddenArr\n        modelArgs = [nn.Flatten()]\n        for i in range(len(layerSizes) - 1):\n            modelArgs.append(nn.Linear(layerSizes[i], layerSizes[i + 1]))\n            modelArgs.append(nn.ReLU())\n        modelArgs.append(nn.Linear(layerSizes[-1], numOutputs))\n\n        self.model = nn.Sequential(*modelArgs)\n\n    def forward(self, s):\n        return self.model(s)\n\n    def generateModelCode(self, name):\n        sd = self.state_dict()\n        weightSuffix = \".weight\"\n        biasSuffix = \".bias\"\n        layers = []\n\n        # layers will be in the same order as model\n        for entryName in sd.keys():\n            if entryName.endswith(weightSuffix):\n                biasName = entryName.replace(weightSuffix, biasSuffix)\n                if biasName in sd.keys():\n                    layers.append({\n                        'w': sd[entryName].cpu().numpy(),\n                        'b':sd[biasName].cpu().numpy(),\n                        'act':'relu',\n                        'name':entryName.replace(weightSuffix, '')\n                        })\n\n        layers[-1]['act'] = ''\n        codeGen = CodeGenerator()\n        for layer in layers:\n            codeGen.addLayer(layer['w'], layer['b'], layer['act'])\n        codeGen.save(name)\n\n\nclass EstimatorModel:\n    def __init__(self, numInputs, numHiddenArr, numOutput, learningRate):\n        self.nn = EstimatorNN(numInputs, numHiddenArr, numOutput).to(torchDevice)\n        self.nn.eval()\n        self.lossFunc = nn.MSELoss(reduction='mean')\n        self.optimizer = torch.optim.Adam(self.nn.parameters(), lr=learningRate)\n\n    def predict(self, input):\n        with torch.no_grad():\n            input = torch.tensor(input).float().to(torchDevice)\n            return self.nn(input).cpu().numpy()\n\n    def fit(self, inputS, inputA, targetAR):\n        self.nn.train()\n\n        inputS = torch.tensor(inputS).float().to(torchDevice)\n        inputA = torch.tensor(inputA).long().to(torchDevice)\n        targetAR = torch.tensor(targetAR).float().to(torchDevice)\n\n        outputAProbs = self.nn(inputS)\n        outputAR = torch.gather(outputAProbs, dim=-1, index=inputA.unsqueeze(-1)).squeeze(-1)\n        loss = self.lossFunc(outputAR, targetAR)\n\n        self.optimizer.zero_grad()\n        loss.backward()\n        self.optimizer.step()\n        self.nn.eval()\n        return loss.item()\n\n    def save(self, name):\n        torch.save(self.nn.state_dict(), name)\n\n    def load(self, name):\n        self.nn.load_state_dict(torch.load(name))\n        self.nn.eval()\n\n    def generateModelCode(self, name):\n        self.nn.generateModelCode(name)\n"
  },
  {
    "path": "ml/solver/utils/trainingMemory.py",
    "content": "from collections import deque\nimport random\n\nclass TrainingMemory:\n    def __init__(self, capacity):\n        self.capacity = capacity\n        self.clear()\n\n    def add(self, sampleTuple):\n        self.numWrites += 1\n        if len(self) < self.capacity:\n            self.buffer.append(sampleTuple)\n            return True\n        return False\n\n    def clear(self):\n        self.numWrites = 0\n        self.buffer = deque([], maxlen=self.capacity)\n\n    def __len__(self):\n        return len(self.buffer)\n\n    def sample(self, batchSize):\n        samples = random.sample(self.buffer, batchSize)\n        return zip(*samples)\n\n\nclass TrainingMemoryCircular(TrainingMemory):\n    def add(self, sampleTuple):\n        isAdded = super().add(sampleTuple)\n        if not isAdded:\n            writeIdx = self.numWrites % self.capacity\n            self.buffer[writeIdx] = sampleTuple\n\n\nclass TrainingMemoryReservoir(TrainingMemory):\n    def add(self, sampleTuple):\n        isAdded = super().add(sampleTuple)\n        if not isAdded:\n            # Algorithm R\n            writeIdx = random.randint(0, self.numWrites)\n            if writeIdx < self.capacity:\n                self.buffer[writeIdx] = sampleTuple\n"
  },
  {
    "path": "sources/App.config",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<configuration>\n    <startup> \n        <supportedRuntime version=\"v4.0\" sku=\".NETFramework,Version=v4.8\"/>\n    </startup>\n</configuration>\n"
  },
  {
    "path": "sources/FFTriadBuddy.csproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <Import Project=\"$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props\" Condition=\"Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props')\" />\n  <PropertyGroup>\n    <Configuration Condition=\" '$(Configuration)' == '' \">Debug</Configuration>\n    <Platform Condition=\" '$(Platform)' == '' \">AnyCPU</Platform>\n    <ProjectGuid>{4FE48C06-9E9C-46C9-A53E-CFE06EFDCBDC}</ProjectGuid>\n    <OutputType>WinExe</OutputType>\n    <RootNamespace>FFTriadBuddy</RootNamespace>\n    <AssemblyName>FFTriadBuddy</AssemblyName>\n    <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>\n    <FileAlignment>512</FileAlignment>\n    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>\n    <IsWebBootstrapper>false</IsWebBootstrapper>\n    <TargetFrameworkProfile />\n    <PublishUrl>publish\\</PublishUrl>\n    <Install>true</Install>\n    <InstallFrom>Disk</InstallFrom>\n    <UpdateEnabled>false</UpdateEnabled>\n    <UpdateMode>Foreground</UpdateMode>\n    <UpdateInterval>7</UpdateInterval>\n    <UpdateIntervalUnits>Days</UpdateIntervalUnits>\n    <UpdatePeriodically>false</UpdatePeriodically>\n    <UpdateRequired>false</UpdateRequired>\n    <MapFileExtensions>true</MapFileExtensions>\n    <ApplicationRevision>0</ApplicationRevision>\n    <ApplicationVersion>1.0.0.%2a</ApplicationVersion>\n    <UseApplicationTrust>false</UseApplicationTrust>\n    <BootstrapperEnabled>true</BootstrapperEnabled>\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' \">\n    <PlatformTarget>AnyCPU</PlatformTarget>\n    <DebugSymbols>true</DebugSymbols>\n    <DebugType>full</DebugType>\n    <Optimize>false</Optimize>\n    <OutputPath>bin\\Debug\\</OutputPath>\n    <DefineConstants>DEBUG;TRACE</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>\n    <Prefer32Bit>false</Prefer32Bit>\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' \">\n    <PlatformTarget>AnyCPU</PlatformTarget>\n    <DebugType>pdbonly</DebugType>\n    <Optimize>true</Optimize>\n    <OutputPath>bin\\Release\\</OutputPath>\n    <DefineConstants>TRACE</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>\n    <Prefer32Bit>false</Prefer32Bit>\n  </PropertyGroup>\n  <PropertyGroup>\n    <ApplicationIcon>card.ico</ApplicationIcon>\n  </PropertyGroup>\n  <ItemGroup>\n    <Reference Include=\"System\" />\n    <Reference Include=\"System.Core\" />\n    <Reference Include=\"System.IO.Compression\" />\n    <Reference Include=\"System.IO.Compression.FileSystem\" />\n    <Reference Include=\"System.Numerics\" />\n    <Reference Include=\"System.Xml.Linq\" />\n    <Reference Include=\"System.Data.DataSetExtensions\" />\n    <Reference Include=\"Microsoft.CSharp\" />\n    <Reference Include=\"System.Data\" />\n    <Reference Include=\"System.Deployment\" />\n    <Reference Include=\"System.Drawing\" />\n    <Reference Include=\"System.Net.Http\" />\n    <Reference Include=\"System.Windows.Forms\" />\n    <Reference Include=\"System.Xml\" />\n    <Reference Include=\"System.Xaml\">\n      <RequiredTargetFramework>4.0</RequiredTargetFramework>\n    </Reference>\n    <Reference Include=\"WindowsBase\" />\n    <Reference Include=\"PresentationCore\" />\n    <Reference Include=\"PresentationFramework\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ApplicationDefinition Include=\"ui\\App.xaml\">\n      <Generator>MSBuild:Compile</Generator>\n      <SubType>Designer</SubType>\n    </ApplicationDefinition>\n    <Compile Include=\"data\\ImageHashDB.cs\" />\n    <Compile Include=\"data\\LocalizationDB.cs\" />\n    <Compile Include=\"data\\PlayerSettingsDB.cs\" />\n    <Compile Include=\"data\\TriadCardDB.cs\" />\n    <Compile Include=\"data\\TriadNpcDB.cs\" />\n    <Compile Include=\"data\\TriadTournamentDB.cs\" />\n    <Compile Include=\"gamelogic\\FavDeckSolver.cs\" />\n    <Compile Include=\"gamelogic\\MiniCactpotGame.cs\" />\n    <Compile Include=\"gamelogic\\TriadCard.cs\" />\n    <Compile Include=\"gamelogic\\TriadDeck.cs\" />\n    <Compile Include=\"gamelogic\\TriadDeckOptimizer.cs\" />\n    <Compile Include=\"gamelogic\\TriadGameSolver.cs\" />\n    <Compile Include=\"gamelogic\\TriadGameSimulation.cs\" />\n    <Compile Include=\"gamelogic\\TriadGameModifier.cs\" />\n    <Compile Include=\"gamelogic\\TriadGameScreenMemory.cs\" />\n    <Compile Include=\"gamelogic\\tests\\TriadGameScreenTests.cs\" />\n    <Compile Include=\"gamelogic\\tests\\TriadGameTests.cs\" />\n    <Compile Include=\"gamelogic\\TriadGameAgent.cs\" />\n    <Compile Include=\"googleapi\\GoogleDriveService.cs\" />\n    <Compile Include=\"googleapi\\GoogleOAuth2.cs\" />\n    <Compile Include=\"loc\\strings.cs\" />\n    <Compile Include=\"ui\\modelproxy\\NpcModelProxy.cs\" />\n    <Compile Include=\"ui\\modelproxy\\RuleModelProxy.cs\" />\n    <Compile Include=\"ui\\modelproxy\\SettingsModel.cs\" />\n    <Compile Include=\"ui\\modelproxy\\TournamentModelProxy.cs\" />\n    <Compile Include=\"ui\\modelproxy\\TriadGameModel.cs\" />\n    <Compile Include=\"ui\\viewmodel\\AdjustCardViewModel.cs\" />\n    <Compile Include=\"ui\\viewmodel\\AdjustHashViewModel.cs\" />\n    <Compile Include=\"ui\\viewmodel\\ContextActionViewModel.cs\" />\n    <Compile Include=\"ui\\viewmodel\\FavDeckEditViewModel.cs\" />\n    <Compile Include=\"ui\\viewmodel\\ImageCardDataViewModel.cs\" />\n    <Compile Include=\"ui\\modelproxy\\ImageHashDataModelProxy.cs\" />\n    <Compile Include=\"ui\\viewmodel\\LocalSavesViewModel.cs\" />\n    <Compile Include=\"ui\\viewmodel\\MainWindowViewModel.cs\" />\n    <Compile Include=\"ui\\viewmodel\\OverlayWindowViewModel.cs\" />\n    <Compile Include=\"ui\\viewmodel\\PageCardsViewModel.cs\" />\n    <Compile Include=\"ui\\viewmodel\\PageInfoViewModel.cs\" />\n    <Compile Include=\"ui\\viewmodel\\PageNpcsViewModel.cs\" />\n    <Compile Include=\"ui\\viewmodel\\PageScreenshotViewModel.cs\" />\n    <Compile Include=\"ui\\viewmodel\\PageSetupViewModel.cs\" />\n    <Compile Include=\"ui\\viewmodel\\PageSimulateViewModel.cs\" />\n    <Compile Include=\"ui\\viewmodel\\SetupFavDeckViewModel.cs\" />\n    <Compile Include=\"ui\\viewmodel\\SimulateRulesViewModel.cs\" />\n    <Compile Include=\"ui\\viewmodel\\SolvableDeckViewModel.cs\" />\n    <Compile Include=\"ui\\viewmodel\\WinChanceViewModel.cs\" />\n    <Compile Include=\"ui\\view\\AdjustCardDialog.xaml.cs\">\n      <DependentUpon>AdjustCardDialog.xaml</DependentUpon>\n    </Compile>\n    <Compile Include=\"ui\\view\\AdjustHashDialog.xaml.cs\">\n      <DependentUpon>AdjustHashDialog.xaml</DependentUpon>\n    </Compile>\n    <Compile Include=\"ui\\view\\DialogWindow.xaml.cs\">\n      <DependentUpon>DialogWindow.xaml</DependentUpon>\n    </Compile>\n    <Compile Include=\"ui\\view\\FavDeckEditDialog.xaml.cs\">\n      <DependentUpon>FavDeckEditDialog.xaml</DependentUpon>\n    </Compile>\n    <Compile Include=\"ui\\view\\FavDeckPreview.xaml.cs\">\n      <DependentUpon>FavDeckPreview.xaml</DependentUpon>\n    </Compile>\n    <Compile Include=\"ui\\view\\OverlayWindowTransparent.xaml.cs\">\n      <DependentUpon>OverlayWindowTransparent.xaml</DependentUpon>\n    </Compile>\n    <Compile Include=\"ui\\view\\OverlayWindowInteractive.xaml.cs\">\n      <DependentUpon>OverlayWindowInteractive.xaml</DependentUpon>\n    </Compile>\n    <Compile Include=\"ui\\view\\utils\\CanvasExtensions.cs\" />\n    <Compile Include=\"ui\\view\\utils\\Converters.cs\" />\n    <Compile Include=\"ui\\view\\utils\\ListViewExtensions.cs\" />\n    <Compile Include=\"ui\\view\\controls\\NumTextBox.xaml.cs\">\n      <DependentUpon>NumTextBox.xaml</DependentUpon>\n    </Compile>\n    <Compile Include=\"ui\\view\\controls\\OutlinedTextBlock.cs\" />\n    <Compile Include=\"ui\\view\\PageCards.xaml.cs\">\n      <DependentUpon>PageCards.xaml</DependentUpon>\n    </Compile>\n    <Compile Include=\"ui\\view\\PageInfo.xaml.cs\">\n      <DependentUpon>PageInfo.xaml</DependentUpon>\n    </Compile>\n    <Compile Include=\"ui\\view\\PageNpcs.xaml.cs\">\n      <DependentUpon>PageNpcs.xaml</DependentUpon>\n    </Compile>\n    <Compile Include=\"ui\\view\\PageScreenshot.xaml.cs\">\n      <DependentUpon>PageScreenshot.xaml</DependentUpon>\n    </Compile>\n    <Compile Include=\"ui\\view\\controls-triad\\PlayerDeckPreview.xaml.cs\">\n      <DependentUpon>PlayerDeckPreview.xaml</DependentUpon>\n    </Compile>\n    <Compile Include=\"ui\\view\\PageSimulate.xaml.cs\">\n      <DependentUpon>PageSimulate.xaml</DependentUpon>\n    </Compile>\n    <Compile Include=\"ui\\view\\utils\\OverlayWindowService.cs\" />\n    <Compile Include=\"ui\\view\\utils\\ViewUtils.cs\" />\n    <Compile Include=\"utils-shared\\LocResourceManager.cs\" />\n    <Compile Include=\"vision\\MLClassifierTriadDigit.cs\" />\n    <Compile Include=\"vision\\MLClassifierCactpot.cs\" />\n    <Compile Include=\"utils\\datamine\\CsvData.cs\" />\n    <Compile Include=\"utils\\datamine\\GameData.cs\" />\n    <Compile Include=\"utils\\tlsh\\BucketSize.cs\" />\n    <Compile Include=\"utils\\tlsh\\ChecksumSize.cs\" />\n    <Compile Include=\"utils\\tlsh\\TlshBuilder.cs\" />\n    <Compile Include=\"utils\\tlsh\\TlshHash.cs\" />\n    <Compile Include=\"utils\\tlsh\\TlshUtilities.cs\" />\n    <Compile Include=\"Properties\\AssemblyInfo.cs\" />\n    <Compile Include=\"utils-shared\\AssetManager.cs\" />\n    <Compile Include=\"utils-shared\\GithubUpdater.cs\" />\n    <Compile Include=\"utils-shared\\JsonParser.cs\" />\n    <Compile Include=\"utils-shared\\Logger.cs\" />\n    <Compile Include=\"utils-shared\\MLDataExporter.cs\" />\n    <Compile Include=\"utils-shared\\MLUtils.cs\" />\n    <Compile Include=\"utils\\DataCoverter.cs\" />\n    <Compile Include=\"utils\\TestManager.cs\" />\n    <Compile Include=\"utils\\XInputStub.cs\" />\n    <Compile Include=\"vision\\ScannerBase.cs\" />\n    <Compile Include=\"vision\\ScannerTriad.cs\" />\n    <Compile Include=\"vision\\ScreenAnalyzer.cs\" />\n    <Compile Include=\"vision\\ScannerCactpot.cs\" />\n    <Compile Include=\"vision\\ScreenReader.cs\" />\n    <Compile Include=\"vision\\ImageUtils.cs\" />\n    <Compile Include=\"ui\\modelproxy\\BulkObservableCollection.cs\" />\n    <Compile Include=\"ui\\modelproxy\\CardModelProxy.cs\" />\n    <Compile Include=\"ui\\modelproxy\\IconDB.cs\" />\n    <Compile Include=\"ui\\modelproxy\\ModelProxyDB.cs\" />\n    <Compile Include=\"ui\\viewmodel\\CardCollectionViewModel.cs\" />\n    <Compile Include=\"ui\\view\\utils\\CardDragDropExtension.cs\" />\n    <Compile Include=\"ui\\viewmodel\\ViewModelUtils.cs\" />\n    <Compile Include=\"ui\\viewmodel\\CardViewModel.cs\" />\n    <Compile Include=\"ui\\view\\controls-triad\\CardGridView.xaml.cs\">\n      <DependentUpon>CardGridView.xaml</DependentUpon>\n    </Compile>\n    <Compile Include=\"ui\\view\\controls-triad\\CardView.xaml.cs\">\n      <DependentUpon>CardView.xaml</DependentUpon>\n    </Compile>\n    <Compile Include=\"ui\\view\\controls-triad\\DeckView.xaml.cs\">\n      <DependentUpon>DeckView.xaml</DependentUpon>\n    </Compile>\n    <Compile Include=\"ui\\viewmodel\\DeckViewModel.cs\" />\n    <Compile Include=\"ui\\view\\PageSetup.xaml.cs\">\n      <DependentUpon>PageSetup.xaml</DependentUpon>\n    </Compile>\n    <Compile Include=\"ui\\view\\controls\\SearchableComboBox.xaml.cs\">\n      <DependentUpon>SearchableComboBox.xaml</DependentUpon>\n    </Compile>\n    <Page Include=\"ui\\view\\AdjustCardDialog.xaml\">\n      <SubType>Designer</SubType>\n      <Generator>MSBuild:Compile</Generator>\n    </Page>\n    <Page Include=\"ui\\view\\AdjustHashDialog.xaml\">\n      <SubType>Designer</SubType>\n      <Generator>MSBuild:Compile</Generator>\n    </Page>\n    <Page Include=\"ui\\view\\controls-triad\\CardGridView.xaml\">\n      <SubType>Designer</SubType>\n      <Generator>MSBuild:Compile</Generator>\n    </Page>\n    <Page Include=\"ui\\view\\controls-triad\\CardView.xaml\">\n      <SubType>Designer</SubType>\n      <Generator>MSBuild:Compile</Generator>\n    </Page>\n    <Page Include=\"ui\\view\\controls-triad\\DeckView.xaml\">\n      <SubType>Designer</SubType>\n      <Generator>MSBuild:Compile</Generator>\n    </Page>\n    <Page Include=\"ui\\view\\DialogWindow.xaml\">\n      <SubType>Designer</SubType>\n      <Generator>MSBuild:Compile</Generator>\n    </Page>\n    <Page Include=\"ui\\view\\FavDeckEditDialog.xaml\">\n      <SubType>Designer</SubType>\n      <Generator>MSBuild:Compile</Generator>\n    </Page>\n    <Page Include=\"ui\\view\\FavDeckPreview.xaml\">\n      <SubType>Designer</SubType>\n      <Generator>MSBuild:Compile</Generator>\n    </Page>\n    <Page Include=\"ui\\view\\MainWindow.xaml\">\n      <Generator>MSBuild:Compile</Generator>\n      <SubType>Designer</SubType>\n    </Page>\n    <Compile Include=\"ui\\App.xaml.cs\">\n      <DependentUpon>App.xaml</DependentUpon>\n      <SubType>Code</SubType>\n    </Compile>\n    <Compile Include=\"ui\\view\\MainWindow.xaml.cs\">\n      <DependentUpon>MainWindow.xaml</DependentUpon>\n      <SubType>Code</SubType>\n    </Compile>\n    <Page Include=\"ui\\view\\controls\\NumTextBox.xaml\">\n      <SubType>Designer</SubType>\n      <Generator>MSBuild:Compile</Generator>\n    </Page>\n    <Page Include=\"ui\\view\\OverlayWindowTransparent.xaml\">\n      <Generator>MSBuild:Compile</Generator>\n      <SubType>Designer</SubType>\n    </Page>\n    <Page Include=\"ui\\view\\PageCards.xaml\">\n      <Generator>MSBuild:Compile</Generator>\n      <SubType>Designer</SubType>\n    </Page>\n    <Page Include=\"ui\\view\\PageInfo.xaml\">\n      <Generator>MSBuild:Compile</Generator>\n      <SubType>Designer</SubType>\n    </Page>\n    <Page Include=\"ui\\view\\PageNpcs.xaml\">\n      <Generator>MSBuild:Compile</Generator>\n      <SubType>Designer</SubType>\n    </Page>\n    <Page Include=\"ui\\view\\PageScreenshot.xaml\">\n      <SubType>Designer</SubType>\n      <Generator>MSBuild:Compile</Generator>\n    </Page>\n    <Page Include=\"ui\\view\\PageSetup.xaml\">\n      <SubType>Designer</SubType>\n      <Generator>MSBuild:Compile</Generator>\n    </Page>\n    <Page Include=\"ui\\view\\controls-triad\\PlayerDeckPreview.xaml\">\n      <Generator>MSBuild:Compile</Generator>\n      <SubType>Designer</SubType>\n    </Page>\n    <Page Include=\"ui\\view\\controls\\SearchableComboBox.xaml\">\n      <SubType>Designer</SubType>\n      <Generator>MSBuild:Compile</Generator>\n    </Page>\n    <None Include=\"loc\\strings.resx\" />\n    <None Include=\"loc\\strings.de.resx\" />\n    <None Include=\"loc\\strings.fr.resx\" />\n    <None Include=\"loc\\strings.ja.resx\" />\n    <None Include=\"loc\\strings.ko.resx\" />\n    <None Include=\"loc\\strings.zh.resx\" />\n    <None Include=\"loc\\strings.es.resx\" />\n    <EmbeddedResource Include=\"Properties\\Resources.resx\">\n      <Generator>ResXFileCodeGenerator</Generator>\n      <SubType>Designer</SubType>\n    </EmbeddedResource>\n    <None Include=\"Properties\\Settings.settings\">\n      <Generator>SettingsSingleFileGenerator</Generator>\n      <LastGenOutput>Settings.Designer.cs</LastGenOutput>\n    </None>\n    <Compile Include=\"Properties\\Settings.Designer.cs\">\n      <AutoGen>True</AutoGen>\n      <DependentUpon>Settings.settings</DependentUpon>\n      <DesignTimeSharedInput>True</DesignTimeSharedInput>\n    </Compile>\n    <Page Include=\"ui\\view\\PageSimulate.xaml\">\n      <SubType>Designer</SubType>\n      <Generator>MSBuild:Compile</Generator>\n    </Page>\n    <Page Include=\"ui\\view\\OverlayWindowInteractive.xaml\">\n      <SubType>Designer</SubType>\n      <Generator>MSBuild:Compile</Generator>\n    </Page>\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"App.config\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Content Include=\"card.ico\" />\n  </ItemGroup>\n  <ItemGroup Condition=\"Exists('googleapi\\GoogleClientIdentifiers.cs')\">\n    <Compile Include=\"googleapi\\GoogleClientIdentifiers.cs\" />\n    <Compile Remove=\"googleapi\\GoogleClientMissingIdentifiers.cs\" />\n  </ItemGroup>\n  <ItemGroup Condition=\"!Exists('googleapi\\GoogleClientIdentifiers.cs')\">\n    <Compile Include=\"googleapi\\GoogleClientMissingIdentifiers.cs\" />\n    <Compile Remove=\"googleapi\\GoogleClientIdentifiers.cs\" />\n  </ItemGroup>\n  <ItemGroup>\n    <BootstrapperPackage Include=\".NETFramework,Version=v4.6.1\">\n      <Visible>False</Visible>\n      <ProductName>Microsoft .NET Framework 4.6.1 %28x86 and x64%29</ProductName>\n      <Install>true</Install>\n    </BootstrapperPackage>\n    <BootstrapperPackage Include=\"Microsoft.Net.Framework.3.5.SP1\">\n      <Visible>False</Visible>\n      <ProductName>.NET Framework 3.5 SP1</ProductName>\n      <Install>false</Install>\n    </BootstrapperPackage>\n  </ItemGroup>\n  <ItemGroup />\n  <Import Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" />\n  <Target Name=\"BeforeBuild\">\n    <CreateItem Include=\"$(ProjectDir)loc\\strings*.resx\">\n      <Output TaskParameter=\"Include\" ItemName=\"LocFiles\" />\n    </CreateItem>\n    <Delete Files=\"$(ProjectDir)\\assets.zip\" />\n    <MakeDir Directories=\"$(SolutionDir)\\assets\\loc\" />\n    <Copy SourceFiles=\"@(LocFiles)\" DestinationFolder=\"$(SolutionDir)\\assets\\loc\" />\n    <ZipDir ZipFileName=\"$(ProjectDir)\\assets.zip\" DirectoryName=\"$(SolutionDir)\\assets\" />\n    <RemoveDir Directories=\"$(SolutionDir)\\assets\\loc\" />\n  </Target>\n  <Target Name=\"AfterBuild\" Condition=\" '$(Configuration)' == 'Release' \">\n    <GetAssemblyIdentity AssemblyFiles=\"$(TargetPath)\">\n      <Output TaskParameter=\"Assemblies\" ItemName=\"myAssemblyInfo\" />\n    </GetAssemblyIdentity>\n    <PropertyGroup>\n      <Pattern>(\\d+)</Pattern>\n      <In>%(myAssemblyInfo.Version)</In>\n      <MajorVersion>$([System.Text.RegularExpressions.Regex]::Match($(In), $(Pattern)))</MajorVersion>\n      <ReleaseZipPath>$(SolutionDir)\\releases\\release-v$(MajorVersion).zip</ReleaseZipPath>\n      <ReleaseZipTemp>$(SolutionDir)\\releases\\temp</ReleaseZipTemp>\n    </PropertyGroup>\n    <MakeDir Directories=\"$(ReleaseZipTemp)\" />\n    <Copy SourceFiles=\"$(TargetPath)\" DestinationFolder=\"$(ReleaseZipTemp)\" />\n    <Delete Files=\"$(ReleaseZipPath)\" />\n    <ZipDir ZipFileName=\"$(ReleaseZipPath)\" DirectoryName=\"$(ReleaseZipTemp)\" />\n    <RemoveDir Directories=\"$(ReleaseZipTemp)\" />\n    <Message Text=\"Created $(ReleaseZipPath)\" Importance=\"high\" />\n  </Target>\n  <UsingTask TaskName=\"ZipDir\" TaskFactory=\"CodeTaskFactory\" AssemblyFile=\"$(MSBuildToolsPath)\\Microsoft.Build.Tasks.v12.0.dll\">\n    <ParameterGroup>\n      <ZipFileName ParameterType=\"System.String\" Required=\"true\" />\n      <DirectoryName ParameterType=\"System.String\" Required=\"true\" />\n    </ParameterGroup>\n    <Task>\n      <Reference Include=\"System.IO.Compression.FileSystem\" />\n      <Using Namespace=\"System.IO.Compression\" />\n      <Code Type=\"Fragment\" Language=\"cs\"><![CDATA[\n      try\n      {\n        Log.LogMessage(string.Format(\"Zipping Directory {0} to {1}\", DirectoryName, ZipFileName));\n        ZipFile.CreateFromDirectory( DirectoryName, ZipFileName );\n        return true;\n      }\n      catch(Exception ex)\n      {\n        Log.LogErrorFromException(ex);\n        return false;\n      }\n    ]]></Code>\n    </Task>\n  </UsingTask>\n</Project>"
  },
  {
    "path": "sources/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Reflection;\nusing System.Runtime.InteropServices;\n\n// General Information about an assembly is controlled through the following\n// set of attributes. Change these attribute values to modify the information\n// associated with an assembly.\n[assembly: AssemblyTitle(\"FFTriadBuddy\")]\n[assembly: AssemblyDescription(\"\")]\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"\")]\n[assembly: AssemblyProduct(\"FFTriadBuddy\")]\n[assembly: AssemblyCopyright(\"Copyright ©  2025\")]\n[assembly: AssemblyTrademark(\"\")]\n[assembly: AssemblyCulture(\"\")]\n\n// Setting ComVisible to false makes the types in this assembly not visible\n// to COM components.  If you need to access a type in this assembly from\n// COM, set the ComVisible attribute to true on that type.\n[assembly: ComVisible(false)]\n\n// The following GUID is for the ID of the typelib if this project is exposed to COM\n[assembly: Guid(\"4fe48c06-9e9c-46c9-a53e-cfe06efdcbdc\")]\n\n// Version information for an assembly consists of the following four values:\n//\n//      Major Version\n//      Minor Version\n//      Build Number\n//      Revision\n//\n// You can specify all the values or you can default the Build and Revision Numbers\n// by using the '*' as shown below:\n// [assembly: AssemblyVersion(\"1.0.*\")]\n[assembly: AssemblyVersion(\"121.0.0.0\")]\n[assembly: AssemblyFileVersion(\"121.0.0.0\")]\n"
  },
  {
    "path": "sources/Properties/Resources.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <assembly alias=\"System.Windows.Forms\" name=\"System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" />\n  <data name=\"assets\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\assets.zip;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </data>\n</root>"
  },
  {
    "path": "sources/Properties/Settings.Designer.cs",
    "content": "﻿//------------------------------------------------------------------------------\n// <auto-generated>\n//     This code was generated by a tool.\n//     Runtime Version:4.0.30319.42000\n//\n//     Changes to this file may cause incorrect behavior and will be lost if\n//     the code is regenerated.\n// </auto-generated>\n//------------------------------------------------------------------------------\n\nnamespace FFTriadBuddy.Properties {\n    \n    \n    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]\n    [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator\", \"16.10.0.0\")]\n    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {\n        \n        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));\n        \n        public static Settings Default {\n            get {\n                return defaultInstance;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/Properties/Settings.settings",
    "content": "﻿<?xml version='1.0' encoding='utf-8'?>\n<SettingsFile xmlns=\"http://schemas.microsoft.com/VisualStudio/2004/01/settings\" CurrentProfile=\"(Default)\">\n  <Profiles>\n    <Profile Name=\"(Default)\" />\n  </Profiles>\n  <Settings />\n</SettingsFile>\n"
  },
  {
    "path": "sources/data/ImageHashDB.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing Palit.TLSHSharp;\nusing System;\nusing System.Collections.Generic;\nusing System.Drawing;\nusing System.Reflection;\nusing System.Security.Cryptography;\nusing System.Xml;\n\nnamespace FFTriadBuddy\n{\n    public enum EImageHashType\n    {\n        CardNumber,\n        CardImage,\n        Rule,\n        Cactpot,\n    }\n\n    public class ImageHashData : IComparable\n    {\n        public byte[] hashMD5;\n        public TlshHash hashTLSH;\n        public EImageHashType type;\n\n        public object ownerOb;\n\n        public bool isAuto;\n        public bool isKnown;\n\n        public int matchDistance;\n        public Image previewImage;\n        public Bitmap sourceImage;\n        public Rectangle previewBounds;\n        public Rectangle previewContextBounds;\n\n        public void CalculateHash(byte[] data)\n        {\n            TlshBuilder hashBuilder = new TlshBuilder();\n            hashBuilder.Update(data);\n            hashTLSH = hashBuilder.IsValid(false) ? hashBuilder.GetHash(false) : null;\n\n            using (MD5 md5Builder = MD5.Create())\n            {\n                hashMD5 = md5Builder.ComputeHash(data);\n            }\n        }\n\n        public void CalculateHash(float[] data)\n        {\n            byte[] byteData = new byte[data.Length * sizeof(float)];\n            Buffer.BlockCopy(data, 0, byteData, 0, byteData.Length);\n            CalculateHash(byteData);\n        }\n\n        private static int GetHexVal(char hex)\n        {\n            return (hex >= 'a' && hex <= 'f') ? (hex - 'a' + 10) :\n                (hex >= 'A' && hex <= 'F') ? (hex - 'A' + 10) :\n                hex;\n        }\n\n        public void LoadFromString(string descTLSH, string descBuffer)\n        {\n            if (!string.IsNullOrEmpty(descTLSH))\n            {\n                hashTLSH = TlshHash.FromTlshStr(descTLSH);\n            }\n\n            if (!string.IsNullOrEmpty(descBuffer))\n            {\n                hashMD5 = new byte[descBuffer.Length / 2];\n                for (int idx = 0; idx < descBuffer.Length; idx++)\n                {\n                    hashMD5[idx / 2] = (byte)((GetHexVal(descBuffer[idx]) << 4) + GetHexVal(descBuffer[idx]));\n                }\n            }\n        }\n\n        public bool IsMatching(ImageHashData other, int maxDistance, out int matchDistance)\n        {\n            matchDistance = GetHashDistance(other);\n            return matchDistance <= maxDistance;\n        }\n\n        public int GetHashDistance(ImageHashData other)\n        {\n            if (hashMD5 != null && other.hashMD5 != null && hashMD5.Length == other.hashMD5.Length)\n            {\n                bool isMatching = true;\n                for (int idx = 0; idx < hashMD5.Length; idx++)\n                {\n                    if (hashMD5[idx] != other.hashMD5[idx])\n                    {\n                        isMatching = false;\n                        break;\n                    }\n                }\n\n                if (isMatching)\n                {\n                    return 0;\n                }\n            }\n\n            if (hashTLSH != null && other.hashTLSH != null)\n            {\n                return hashTLSH.TotalDiff(other.hashTLSH, false);\n            }\n\n            return int.MaxValue;\n        }\n\n        public void UpdatePreviewImage()\n        {\n            if (previewImage == null)\n            {\n                previewImage = ImageUtils.CreatePreviewImage(sourceImage, previewBounds, previewContextBounds);\n            }\n        }\n\n        public int CompareTo(object obj)\n        {\n            ImageHashData otherOb = (ImageHashData)obj;\n            if (otherOb == null) { return 1; }\n            if (type != otherOb.type) { return type.CompareTo(otherOb.type); }\n\n            return ownerOb.ToString().CompareTo(otherOb.ownerOb.ToString());\n        }\n\n        public bool IsValid()\n        {\n            return (hashMD5 != null) || (hashTLSH != null);\n        }\n\n        public override string ToString()\n        {\n            return type + \": \" + ownerOb;\n        }\n    }\n\n    public class ImageHashDB\n    {\n        public List<ImageHashData> hashes;\n        public string DBPath;\n        private static ImageHashDB instance = new ImageHashDB();\n\n        public List<TriadGameModifier> modObjects;\n\n        public ImageHashDB()\n        {\n            DBPath = \"data/hashes.xml\";\n            hashes = new List<ImageHashData>();\n            modObjects = new List<TriadGameModifier>();\n        }\n\n        public static ImageHashDB Get()\n        {\n            return instance;\n        }\n\n        public bool Load()\n        {\n            hashes.Clear();\n\n            modObjects.Clear();\n            foreach (Type type in Assembly.GetAssembly(typeof(TriadGameModifier)).GetTypes())\n            {\n                if (type.IsSubclassOf(typeof(TriadGameModifier)))\n                {\n                    modObjects.Add((TriadGameModifier)Activator.CreateInstance(type));\n                }\n            }\n\n            try\n            {\n                XmlDocument xdoc = new XmlDocument();\n                xdoc.Load(AssetManager.Get().GetAsset(DBPath));\n\n                foreach (XmlNode testNode in xdoc.DocumentElement.ChildNodes)\n                {\n                    XmlElement testElem = (XmlElement)testNode;\n                    ImageHashData hashEntry = LoadHashEntry(testElem);\n                    if (hashEntry != null && hashEntry.IsValid())\n                    {\n                        hashes.Add(hashEntry);\n                    }\n                }\n            }\n            catch (Exception ex)\n            {\n                Logger.WriteLine(\"Loading failed! Exception:\" + ex);\n            }\n\n            Logger.WriteLine(\"Loaded hashes: \" + hashes.Count);\n            return true;\n        }\n\n        public ImageHashData LoadHashEntry(XmlElement xmlElem)\n        {\n            ImageHashData result = null;\n            if (xmlElem != null && xmlElem.Name == \"hash\" && xmlElem.HasAttribute(\"type\") && (xmlElem.HasAttribute(\"value\") || xmlElem.HasAttribute(\"valueB\")))\n            {\n                string typeName = xmlElem.GetAttribute(\"type\");\n\n                string hashValueC = xmlElem.HasAttribute(\"value\") ? xmlElem.GetAttribute(\"value\") : null;\n                string hashValueB = xmlElem.HasAttribute(\"valueB\") ? xmlElem.GetAttribute(\"valueB\") : null;\n\n                if (typeName.Equals(\"rule\", StringComparison.InvariantCultureIgnoreCase))\n                {\n                    string ruleName = xmlElem.GetAttribute(\"name\");\n\n                    result = new ImageHashData() { type = EImageHashType.Rule, isKnown = true };\n                    result.ownerOb = ParseRule(ruleName);\n                    result.LoadFromString(hashValueC, hashValueB);\n                }\n                else if (typeName.Equals(\"card\", StringComparison.InvariantCultureIgnoreCase))\n                {\n                    string cardIdName = xmlElem.GetAttribute(\"id\");\n                    int cardId = int.Parse(cardIdName);\n\n                    result = new ImageHashData() { type = EImageHashType.CardImage, isKnown = true };\n                    result.ownerOb = TriadCardDB.Get().cards[cardId];\n                    result.LoadFromString(hashValueC, hashValueB);\n                }\n                else if (typeName.Equals(\"cactpot\", StringComparison.InvariantCultureIgnoreCase))\n                {\n                    string numIdName = xmlElem.GetAttribute(\"id\");\n\n                    result = new ImageHashData() { type = EImageHashType.Cactpot, isKnown = true };\n                    result.ownerOb = int.Parse(numIdName);\n                    result.LoadFromString(hashValueC, hashValueB);\n                }\n            }\n\n            return result;\n        }\n\n        public List<ImageHashData> LoadImageHashes(JsonParser.ObjectValue jsonOb)\n        {\n            List<ImageHashData> list = new List<ImageHashData>();\n\n            string[] enumArr = Enum.GetNames(typeof(EImageHashType));\n            foreach (var kvp in jsonOb.entries)\n            {\n                EImageHashType groupType = (EImageHashType)Array.IndexOf(enumArr, kvp.Key);\n                JsonParser.ArrayValue typeArr = (JsonParser.ArrayValue)kvp.Value;\n\n                foreach (JsonParser.Value value in typeArr.entries)\n                {\n                    JsonParser.ObjectValue jsonHashOb = (JsonParser.ObjectValue)value;\n                    string idStr = jsonHashOb[\"id\"];\n\n                    bool hasIdNum = int.TryParse(idStr, out int idNum);\n                    bool needsIdNum = (groupType != EImageHashType.Rule);\n                    if (hasIdNum != needsIdNum)\n                    {\n                        continue;\n                    }\n\n                    ImageHashData hashEntry = new ImageHashData() { type = groupType, isKnown = true };\n                    switch (groupType)\n                    {\n                        case EImageHashType.Rule:\n                            hashEntry.ownerOb = ParseRule(idStr);\n                            break;\n\n                        case EImageHashType.CardImage:\n                            hashEntry.ownerOb = TriadCardDB.Get().cards[idNum];\n                            break;\n\n                        default:\n                            hashEntry.ownerOb = idNum;\n                            break;\n                    }\n\n                    if (hashEntry.ownerOb != null)\n                    {\n                        string descHashTLSH = jsonHashOb[\"hashC\", JsonParser.StringValue.Empty];\n                        string descHashMd5 = jsonHashOb[\"hashB\", JsonParser.StringValue.Empty];\n\n                        hashEntry.LoadFromString(descHashTLSH, descHashMd5);\n                        if (hashEntry.IsValid())\n                        {\n                            list.Add(hashEntry);\n                        }\n                    }\n                }\n            }\n\n            return list;\n        }\n\n        public void StoreHashes(List<ImageHashData> entries, JsonWriter jsonWriter)\n        {\n            foreach (EImageHashType subType in Enum.GetValues(typeof(EImageHashType)))\n            {\n                List<ImageHashData> sortedSubtypeList = entries.FindAll(x => x.type == subType);\n                sortedSubtypeList.Sort();\n\n                jsonWriter.WriteArrayStart(subType.ToString());\n                foreach (ImageHashData entry in sortedSubtypeList)\n                {\n                    jsonWriter.WriteObjectStart();\n                    switch (subType)\n                    {\n                        case EImageHashType.CardImage: jsonWriter.WriteString(((TriadCard)entry.ownerOb).Id.ToString(), \"id\"); break;\n                        default: jsonWriter.WriteString(entry.ownerOb.ToString(), \"id\"); break;\n                    }\n\n                    if (entry.hashTLSH != null)\n                    {\n                        jsonWriter.WriteString(entry.hashTLSH.ToString(), \"hashC\");\n                    }\n                    else\n                    {\n                        string hexStr = BitConverter.ToString(entry.hashMD5).ToLower();\n                        jsonWriter.WriteString(hexStr, \"hashB\");\n                    }\n\n                    jsonWriter.WriteObjectEnd();\n                }\n                jsonWriter.WriteArrayEnd();\n            }\n        }\n\n        private TriadGameModifier ParseRule(string ruleName)\n        {\n            TriadGameModifier result = null;\n            foreach (TriadGameModifier mod in modObjects)\n            {\n                if (ruleName.Equals(mod.GetCodeName(), StringComparison.InvariantCultureIgnoreCase))\n                {\n                    result = (TriadGameModifier)Activator.CreateInstance(mod.GetType());\n                    break;\n                }\n            }\n\n            if (result == null)\n            {\n                Logger.WriteLine(\"Loading failed! Can't parse rule: \" + ruleName);\n            }\n\n            return result;\n        }\n\n        public ImageHashData FindExactMatch(ImageHashData hashData)\n        {\n            return FindBestMatch(hashData, 0, out int dummyV);\n        }\n\n        public ImageHashData FindBestMatch(ImageHashData hashData, int maxDistance, out int matchDistance)\n        {\n            int bestDistance = 0;\n            int bestIdx = -1;\n\n            for (int idx = 0; idx < hashes.Count; idx++)\n            {\n                if (hashes[idx].type == hashData.type)\n                {\n                    int distance = hashes[idx].GetHashDistance(hashData);\n                    if (distance <= maxDistance)\n                    {\n                        if (bestIdx < 0 || bestDistance > distance)\n                        {\n                            bestIdx = idx;\n                            bestDistance = distance;\n                        }\n                    }\n                }\n            }\n\n            matchDistance = bestDistance;\n            return (bestIdx < 0) ? null : hashes[bestIdx];\n        }\n    }\n}\n"
  },
  {
    "path": "sources/data/LocalizationDB.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\nusing System.Globalization;\nusing System.Net;\nusing System.Xml;\n\nnamespace FFTriadBuddy\n{\n    public enum ELocStringType\n    {\n        Unknown,\n        RuleName,\n        CardType,\n        CardName,\n        NpcName,\n        NpcLocation,\n        TournamentName,\n    }\n\n    public class LocString\n    {\n        public string[] Text = new string[LocalizationDB.Languages.Length];\n        public ELocStringType Type;\n        public int Id;\n\n        public LocString()\n        {\n            Type = ELocStringType.Unknown;\n            Id = 0;\n        }\n\n        public LocString(ELocStringType Type, int Id)\n        {\n            this.Type = Type;\n            this.Id = Id;\n        }\n\n        public LocString(ELocStringType Type, int Id, string DefaultText)\n        {\n            this.Type = Type;\n            this.Id = Id;\n\n            Text[LocalizationDB.CodeLanguageIdx] = DefaultText;\n        }\n\n        public override string ToString()\n        {\n            return string.Format(\"{0}:{1} '{2}'\", Type, Id, GetCodeName());\n        }\n\n        public string Get(string lang)\n        {\n            int langIdx = Array.IndexOf(LocalizationDB.Languages, lang);\n            return Get(langIdx);\n        }\n\n        public string Get(int langIdx)\n        {\n            if (Text != null)\n            {\n                string resultStr = (langIdx < 0) ? null : Text[langIdx];\n                if (string.IsNullOrEmpty(resultStr))\n                {\n                    // fallback language if game data is not available?\n                    resultStr = Text[LocalizationDB.CodeLanguageIdx];\n                }\n\n                if (resultStr != null)\n                {\n                    return resultStr;\n                }\n            }\n\n            return string.Format(\"--LOC:{0}:{1}--\", Type, Id);\n        }\n\n        public string GetLocalized()\n        {\n            return Get(LocalizationDB.UserLanguageIdx);\n        }\n\n        public string GetCodeName()\n        {\n            return Get(LocalizationDB.CodeLanguageIdx);\n        }\n    }\n\n    public class LocalizationDB\n    {\n        public readonly static string[] Languages = { \"de\", \"en\", \"fr\", \"ja\", \"cn\", \"ko\" };\n        public readonly static string CodeLanguage = \"en\";\n        public readonly static int CodeLanguageIdx = Array.IndexOf(Languages, CodeLanguage);\n        public static int UserLanguageIdx = -1;\n        public string DBPath;\n        private static LocalizationDB instance = new LocalizationDB();\n\n        public delegate void LangChangedDelegate();\n        public static event LangChangedDelegate OnLanguageChanged;\n\n        public List<LocString> LocUnknown;\n        public List<LocString> LocRuleNames;\n        public List<LocString> LocCardTypes;\n        public List<LocString> LocCardNames;\n        public List<LocString> LocNpcNames;\n        public List<LocString> LocNpcLocations;\n        public List<LocString> LocTournamentNames;\n\n        public Dictionary<ELocStringType, List<LocString>> mapLocStrings;\n        public Dictionary<ETriadCardType, LocString> mapCardTypes;\n\n        public LocalizationDB()\n        {\n            DBPath = \"data/loc.xml\";\n\n            LocUnknown = new List<LocString>();\n            LocRuleNames = new List<LocString>();\n            LocCardTypes = new List<LocString>();\n            LocCardNames = new List<LocString>();\n            LocNpcNames = new List<LocString>();\n            LocNpcLocations = new List<LocString>();\n            LocTournamentNames = new List<LocString>();\n\n            mapLocStrings = new Dictionary<ELocStringType, List<LocString>>();\n            mapLocStrings.Add(ELocStringType.Unknown, LocUnknown);\n            mapLocStrings.Add(ELocStringType.RuleName, LocRuleNames);\n            mapLocStrings.Add(ELocStringType.CardType, LocCardTypes);\n            mapLocStrings.Add(ELocStringType.CardName, LocCardNames);\n            mapLocStrings.Add(ELocStringType.NpcName, LocNpcNames);\n            mapLocStrings.Add(ELocStringType.NpcLocation, LocNpcLocations);\n            mapLocStrings.Add(ELocStringType.TournamentName, LocTournamentNames);\n\n            mapCardTypes = new Dictionary<ETriadCardType, LocString>();\n            string[] enumNames = Enum.GetNames(typeof(ETriadCardType));\n            for (int enumIdx = 0; enumIdx < enumNames.Length; enumIdx++)\n            {\n                var locStr = new LocString(ELocStringType.CardType, enumIdx, enumIdx == 0 ? \"\" : enumNames[enumIdx]);\n                mapCardTypes.Add((ETriadCardType)enumIdx, locStr);\n                LocCardTypes.Add(locStr);\n            }\n        }\n\n        public static LocalizationDB Get()\n        {\n            return instance;\n        }\n\n        public static void SetCurrentUserLanguage(string cultureCode)\n        {\n            int newLangIdx = CodeLanguageIdx;\n            if (cultureCode == \"de\" || cultureCode.StartsWith(\"de-\"))\n            {\n                newLangIdx = Array.IndexOf(Languages, \"de\");\n            }\n            else if (cultureCode == \"fr\" || cultureCode.StartsWith(\"fr-\"))\n            {\n                newLangIdx = Array.IndexOf(Languages, \"fr\");\n            }\n            else if (cultureCode == \"ja\" || cultureCode.StartsWith(\"ja-\"))\n            {\n                newLangIdx = Array.IndexOf(Languages, \"ja\");\n            }\n            else if (cultureCode == \"ko\" || cultureCode.StartsWith(\"ko-\"))\n            {\n                newLangIdx = Array.IndexOf(Languages, \"ko\");\n            }\n            else if (cultureCode == \"zh\" || cultureCode.StartsWith(\"zh-\"))\n            {\n                newLangIdx = Array.IndexOf(Languages, \"cn\");\n            }\n            else\n            {\n                newLangIdx = CodeLanguageIdx;\n            }\n\n            bool changed = false;\n            if (newLangIdx != UserLanguageIdx)\n            {\n                UserLanguageIdx = newLangIdx;\n                changed = true;\n            }\n\n            var locManager = LocResourceManager.Get();\n            if (locManager.UserCultureCode != cultureCode)\n            {\n                CultureInfo.CurrentUICulture = new CultureInfo(cultureCode);\n                locManager.SetCurrentUserLanguage(CultureInfo.CurrentUICulture, typeof(loc.strings));\n                changed = true;\n            }\n\n            if (changed)\n            {\n                OnLanguageChanged?.Invoke();\n                Logger.WriteLine(\"Init localization: culture:{0} -> gameData:{1}, UI:{2}\", cultureCode, Languages[UserLanguageIdx], locManager.UserCultureCode);\n            }\n        }\n\n        public LocString FindOrAddLocString(ELocStringType Type, int Id)\n        {\n            var list = mapLocStrings[Type];\n\n            // so far those ids are continuous from 0, switch to dictionaries when it changes\n            while (list.Count <= Id)\n            {\n                list.Add(new LocString(Type, list.Count));\n            }\n\n            return list[Id];\n        }\n\n        public bool Load()\n        {\n            int numLoaded = 0;\n\n            try\n            {\n                XmlDocument xdoc = new XmlDocument();\n                xdoc.Load(AssetManager.Get().GetAsset(DBPath));\n\n                foreach (XmlNode locNode in xdoc.DocumentElement.ChildNodes)\n                {\n                    XmlElement locElem = (XmlElement)locNode;\n                    if (locElem != null && locElem.Name == \"loc\")\n                    {\n                        try\n                        {\n                            int locType = int.Parse(locElem.GetAttribute(\"type\"));\n                            int locId = int.Parse(locElem.GetAttribute(\"id\"));\n\n                            LocString locStr = FindOrAddLocString((ELocStringType)locType, locId);\n                            for (int idx = 0; idx < locStr.Text.Length; idx++)\n                            {\n                                if (locElem.HasAttribute(Languages[idx]))\n                                {\n                                    locStr.Text[idx] = WebUtility.HtmlDecode(locElem.GetAttribute(Languages[idx]));\n                                }\n                            }\n\n                            FixLocalizedNameCasing(locStr);\n                            numLoaded++;\n                        }\n                        catch (Exception ex)\n                        {\n                            Logger.WriteLine(\"Loading failed! Exception:\" + ex);\n                        }\n                    }\n                }\n            }\n            catch (Exception ex)\n            {\n                Logger.WriteLine(\"Loading failed! Exception:\" + ex);\n            }\n\n            Logger.WriteLine(\"Loaded localized strings: \" + numLoaded);\n            return numLoaded > 0;\n        }\n\n        public void Save()\n        {\n            string RawFilePath = AssetManager.Get().CreateFilePath(\"assets/\" + DBPath);\n            try\n            {\n                XmlWriterSettings writerSettings = new XmlWriterSettings();\n                writerSettings.Indent = true;\n\n                XmlWriter xmlWriter = XmlWriter.Create(RawFilePath, writerSettings);\n                xmlWriter.WriteStartDocument();\n                xmlWriter.WriteStartElement(\"root\");\n\n                foreach (var kvp in mapLocStrings)\n                {\n                    foreach (var locStr in kvp.Value)\n                    {\n                        bool isEmpty = true;\n                        for (int idx = 0; idx < locStr.Text.Length; idx++)\n                        {\n                            if (locStr.Text[idx] != null)\n                            {\n                                // empty str is valid value, ignore only nulls\n                                isEmpty = false;\n                                break;\n                            }\n                        }\n\n                        if (!isEmpty)\n                        {\n                            xmlWriter.WriteStartElement(\"loc\");\n                            xmlWriter.WriteAttributeString(\"type\", ((int)locStr.Type).ToString());\n                            xmlWriter.WriteAttributeString(\"id\", locStr.Id.ToString());\n\n                            for (int idx = 0; idx < locStr.Text.Length; idx++)\n                            {\n                                if (locStr.Text[idx] != null)\n                                {\n                                    xmlWriter.WriteAttributeString(Languages[idx], locStr.Text[idx]);\n                                }\n                            }\n\n                            xmlWriter.WriteEndElement();\n                        }\n                    }\n                }\n\n                xmlWriter.WriteEndDocument();\n                xmlWriter.Close();\n            }\n            catch (Exception ex)\n            {\n                Logger.WriteLine(\"Saving failed! Exception:\" + ex);\n            }\n        }\n\n        private static string[] caseFixExclusions = new string[] { \"the\", \"goe\", \"van\", \"des\", \"sas\", \"yae\", \"tol\", \"der\", \"rem\" };\n        private void FixLocalizedNameCasing(LocString entry)\n        {\n            bool canFixCase = (entry.Type == ELocStringType.CardName) || (entry.Type == ELocStringType.NpcName);\n            if (!canFixCase)\n            {\n                return;\n            }\n\n            for (int idxLang = 0; idxLang < entry.Text.Length; idxLang++)\n            {\n                if (string.IsNullOrEmpty(entry.Text[idxLang]))\n                {\n                    continue;\n                }\n\n                string[] tokens = entry.Text[idxLang].Split(' ');\n                int numChangedTokens = 0;\n\n                for (int idx = 0; idx < tokens.Length; idx++)\n                {\n                    if (tokens[idx].Length > 2 && Array.IndexOf(caseFixExclusions, tokens[idx]) < 0)\n                    {\n                        if (tokens[idx][1] == '\\'')\n                        {\n                            // don't touch, i have no idea how french capitalization work\n                            continue;\n                        }\n\n                        bool hasLowerCase = char.IsLower(tokens[idx], 0);\n                        if (hasLowerCase)\n                        {\n                            var newToken = tokens[idx].Substring(0, 1).ToUpper() + tokens[idx].Substring(1);\n                            tokens[idx] = newToken;\n                            numChangedTokens++;\n                        }\n                    }\n                }\n\n                if (numChangedTokens > 0)\n                {\n                    var newText = string.Join(\" \", tokens);\n                    entry.Text[idxLang] = newText;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/data/PlayerSettingsDB.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Reflection;\n\nnamespace FFTriadBuddy\n{\n    public class PlayerSettingsDB\n    {\n        public List<TriadCard> ownedCards;\n        public List<TriadNpc> completedNpcs;\n        public List<ImageHashData> customHashes;\n        public TriadCard[] starterCards;\n        public Dictionary<TriadNpc, TriadDeck> lastDeck;\n        public List<TriadDeckNamed> favDecks;\n        public bool useCloudStorage;\n        public bool useXInput;\n        public bool useSmallIcons;\n        public bool useSoftwareRendering;\n        public bool alwaysOnTop;\n        public bool skipOptionalSimulateRules;\n        public bool isDirty;\n        public string DBPath;\n        public string cloudToken;\n        public string forcedLanguage;\n\n        public int lastNpcId;\n        public float lastWidth;\n        public float lastHeight;\n\n        public float fontSize;\n        public float markerDurationCard;\n        public float markerDurationSwap;\n        public float markerDurationCactpot;\n\n        private static PlayerSettingsDB instance = new PlayerSettingsDB();\n\n        public delegate void UpdatedDelegate(bool bCards, bool bNpcs, bool bDecks);\n        public event UpdatedDelegate OnUpdated;\n\n        public PlayerSettingsDB()\n        {\n            DBPath = \"FFTriadBuddy-settings.json\";\n            ownedCards = new List<TriadCard>();\n            completedNpcs = new List<TriadNpc>();\n            lastDeck = new Dictionary<TriadNpc, TriadDeck>();\n            favDecks = new List<TriadDeckNamed>();\n            starterCards = new TriadCard[5];\n            customHashes = new List<ImageHashData>();\n            useCloudStorage = false;\n            useXInput = true;\n            useSmallIcons = false;\n            alwaysOnTop = false;\n            skipOptionalSimulateRules = false;\n            isDirty = false;\n            cloudToken = null;\n            forcedLanguage = null;\n\n            lastNpcId = -1;\n            lastWidth = 0;\n            lastHeight = 0;\n\n            fontSize = 12.0f;\n            markerDurationCard = 4.0f;\n            markerDurationSwap = 10.0f;\n            markerDurationCactpot = 1.5f;\n        }\n\n        public static PlayerSettingsDB Get()\n        {\n            return instance;\n        }\n\n        public bool Load()\n        {\n            bool bResult = false;\n\n            string FilePath = AssetManager.Get().CreateFilePath(DBPath);\n            if (File.Exists(FilePath))\n            {\n                using (StreamReader file = new StreamReader(FilePath))\n                {\n                    string fileContent = file.ReadToEnd();\n                    bResult = LoadFromJson(fileContent);\n                    file.Close();\n                }\n            }\n\n            TriadCardDB cardDB = TriadCardDB.Get();\n            starterCards[0] = cardDB.Find(\"Dodo\");\n            starterCards[1] = cardDB.Find(\"Sabotender\");\n            starterCards[2] = cardDB.Find(\"Bomb\");\n            starterCards[3] = cardDB.Find(\"Mandragora\");\n            starterCards[4] = cardDB.Find(\"Coeurl\");\n\n            foreach (TriadCard starterCard in starterCards)\n            {\n                if (!ownedCards.Contains(starterCard))\n                {\n                    ownedCards.Add(starterCard);\n                }\n            }\n\n            return bResult;\n        }\n\n        public bool LoadFromJson(string jsonStr)\n        {\n            TriadCardDB cardDB = TriadCardDB.Get();\n            TriadNpcDB npcDB = TriadNpcDB.Get();\n\n            ownedCards.Clear();\n            completedNpcs.Clear();\n            lastDeck.Clear();\n            favDecks.Clear();\n\n            try\n            {\n                JsonParser.ObjectValue jsonOb = JsonParser.ParseJson(jsonStr);\n\n                JsonParser.ObjectValue uiOb = (JsonParser.ObjectValue)jsonOb[\"ui\", null];\n                if (uiOb != null)\n                {\n                    JsonParser.Value BoolTrue = new JsonParser.BoolValue(true);\n                    JsonParser.Value BoolFalse = new JsonParser.BoolValue(false);\n\n                    useXInput = (JsonParser.BoolValue)uiOb[\"xInput\", BoolTrue];\n                    useSmallIcons = (JsonParser.BoolValue)uiOb[\"smallIcons\", BoolFalse];\n                    useSoftwareRendering = (JsonParser.BoolValue)uiOb[\"noGPU\", BoolFalse];\n                    alwaysOnTop = (JsonParser.BoolValue)uiOb[\"onTop\", BoolFalse];\n                    skipOptionalSimulateRules = (JsonParser.BoolValue)uiOb[\"skipOptRules\", BoolFalse];\n                    forcedLanguage = (JsonParser.StringValue)uiOb[\"lang\", null];\n\n                    TryGettingFloatValue(uiOb, \"fontSize\", ref fontSize);\n                    TryGettingFloatValue(uiOb, \"markerCard\", ref markerDurationCard);\n                    TryGettingFloatValue(uiOb, \"markerSwap\", ref markerDurationSwap);\n                    TryGettingFloatValue(uiOb, \"markerCactpot\", ref markerDurationCactpot);\n\n                    TryGettingIntValue(uiOb, \"lastNpcId\", ref lastNpcId);\n                    TryGettingFloatValue(uiOb, \"lastWidth\", ref lastWidth);\n                    TryGettingFloatValue(uiOb, \"lastHeight\", ref lastHeight);\n\n                    fontSize = Math.Min(Math.Max(fontSize, 10), 40);\n                }\n\n                JsonParser.ObjectValue cloudOb = (JsonParser.ObjectValue)jsonOb[\"cloud\", null];\n                if (cloudOb != null)\n                {\n                    useCloudStorage = (JsonParser.BoolValue)cloudOb[\"use\", JsonParser.BoolValue.Empty];\n                    cloudToken = (JsonParser.StringValue)cloudOb[\"token\", null];\n                }\n\n                JsonParser.ArrayValue cardsArr = (JsonParser.ArrayValue)jsonOb[\"cards\", JsonParser.ArrayValue.Empty];\n                foreach (JsonParser.Value value in cardsArr.entries)\n                {\n                    int cardId = (JsonParser.IntValue)value;\n                    ownedCards.Add(cardDB.cards[cardId]);\n                }\n\n                JsonParser.ArrayValue npcsArr = (JsonParser.ArrayValue)jsonOb[\"npcs\", JsonParser.ArrayValue.Empty];\n                foreach (JsonParser.Value value in npcsArr.entries)\n                {\n                    int npcId = (JsonParser.IntValue)value;\n                    completedNpcs.Add(npcDB.npcs[npcId]);\n                }\n\n                JsonParser.ArrayValue decksArr = (JsonParser.ArrayValue)jsonOb[\"decks\", JsonParser.ArrayValue.Empty];\n                foreach (JsonParser.Value value in decksArr.entries)\n                {\n                    JsonParser.ObjectValue deckOb = (JsonParser.ObjectValue)value;\n                    int npcId = (JsonParser.IntValue)deckOb[\"id\"];\n\n                    TriadNpc npc = TriadNpcDB.Get().npcs[npcId];\n                    if (npc != null)\n                    {\n                        TriadDeck deckCards = new TriadDeck();\n\n                        cardsArr = (JsonParser.ArrayValue)deckOb[\"cards\", JsonParser.ArrayValue.Empty];\n                        foreach (JsonParser.Value cardValue in cardsArr.entries)\n                        {\n                            int cardId = (JsonParser.IntValue)cardValue;\n                            deckCards.knownCards.Add(cardDB.cards[cardId]);\n                        }\n\n                        lastDeck.Add(npc, deckCards);\n                    }\n                }\n\n                JsonParser.ArrayValue favDecksArr = (JsonParser.ArrayValue)jsonOb[\"favDecks\", JsonParser.ArrayValue.Empty];\n                foreach (JsonParser.Value value in favDecksArr.entries)\n                {\n                    JsonParser.ObjectValue deckOb = (JsonParser.ObjectValue)value;\n                    TriadDeckNamed deckCards = new TriadDeckNamed();\n\n                    cardsArr = (JsonParser.ArrayValue)deckOb[\"cards\", JsonParser.ArrayValue.Empty];\n                    foreach (JsonParser.Value cardValue in cardsArr.entries)\n                    {\n                        int cardId = (JsonParser.IntValue)cardValue;\n                        deckCards.knownCards.Add(cardDB.cards[cardId]);\n                    }\n\n                    if (deckCards.knownCards.Count > 0)\n                    {\n                        deckCards.Name = deckOb[\"name\", JsonParser.StringValue.Empty];\n                        favDecks.Add(deckCards);\n                    }\n                }\n\n                JsonParser.ObjectValue imageHashesOb = (JsonParser.ObjectValue)jsonOb[\"images\", null];\n                if (imageHashesOb != null)\n                {\n                    customHashes = ImageHashDB.Get().LoadImageHashes(imageHashesOb);\n                    ImageHashDB.Get().hashes.AddRange(customHashes);\n                }\n            }\n            catch (Exception ex)\n            {\n                Logger.WriteLine(\"Loading failed! Exception:\" + ex);\n            }\n\n            Logger.WriteLine(\"Loaded player cards: \" + ownedCards.Count + \", npcs: \" + completedNpcs.Count + \", hashes: \" + customHashes.Count);\n            return ownedCards.Count > 0;\n        }\n\n        public void OnImport()\n        {\n            OnUpdated?.Invoke(true, true, true);\n        }\n\n        private void TryGettingIntValue(JsonParser.ObjectValue ob, string key, ref int value)\n        {\n            if (ob.entries.ContainsKey(key))\n            {\n                var jsonValue = ob[key];\n                var jsonInt = jsonValue as JsonParser.IntValue;\n\n                if (jsonInt != null)\n                {\n                    value = jsonInt.Number;\n                }\n            }\n        }\n\n        private void TryGettingFloatValue(JsonParser.ObjectValue ob, string key, ref float value)\n        {\n            if (ob.entries.ContainsKey(key))\n            {\n                var jsonValue = ob[key];\n                var jsonInt = jsonValue as JsonParser.IntValue;\n                var jsonFloat = jsonValue as JsonParser.FloatValue;\n\n                if (jsonInt != null)\n                {\n                    value = jsonInt.Number;\n                }\n                else if (jsonFloat != null)\n                {\n                    value = jsonFloat.Number;\n                }\n            }\n        }\n\n        public bool MergeWithContent(string jsonString)\n        {\n            PlayerSettingsDB mergeDB = new PlayerSettingsDB();\n            bool bLoaded = mergeDB.LoadFromJson(jsonString);\n            bool bHadUniqueSettings = false;\n\n            if (bLoaded)\n            {\n                bool bUpdatedOwnedCards = false;\n                foreach (TriadCard card in mergeDB.ownedCards)\n                {\n                    if (!ownedCards.Contains(card))\n                    {\n                        ownedCards.Add(card);\n                        bUpdatedOwnedCards = true;\n                    }\n                }\n                bHadUniqueSettings = bHadUniqueSettings || (ownedCards.Count > mergeDB.ownedCards.Count);\n\n                bool bUpdatedNpcs = false;\n                foreach (TriadNpc npc in mergeDB.completedNpcs)\n                {\n                    if (!completedNpcs.Contains(npc))\n                    {\n                        completedNpcs.Add(npc);\n                        bUpdatedNpcs = true;\n                    }\n                }\n                bHadUniqueSettings = bHadUniqueSettings || (completedNpcs.Count > mergeDB.completedNpcs.Count);\n\n                bool bUpdatedDecks = false;\n                foreach (KeyValuePair<TriadNpc, TriadDeck> kvp in mergeDB.lastDeck)\n                {\n                    if (!lastDeck.ContainsKey(kvp.Key))\n                    {\n                        lastDeck.Add(kvp.Key, kvp.Value);\n                    }\n\n                    // replace existing? skip for now...\n                }\n                bHadUniqueSettings = bHadUniqueSettings || (lastDeck.Count > mergeDB.lastDeck.Count);\n\n                OnUpdated.Invoke(bUpdatedOwnedCards, bUpdatedNpcs, bUpdatedDecks);\n            }\n\n            return bHadUniqueSettings;\n        }\n\n        public void Save()\n        {\n            string FilePath = AssetManager.Get().CreateFilePath(DBPath);\n            using (StreamWriter file = new StreamWriter(FilePath))\n            {\n                string jsonString = SaveToJson(false);\n                file.Write(jsonString);\n                file.Close();\n            }\n        }\n\n        public string SaveToString()\n        {\n            isDirty = false;\n            return SaveToJson(true);\n        }\n\n        public string SaveToJson(bool bLimitedMode = false)\n        {\n            JsonWriter jsonWriter = new JsonWriter();\n            try\n            {\n                jsonWriter.WriteObjectStart();\n\n                if (!bLimitedMode)\n                {\n                    jsonWriter.WriteObjectStart(\"ui\");\n                    jsonWriter.WriteBool(useXInput, \"xInput\");\n                    jsonWriter.WriteBool(useSmallIcons, \"smallIcons\");\n                    jsonWriter.WriteBool(useSoftwareRendering, \"noGPU\");\n                    jsonWriter.WriteBool(alwaysOnTop, \"onTop\");\n                    jsonWriter.WriteBool(skipOptionalSimulateRules, \"skipOptRules\");\n                    jsonWriter.WriteString(forcedLanguage, \"lang\");\n                    jsonWriter.WriteInt(lastNpcId, \"lastNpcId\");\n                    jsonWriter.WriteFloat(lastWidth, \"lastWidth\");\n                    jsonWriter.WriteFloat(lastHeight, \"lastHeight\");\n                    jsonWriter.WriteFloat(fontSize, \"fontSize\");\n                    jsonWriter.WriteFloat(markerDurationCard, \"markerCard\");\n                    jsonWriter.WriteFloat(markerDurationSwap, \"markerSwap\");\n                    jsonWriter.WriteFloat(markerDurationCactpot, \"markerCactpot\");\n                    jsonWriter.WriteObjectEnd();\n                }\n\n                if (!bLimitedMode)\n                {\n                    jsonWriter.WriteObjectStart(\"cloud\");\n                    jsonWriter.WriteBool(useCloudStorage, \"use\");\n                    if (cloudToken != null)\n                    {\n                        jsonWriter.WriteString(cloudToken, \"token\");\n                    }\n\n                    jsonWriter.WriteObjectEnd();\n                }\n\n                {\n                    List<int> listIds = new List<int>();\n                    foreach (TriadCard card in ownedCards)\n                    {\n                        listIds.Add(card.Id);\n                    }\n                    listIds.Sort();\n\n                    jsonWriter.WriteArrayStart(\"cards\");\n                    foreach (int id in listIds)\n                    {\n                        jsonWriter.WriteInt(id);\n                    }\n\n                    jsonWriter.WriteArrayEnd();\n                }\n\n                {\n                    List<int> listIds = new List<int>();\n                    foreach (TriadNpc npc in completedNpcs)\n                    {\n                        listIds.Add(npc.Id);\n                    }\n                    listIds.Sort();\n\n                    jsonWriter.WriteArrayStart(\"npcs\");\n                    foreach (int id in listIds)\n                    {\n                        jsonWriter.WriteInt(id);\n                    }\n\n                    jsonWriter.WriteArrayEnd();\n                }\n\n                {\n                    jsonWriter.WriteArrayStart(\"decks\");\n                    foreach (KeyValuePair<TriadNpc, TriadDeck> kvp in lastDeck)\n                    {\n                        jsonWriter.WriteObjectStart();\n                        jsonWriter.WriteInt(kvp.Key.Id, \"id\");\n                        jsonWriter.WriteArrayStart(\"cards\");\n                        for (int Idx = 0; Idx < kvp.Value.knownCards.Count; Idx++)\n                        {\n                            jsonWriter.WriteInt(kvp.Value.knownCards[Idx].Id);\n                        }\n                        jsonWriter.WriteArrayEnd();\n                        jsonWriter.WriteObjectEnd();\n                    }\n\n                    jsonWriter.WriteArrayEnd();\n                }\n\n                {\n                    jsonWriter.WriteArrayStart(\"favDecks\");\n                    foreach (TriadDeckNamed deck in favDecks)\n                    {\n                        jsonWriter.WriteObjectStart();\n                        if (deck != null)\n                        {\n                            jsonWriter.WriteString(deck.Name, \"name\");\n                            jsonWriter.WriteArrayStart(\"cards\");\n                            for (int Idx = 0; Idx < deck.knownCards.Count; Idx++)\n                            {\n                                jsonWriter.WriteInt(deck.knownCards[Idx].Id);\n                            }\n                            jsonWriter.WriteArrayEnd();\n                        }\n                        jsonWriter.WriteObjectEnd();\n                    }\n                    jsonWriter.WriteArrayEnd();\n                }\n\n                if (!bLimitedMode)\n                {\n                    jsonWriter.WriteObjectStart(\"images\");\n                    ImageHashDB.Get().StoreHashes(customHashes, jsonWriter);\n                    jsonWriter.WriteObjectEnd();\n                }\n\n                jsonWriter.WriteObjectEnd();\n            }\n            catch (Exception ex)\n            {\n                Logger.WriteLine(\"Saving failed! Exception:\" + ex);\n            }\n\n            return jsonWriter.ToString();\n        }\n\n        public string GetBackupFolderPath()\n        {\n            var assembly = Assembly.GetEntryAssembly().GetName();\n            string settingsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), assembly.Name);\n            return settingsPath;\n        }\n\n        public void SaveBackup()\n        {\n            string backupJson = SaveToJson(true);\n            if (!string.IsNullOrEmpty(backupJson))\n            {\n                var assembly = Assembly.GetEntryAssembly().GetName();\n                int currentVersion = assembly.Version.Major;\n                string settingsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), assembly.Name);\n\n                string backupPath = Path.Combine(settingsPath, \"settings-backup-v\" + currentVersion + \".json\");\n\n                try\n                {\n                    if (File.Exists(backupPath))\n                    {\n                        File.Delete(backupPath);\n                    }\n\n                    File.WriteAllText(backupPath, backupJson);\n                    Logger.WriteLine(\"Saved player settings backup\");\n                }\n                catch (Exception ex)\n                {\n                    Logger.WriteLine(\"Failed to save backup: {0}, exception:{1}\", backupPath, ex);\n                }\n            }\n        }\n\n        public void MarkDirty()\n        {\n            isDirty = true;\n        }\n\n        public void UpdatePlayerDeckForNpc(TriadNpc npc, TriadDeck deck)\n        {\n            if (npc != null && deck != null && deck.knownCards.Count == 5)\n            {\n                bool bIsStarterDeck = true;\n                for (int Idx = 0; Idx < starterCards.Length; Idx++)\n                {\n                    bIsStarterDeck = bIsStarterDeck && (starterCards[Idx] == deck.knownCards[Idx]);\n                }\n\n                if (!bIsStarterDeck)\n                {\n                    bool bChanged = true;\n                    if (lastDeck.ContainsKey(npc))\n                    {\n                        if (lastDeck[npc].Equals(deck))\n                        {\n                            bChanged = false;\n                        }\n                        else\n                        {\n                            lastDeck.Remove(npc);\n                        }\n                    }\n\n                    if (bChanged)\n                    {\n                        TriadCard[] deckCardsCopy = deck.knownCards.ToArray();\n                        lastDeck.Add(npc, new TriadDeck(deckCardsCopy));\n                        MarkDirty();\n                    }\n                }\n            }\n        }\n\n        public void UpdateFavDeck(int slot, TriadDeckNamed deck)\n        {\n            if (slot < 0 || slot > 16)\n            {\n                return;\n            }\n\n            if (deck == null)\n            {\n                if (slot < favDecks.Count)\n                {\n                    favDecks.RemoveAt(slot);\n                }\n\n                MarkDirty();\n            }\n            else\n            {\n                while (favDecks.Count <= slot)\n                {\n                    favDecks.Add(null);\n                }\n\n                bool bChanged = (deck == null) != (favDecks[slot] == null);\n                if (!bChanged && (deck != null))\n                {\n                    bChanged = !deck.Name.Equals(favDecks[slot].Name) || (deck.knownCards != favDecks[slot].knownCards);\n                }\n\n                if (bChanged)\n                {\n                    MarkDirty();\n                }\n\n                favDecks[slot] = deck;\n            }\n        }\n\n        public void AddKnownHash(ImageHashData hashData)\n        {\n            foreach (ImageHashData testHash in customHashes)\n            {\n                if (hashData.IsMatching(testHash, 0, out int dummyDistance))\n                {\n                    Logger.WriteLine(\"Adding hash ({0}: {1}) failed! Colision with already known ({2}: {3})\", hashData.type, hashData.ownerOb, testHash.type, testHash.ownerOb);\n                    return;\n                }\n            }\n\n            customHashes.Add(hashData);\n            ImageHashDB.Get().hashes.Add(hashData);\n\n            MarkDirty();\n        }\n\n        public void RemoveKnownHash(ImageHashData hashData)\n        {\n            for (int idx = customHashes.Count - 1; idx >= 0; idx--)\n            {\n                ImageHashData testHash = customHashes[idx];\n                if (hashData.IsMatching(testHash, 0, out int dummyDistance))\n                {\n                    customHashes.RemoveAt(idx);\n                    ImageHashDB.Get().hashes.Remove(testHash);\n\n                    MarkDirty();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/data/TriadCardDB.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Xml;\n\nnamespace FFTriadBuddy\n{\n    public class TriadCardDB\n    {\n        public List<TriadCard> cards;\n        public TriadCard hiddenCard;\n        public string DBPath;\n        public Dictionary<int, List<TriadCard>> sameNumberMap;\n        private static TriadCardDB instance = new TriadCardDB();\n\n        public TriadCardDB()\n        {\n            DBPath = \"data/cards.xml\";\n            cards = new List<TriadCard>();\n            hiddenCard = new TriadCard(0, ETriadCardRarity.Common, ETriadCardType.None, 0, 0, 0, 0, 0, 0);\n            hiddenCard.Name.Text[LocalizationDB.CodeLanguageIdx] = \"(hidden)\";\n\n            sameNumberMap = new Dictionary<int, List<TriadCard>>();\n        }\n\n        public static TriadCardDB Get()\n        {\n            return instance;\n        }\n\n        public bool Load()\n        {\n            try\n            {\n                XmlDocument xdoc = new XmlDocument();\n                Stream dataStream = AssetManager.Get().GetAsset(DBPath);\n                xdoc.Load(dataStream);\n\n                foreach (XmlNode cardNode in xdoc.DocumentElement.ChildNodes)\n                {\n                    XmlElement cardElem = (XmlElement)cardNode;\n                    if (cardElem != null && cardElem.Name == \"card\")\n                    {\n                        try\n                        {\n                            ETriadCardRarity cardRarity = (ETriadCardRarity)int.Parse(cardElem.GetAttribute(\"rarity\"));\n                            ETriadCardType cardType = (ETriadCardType)int.Parse(cardElem.GetAttribute(\"type\"));\n                            int sortOrder = int.Parse(cardElem.GetAttribute(\"sort\"));\n                            int cardGroup = int.Parse(cardElem.GetAttribute(\"group\"));\n\n                            TriadCard newCard = new TriadCard(\n                                int.Parse(cardElem.GetAttribute(\"id\")),\n                                cardRarity,\n                                cardType,\n                                ParseCardSideNum(cardElem.GetAttribute(\"up\")),\n                                ParseCardSideNum(cardElem.GetAttribute(\"dn\")),\n                                ParseCardSideNum(cardElem.GetAttribute(\"lt\")),\n                                ParseCardSideNum(cardElem.GetAttribute(\"rt\")),\n                                sortOrder,\n                                cardGroup);\n\n                            while (cards.Count <= newCard.Id)\n                            {\n                                cards.Add(null);\n                            }\n\n                            cards[newCard.Id] = newCard;\n                        }\n                        catch (Exception ex)\n                        {\n                            Logger.WriteLine(\"Loading failed! Exception:\" + ex);\n                        }\n                    }\n                }\n            }\n            catch (Exception ex)\n            {\n                Logger.WriteLine(\"Loading failed! Exception:\" + ex);\n            }\n\n            sameNumberMap.Clear();\n            int sameNumberId = 0;\n            for (int Idx1 = 0; Idx1 < cards.Count; Idx1++)\n            {\n                TriadCard card1 = cards[Idx1];\n                if (card1 != null && card1.SameNumberId < 0)\n                {\n                    bool bHasSameNumberCards = false;\n                    for (int Idx2 = (Idx1 + 1); Idx2 < cards.Count; Idx2++)\n                    {\n                        TriadCard card2 = cards[Idx2];\n                        if (card2 != null && card2.SameNumberId < 0)\n                        {\n                            bool bHasSameNumbers =\n                                (card1.Sides[0] == card2.Sides[0]) &&\n                                (card1.Sides[1] == card2.Sides[1]) &&\n                                (card1.Sides[2] == card2.Sides[2]) &&\n                                (card1.Sides[3] == card2.Sides[3]);\n\n                            bHasSameNumberCards = bHasSameNumberCards || bHasSameNumbers;\n                            if (bHasSameNumbers)\n                            {\n                                if (!sameNumberMap.ContainsKey(sameNumberId))\n                                {\n                                    sameNumberMap.Add(sameNumberId, new List<TriadCard>());\n                                    sameNumberMap[sameNumberId].Add(card1);\n                                    card1.SameNumberId = sameNumberId;\n                                }\n\n                                sameNumberMap[sameNumberId].Add(card2);\n                                card2.SameNumberId = sameNumberId;\n                            }\n                        }\n                    }\n\n                    if (bHasSameNumberCards)\n                    {\n                        sameNumberId++;\n                    }\n                }\n            }\n\n            Logger.WriteLine(\"Loaded cards: \" + cards.Count + \", same sides: \" + sameNumberMap.Count);\n            return cards.Count > 0;\n        }\n\n        public void Save()\n        {\n            string RawFilePath = AssetManager.Get().CreateFilePath(\"assets/\" + DBPath);\n            try\n            {\n                XmlWriterSettings writerSettings = new XmlWriterSettings();\n                writerSettings.Indent = true;\n\n                XmlWriter xmlWriter = XmlWriter.Create(RawFilePath, writerSettings);\n                xmlWriter.WriteStartDocument();\n                xmlWriter.WriteStartElement(\"root\");\n\n                foreach (TriadCard card in cards)\n                {\n                    if (card != null)\n                    {\n                        xmlWriter.WriteStartElement(\"card\");\n                        xmlWriter.WriteAttributeString(\"id\", card.Id.ToString());\n                        xmlWriter.WriteAttributeString(\"rarity\", ((int)card.Rarity).ToString());\n                        xmlWriter.WriteAttributeString(\"type\", ((int)card.Type).ToString());\n                        xmlWriter.WriteAttributeString(\"up\", card.Sides[(int)ETriadGameSide.Up].ToString());\n                        xmlWriter.WriteAttributeString(\"lt\", card.Sides[(int)ETriadGameSide.Left].ToString());\n                        xmlWriter.WriteAttributeString(\"dn\", card.Sides[(int)ETriadGameSide.Down].ToString());\n                        xmlWriter.WriteAttributeString(\"rt\", card.Sides[(int)ETriadGameSide.Right].ToString());\n                        xmlWriter.WriteAttributeString(\"sort\", card.SortOrder.ToString());\n                        xmlWriter.WriteAttributeString(\"group\", card.Group.ToString());\n                        xmlWriter.WriteEndElement();\n                    }\n                }\n\n                xmlWriter.WriteEndDocument();\n                xmlWriter.Close();\n            }\n            catch (Exception ex)\n            {\n                Logger.WriteLine(\"Saving failed! Exception:\" + ex);\n            }\n        }\n\n        public TriadCard Find(string Name)\n        {\n            foreach (TriadCard testCard in cards)\n            {\n                if (testCard != null &&\n                    testCard.Name.GetCodeName().Equals(Name, StringComparison.InvariantCultureIgnoreCase))\n                {\n                    return testCard;\n                }\n            }\n\n            return null;\n        }\n\n        public TriadCard Find(int numUp, int numLeft, int numDown, int numRight)\n        {\n            foreach (TriadCard testCard in cards)\n            {\n                if (testCard != null &&\n                    testCard.Sides[(int)ETriadGameSide.Up] == numUp &&\n                    testCard.Sides[(int)ETriadGameSide.Down] == numDown &&\n                    testCard.Sides[(int)ETriadGameSide.Left] == numLeft &&\n                    testCard.Sides[(int)ETriadGameSide.Right] == numRight)\n                {\n                    return testCard;\n                }\n            }\n\n            return null;\n        }\n\n        private int ParseCardSideNum(string desc)\n        {\n            if (desc == \"A\" || desc == \"a\" || desc == \"10\")\n            {\n                return 10;\n            }\n\n            if (desc.Length == 1 && desc[0] >= '1' && desc[0] <= '9')\n            {\n                return desc[0] - '0';\n            }\n\n            return -1;\n        }\n    }\n}\n"
  },
  {
    "path": "sources/data/TriadNpcDB.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\nusing System.Xml;\n\nnamespace FFTriadBuddy\n{\n    public class TriadNpc\n    {\n        public int Id;\n        public LocString Name;\n        public LocString LocationMap;\n        public int LocationX;\n        public int LocationY;\n        public List<TriadGameModifier> Rules;\n        public List<TriadCard> Rewards;\n        public TriadDeck Deck;\n\n        public TriadNpc(int id, List<TriadGameModifier> rules, List<TriadCard> rewards, int[] cardsAlways, int[] cardsPool)\n        {\n            Id = id;\n            Name = LocalizationDB.Get().FindOrAddLocString(ELocStringType.NpcName, id);\n            LocationMap = LocalizationDB.Get().FindOrAddLocString(ELocStringType.NpcLocation, id);\n            Rules = rules;\n            Rewards = rewards;\n            Deck = new TriadDeck(cardsAlways, cardsPool);\n        }\n\n        public TriadNpc(int id, List<TriadGameModifier> rules, List<TriadCard> rewards, TriadDeck deck)\n        {\n            Id = id;\n            Name = LocalizationDB.Get().FindOrAddLocString(ELocStringType.NpcName, id);\n            LocationMap = LocalizationDB.Get().FindOrAddLocString(ELocStringType.NpcLocation, id);\n            Rules = rules;\n            Rewards = rewards;\n            Deck = deck;\n        }\n\n        public override string ToString()\n        {\n            return Name.GetCodeName();\n        }\n\n        public string GetLocationDesc()\n        {\n            return string.Format(\"{0} ({1}, {2})\", LocationMap.GetLocalized(), LocationX, LocationY);\n        }\n    }\n\n    public class TriadNpcDB\n    {\n        public List<TriadNpc> npcs;\n        public string DBPath;\n        private static TriadNpcDB instance = new TriadNpcDB();\n\n        public TriadNpcDB()\n        {\n            DBPath = \"data/npcs.xml\";\n            npcs = new List<TriadNpc>();\n        }\n\n        public static TriadNpcDB Get()\n        {\n            return instance;\n        }\n\n        public bool Load()\n        {\n            try\n            {\n                XmlDocument xdoc = new XmlDocument();\n                xdoc.Load(AssetManager.Get().GetAsset(DBPath));\n\n                foreach (XmlNode npcNode in xdoc.DocumentElement.ChildNodes)\n                {\n                    XmlElement npcElem = (XmlElement)npcNode;\n                    if (npcElem != null && npcElem.Name == \"npc\")\n                    {\n                        try\n                        {\n                            List<TriadGameModifier> rules = new List<TriadGameModifier>();\n                            List<TriadCard> rewards = new List<TriadCard>();\n                            int[] deckA = new int[5];\n                            int[] deckV = new int[5];\n\n                            foreach (XmlNode innerNode in npcElem.ChildNodes)\n                            {\n                                XmlElement testElem = (XmlElement)innerNode;\n                                if (testElem != null)\n                                {\n                                    if (testElem.Name == \"rule\")\n                                    {\n                                        int ruleId = int.Parse(testElem.GetAttribute(\"id\"));\n                                        rules.Add(TriadGameModifierDB.Get().mods[ruleId].Clone());\n                                    }\n                                    else if (testElem.Name == \"reward\")\n                                    {\n                                        int cardId = int.Parse(testElem.GetAttribute(\"id\"));\n                                        rewards.Add(TriadCardDB.Get().cards[cardId]);\n                                    }\n                                    else if (testElem.Name == \"deckA\")\n                                    {\n                                        deckA[0] = int.Parse(testElem.GetAttribute(\"id0\"));\n                                        deckA[1] = int.Parse(testElem.GetAttribute(\"id1\"));\n                                        deckA[2] = int.Parse(testElem.GetAttribute(\"id2\"));\n                                        deckA[3] = int.Parse(testElem.GetAttribute(\"id3\"));\n                                        deckA[4] = int.Parse(testElem.GetAttribute(\"id4\"));\n                                    }\n                                    else if (testElem.Name == \"deckV\")\n                                    {\n                                        deckV[0] = int.Parse(testElem.GetAttribute(\"id0\"));\n                                        deckV[1] = int.Parse(testElem.GetAttribute(\"id1\"));\n                                        deckV[2] = int.Parse(testElem.GetAttribute(\"id2\"));\n                                        deckV[3] = int.Parse(testElem.GetAttribute(\"id3\"));\n                                        deckV[4] = int.Parse(testElem.GetAttribute(\"id4\"));\n                                    }\n                                }\n                            }\n\n                            TriadNpc newNpc = new TriadNpc(\n                                int.Parse(npcElem.GetAttribute(\"id\")),\n                                rules,\n                                rewards,\n                                deckA,\n                                deckV);\n                            newNpc.LocationX = int.Parse(npcElem.GetAttribute(\"mx\"));\n                            newNpc.LocationY = int.Parse(npcElem.GetAttribute(\"my\"));\n\n                            while (npcs.Count <= newNpc.Id)\n                            {\n                                npcs.Add(null);\n                            }\n\n                            npcs[newNpc.Id] = newNpc;\n                        }\n                        catch (Exception ex)\n                        {\n                            Logger.WriteLine(\"Loading failed! Exception:\" + ex);\n                        }\n                    }\n                }\n            }\n            catch (Exception ex)\n            {\n                Logger.WriteLine(\"Loading failed! Exception:\" + ex);\n            }\n\n            Logger.WriteLine(\"Loaded npcs: \" + npcs.Count);\n            return npcs.Count > 0;\n        }\n\n        public void Save()\n        {\n            string RawFilePath = AssetManager.Get().CreateFilePath(\"assets/\" + DBPath);\n            try\n            {\n                XmlWriterSettings writerSettings = new XmlWriterSettings();\n                writerSettings.Indent = true;\n\n                XmlWriter xmlWriter = XmlWriter.Create(RawFilePath, writerSettings);\n                xmlWriter.WriteStartDocument();\n                xmlWriter.WriteStartElement(\"root\");\n\n                foreach (TriadNpc npc in npcs)\n                {\n                    if (npc != null)\n                    {\n                        xmlWriter.WriteStartElement(\"npc\");\n                        xmlWriter.WriteAttributeString(\"id\", npc.Id.ToString());\n                        xmlWriter.WriteAttributeString(\"mx\", npc.LocationX.ToString());\n                        xmlWriter.WriteAttributeString(\"my\", npc.LocationY.ToString());\n\n                        xmlWriter.WriteStartElement(\"deckA\");\n                        for (int Idx = 0; Idx < 5; Idx++)\n                        {\n                            xmlWriter.WriteAttributeString(\"id\" + Idx, (npc.Deck.knownCards != null && npc.Deck.knownCards.Count > Idx) ? npc.Deck.knownCards[Idx].Id.ToString() : \"0\");\n                        }\n                        xmlWriter.WriteEndElement();\n\n                        xmlWriter.WriteStartElement(\"deckV\");\n                        for (int Idx = 0; Idx < 5; Idx++)\n                        {\n                            xmlWriter.WriteAttributeString(\"id\" + Idx, (npc.Deck.unknownCardPool != null && npc.Deck.unknownCardPool.Count > Idx) ? npc.Deck.unknownCardPool[Idx].Id.ToString() : \"0\");\n                        }\n                        xmlWriter.WriteEndElement();\n\n                        for (int Idx = 0; Idx < npc.Rules.Count; Idx++)\n                        {\n                            xmlWriter.WriteStartElement(\"rule\");\n                            xmlWriter.WriteAttributeString(\"id\", npc.Rules[Idx].GetLocalizationId().ToString());\n                            xmlWriter.WriteEndElement();\n                        }\n\n                        for (int Idx = 0; Idx < npc.Rewards.Count; Idx++)\n                        {\n                            xmlWriter.WriteStartElement(\"reward\");\n                            xmlWriter.WriteAttributeString(\"id\", npc.Rewards[Idx].Id.ToString());\n                            xmlWriter.WriteEndElement();\n                        }\n\n                        xmlWriter.WriteEndElement();\n                    }\n                }\n\n                xmlWriter.WriteEndDocument();\n                xmlWriter.Close();\n            }\n            catch (Exception ex)\n            {\n                Logger.WriteLine(\"Saving failed! Exception:\" + ex);\n            }\n        }\n\n        public TriadNpc Find(string Name)\n        {\n            foreach (TriadNpc testNpc in npcs)\n            {\n                if (testNpc != null && testNpc.Name.GetCodeName().Equals(Name, StringComparison.InvariantCultureIgnoreCase))\n                {\n                    return testNpc;\n                }\n            }\n\n            return null;\n        }\n\n        public List<TriadNpc> FindByReward(TriadCard card)\n        {\n            List<TriadNpc> result = new List<TriadNpc>();\n            foreach (TriadNpc testNpc in npcs)\n            {\n                if (testNpc != null && testNpc.Rewards.Contains(card))\n                {\n                    result.Add(testNpc);\n                }\n            }\n\n            return result;\n        }\n\n        public TriadNpc FindByDeckId(string deckId)\n        {\n            foreach (TriadNpc testNpc in npcs)\n            {\n                if (testNpc != null && testNpc.Deck != null && testNpc.Deck.deckId == deckId)\n                {\n                    return testNpc;\n                }\n            }\n\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "sources/data/TriadTournamentDB.cs",
    "content": "using MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\nusing System.Xml;\n\nnamespace FFTriadBuddy\n{\n    public class TriadTournament\n    {\n        public readonly LocString Name;\n        public readonly List<TriadGameModifier> Rules;\n        public readonly int Id;\n\n        public TriadTournament(int id, List<TriadGameModifier> rules)\n        {\n            Id = id;\n            Name = LocalizationDB.Get().FindOrAddLocString(ELocStringType.TournamentName, id);\n            Rules = rules;\n        }\n\n        public override string ToString()\n        {\n            return Name.GetCodeName();\n        }\n    }\n\n    public class TriadTournamentDB\n    {\n        public List<TriadTournament> tournaments;\n        public string DBPath;\n        private static TriadTournamentDB instance = new TriadTournamentDB();\n\n        public TriadTournamentDB()\n        {\n            DBPath = \"data/tournaments.xml\";\n            tournaments = new List<TriadTournament>();\n        }\n\n        public static TriadTournamentDB Get()\n        {\n            return instance;\n        }\n\n        public bool Load()\n        {\n            try\n            {\n                XmlDocument xdoc = new XmlDocument();\n                xdoc.Load(AssetManager.Get().GetAsset(DBPath));\n\n                foreach (XmlNode ttNode in xdoc.DocumentElement.ChildNodes)\n                {\n                    XmlElement ttElem = (XmlElement)ttNode;\n                    if (ttElem != null && ttElem.Name == \"tournament\")\n                    {\n                        try\n                        {\n                            List<TriadGameModifier> rules = new List<TriadGameModifier>();\n                            foreach (XmlNode innerNode in ttElem.ChildNodes)\n                            {\n                                XmlElement testElem = (XmlElement)innerNode;\n                                if (testElem != null)\n                                {\n                                    if (testElem.Name == \"rule\")\n                                    {\n                                        int ruleId = int.Parse(testElem.GetAttribute(\"id\"));\n                                        rules.Add(TriadGameModifierDB.Get().mods[ruleId].Clone());\n                                    }\n                                }\n                            }\n\n                            TriadTournament newTournament = new TriadTournament(int.Parse(ttElem.GetAttribute(\"id\")), rules);\n                            while (tournaments.Count <= newTournament.Id)\n                            {\n                                tournaments.Add(null);\n                            }\n                            tournaments[newTournament.Id] = newTournament;\n                        }\n                        catch (Exception ex)\n                        {\n                            Logger.WriteLine(\"Loading failed! Exception:\" + ex);\n                        }\n                    }\n                }\n            }\n            catch (Exception ex)\n            {\n                Logger.WriteLine(\"Loading failed! Exception:\" + ex);\n            }\n\n            Logger.WriteLine(\"Loaded tournaments: \" + tournaments.Count);\n            return tournaments.Count > 0;\n        }\n\n        public void Save()\n        {\n            string RawFilePath = AssetManager.Get().CreateFilePath(\"assets/\" + DBPath);\n            try\n            {\n                XmlWriterSettings writerSettings = new XmlWriterSettings();\n                writerSettings.Indent = true;\n\n                XmlWriter xmlWriter = XmlWriter.Create(RawFilePath, writerSettings);\n                xmlWriter.WriteStartDocument();\n                xmlWriter.WriteStartElement(\"root\");\n\n                foreach (TriadTournament tournament in tournaments)\n                {\n                    xmlWriter.WriteStartElement(\"tournament\");\n                    xmlWriter.WriteAttributeString(\"id\", tournament.Id.ToString());\n\n                    for (int Idx = 0; Idx < tournament.Rules.Count; Idx++)\n                    {\n                        xmlWriter.WriteStartElement(\"rule\");\n                        xmlWriter.WriteAttributeString(\"id\", tournament.Rules[Idx].GetLocalizationId().ToString());\n                        xmlWriter.WriteEndElement();\n                    }\n\n                    xmlWriter.WriteEndElement();\n                }\n\n                xmlWriter.WriteEndDocument();\n                xmlWriter.Close();\n            }\n            catch (Exception ex)\n            {\n                Logger.WriteLine(\"Saving failed! Exception:\" + ex);\n            }\n        }\n\n    }\n}\n"
  },
  {
    "path": "sources/gamelogic/FavDeckSolver.cs",
    "content": "﻿using System;\nusing System.Threading.Tasks;\n\nnamespace FFTriadBuddy\n{\n    public class FavDeckSolver\n    {\n        private TriadDeck deck;\n        private TriadGameSimulation currentGame;\n        private TriadNpc npc;\n        private TriadGameSolver solver;\n        public int calcId;\n\n        public int contextId;\n        public int progress => (solver == null) ? 0 : (int)(solver.GetAgentProgress() * 100);\n\n        public delegate void SolvedDelegate(int id, TriadDeck deck, SolverResult chance);\n        public event SolvedDelegate OnSolved;\n\n        public FavDeckSolver()\n        {\n            calcId = 0;\n        }\n\n        public void SetDeck(TriadDeck deck)\n        {\n            if (this.deck == null || deck == null || !this.deck.Equals(deck))\n            {\n                this.deck = deck;\n                CalcWinChance();\n            }\n        }\n\n        public void Update(TriadGameSimulation currentGame, TriadNpc npc)\n        {\n            bool isDirty = true;\n            if (solver != null && solver.simulation.modifiers.Count == currentGame.modifiers.Count)\n            {\n                int numMatching = 0;\n                for (int Idx = 0; Idx < currentGame.modifiers.Count; Idx++)\n                {\n                    TriadGameModifier currentMod = solver.simulation.modifiers[Idx];\n                    TriadGameModifier reqMod = currentGame.modifiers[Idx];\n\n                    if (currentMod.GetType() == reqMod.GetType())\n                    {\n                        numMatching++;\n                    }\n                }\n\n                isDirty = (numMatching != solver.simulation.modifiers.Count);\n            }\n\n            if (npc != this.npc)\n            {\n                this.npc = npc;\n                isDirty = true;\n            }\n\n            if (isDirty)\n            {\n                this.currentGame = currentGame;\n                CalcWinChance();\n            }\n        }\n\n        private class CalcContext\n        {\n            public TriadGameSolver solver;\n            public TriadGameSimulationState gameState;\n            public int calcId;\n        }\n\n        private void CalcWinChance()\n        {\n            if (currentGame != null && deck != null && npc != null)\n            {\n                calcId++;\n\n                solver = new TriadGameSolver() { name = string.Format(\"Solv{0}:{1}\", contextId + 1, calcId) };\n                solver.InitializeSimulation(currentGame.modifiers);\n\n                var gameState = solver.StartSimulation(deck, npc.Deck, ETriadGameState.InProgressBlue);\n                var calcContext = new CalcContext() { solver = solver, gameState = gameState, calcId = calcId };\n\n                Action<object> solverAction = (ctxOb) =>\n                {\n                    var ctx = ctxOb as CalcContext;\n                    ctx.solver.FindNextMove(ctx.gameState, out var dummyCardIdx, out var dummyBoardPos, out SolverResult bestChance);\n                    OnSolved(ctx.calcId, ctx.gameState.deckBlue.deck, bestChance);\n                };\n\n                new TaskFactory().StartNew(solverAction, calcContext);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/gamelogic/MiniCactpotGame.cs",
    "content": "using MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\n\nnamespace FFTriadBuddy\n{\n    public class CactpotGame\n    {\n        private static readonly int[,] cachedSolverData = new int[,] { { 2, 2, 2, 4, 4, 4, 4, 2, 2 }, { 4, 4, 4, 6, 4, 4, 4, 0, 0 }, { 0, 0, 0, 4, 4, 4, 4, 0, 0 }, { 4, 4, 4, 2, 2, 4, 4, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 4, 4, 4, 0, 0, 4, 4, 2, 2 }, { 0, 0, 0, 4, 4, 4, 4, 0, 0 }, { 4, 4, 4, 0, 0, 4, 4, 6, 6 }, { 2, 2, 2, 4, 4, 4, 4, 2, 2 } };\n        private static readonly int[] payouts = new int[] {\n            0, 0, 0, 0, 0, 0, // 0..5 - padding\n            10000, // 6\n            36, // 7\n            720, // 8\n            360, // 9\n            80, // 10\n            252, // 11 \n            108, // 12\n            72, // 13\n            54, // 14\n            180, // 15\n            72, // 16\n            180, // 17\n            119, // 18\n            36, // 19\n            306, // 20\n            1080, // 21\n            144, // 22\n            1800, // 23\n            3600, // 24\n        };\n\n        private static IEnumerable<List<int>> Permutate(List<int> seq, int count)\n        {\n            if (count == 1)\n            {\n                yield return seq;\n            }\n            else\n            {\n                for (int Idx = 0; Idx < count; Idx++)\n                {\n                    foreach (var perm in Permutate(seq, count - 1))\n                    {\n                        yield return perm;\n                    }\n\n                    int swap = seq[count - 1];\n                    seq.RemoveAt(count - 1);\n                    seq.Insert(0, swap);\n                }\n            }\n        }\n\n        private static void CalculateLinePayouts(int[] board, List<int> remainingNumbers, out float[] linePayouts, bool bDebugMode = false)\n        {\n            // build mapping to combine board and missing number list into fully filled test board\n            // val >= 0 : use number from board[val]\n            // val < 0  : use number from missingNumbers[val]\n            int[] mapBoard = new int[9];\n            int nextMissingId = -1;\n            for (int Idx = 0; Idx < board.Length; Idx++)\n            {\n                if (board[Idx] == 0)\n                {\n                    mapBoard[Idx] = nextMissingId;\n                    nextMissingId--;\n                }\n                else\n                {\n                    mapBoard[Idx] = Idx;\n                }\n            }\n\n            // calc avg payouts on each line from all possible permutations\n            linePayouts = new float[8] { 0, 0, 0, 0, 0, 0, 0, 0 };\n            int[] testBoard = new int[9];\n\n            foreach (List<int> permNumbers in Permutate(remainingNumbers, remainingNumbers.Count))\n            {\n                for (int Idx = 0; Idx < testBoard.Length; Idx++)\n                {\n                    testBoard[Idx] = (mapBoard[Idx] < 0) ? permNumbers[-mapBoard[Idx] - 1] : board[Idx];\n                }\n\n                // 3 horizontal: 012, 345, 678\n                linePayouts[0] += payouts[testBoard[0] + testBoard[1] + testBoard[2]];\n                linePayouts[1] += payouts[testBoard[3] + testBoard[4] + testBoard[5]];\n                linePayouts[2] += payouts[testBoard[6] + testBoard[7] + testBoard[8]];\n\n                // 3 vertical: 036, 147, 258\n                linePayouts[3] += payouts[testBoard[0] + testBoard[3] + testBoard[6]];\n                linePayouts[4] += payouts[testBoard[1] + testBoard[4] + testBoard[7]];\n                linePayouts[5] += payouts[testBoard[2] + testBoard[5] + testBoard[8]];\n\n                // 2 diagonal: 048, 246\n                linePayouts[6] += payouts[testBoard[0] + testBoard[4] + testBoard[8]];\n                linePayouts[7] += payouts[testBoard[2] + testBoard[4] + testBoard[6]];\n            }\n\n            int permCount = 1;\n            for (int Idx = 2; Idx <= remainingNumbers.Count; Idx++)\n            {\n                permCount *= Idx;\n            }\n\n            for (int Idx = 0; Idx < linePayouts.Length; Idx++)\n            {\n                linePayouts[Idx] /= permCount;\n                if (bDebugMode) { Logger.WriteLine(\"> line[\" + Idx + \"]: \" + linePayouts[Idx]); }\n            }\n        }\n\n        private static float GetBestScore(int[] board, List<int> remainingNumbers, List<int> remainingPos, out int bestIdx, bool bDebugMode = false)\n        {\n            bestIdx = 0;\n            float bestScore = 0;\n\n            // spot score: explore all possibles results recursively until reaching 4 revealed, avg all best line scores \n            if (remainingPos.Count <= 5)\n            {\n                CalculateLinePayouts(board, remainingNumbers, out float[] linePayouts);\n\n                for (int Idx = 0; Idx < linePayouts.Length; Idx++)\n                {\n                    if (bestScore < linePayouts[Idx])\n                    {\n                        bestScore = linePayouts[Idx];\n                        bestIdx = Idx;\n                    }\n                }\n\n                return bestScore;\n            }\n            else\n            {\n                for (int PosIdx = 0; PosIdx < remainingPos.Count; PosIdx++)\n                {\n                    int useBoardPos = remainingPos[PosIdx];\n                    remainingPos.RemoveAt(PosIdx);\n\n                    float sumPos = 0;\n                    for (int NumberIdx = 0; NumberIdx < remainingNumbers.Count; NumberIdx++)\n                    {\n                        int useNumber = remainingNumbers[NumberIdx];\n                        remainingNumbers.RemoveAt(NumberIdx);\n\n                        board[useBoardPos] = useNumber;\n\n                        float maxNestedScore = GetBestScore(board, remainingNumbers, remainingPos, out int dummyPos, bDebugMode);\n                        sumPos += maxNestedScore;\n\n                        remainingNumbers.Insert(NumberIdx, useNumber);\n                    }\n\n                    board[useBoardPos] = 0;\n                    remainingPos.Insert(PosIdx, useBoardPos);\n\n                    if (bestScore < sumPos)\n                    {\n                        bestScore = sumPos;\n                        bestIdx = useBoardPos;\n                    }\n                }\n\n                return bestScore / remainingPos.Count;\n            }\n        }\n\n        public static int FindNextCircle(int[] board, bool bDebugMode = false)\n        {\n            // this takes way too long with only 1 known number (~3s to solve)\n            // and is acceptable with 2+ (up to 100ms)\n            // cache all possibile combinations of first step - call BuildCachedData() on program startup\n\n            Stopwatch timer = new Stopwatch();\n            timer.Start();\n\n            int bestIdx = -1;\n\n            List<int> remainingSpots = new List<int>();\n            for (int Idx = 0; Idx < board.Length; Idx++)\n            {\n                if (board[Idx] == 0)\n                {\n                    remainingSpots.Add(Idx);\n                }\n                else\n                {\n                    bestIdx = cachedSolverData[Idx, board[Idx] - 1];\n                }\n            }\n\n            if (remainingSpots.Count < 8)\n            {\n                List<int> remainingNumbers = new List<int>();\n                for (int Idx = 1; Idx <= 9; Idx++)\n                {\n                    if (Array.IndexOf(board, Idx) < 0)\n                    {\n                        remainingNumbers.Add(Idx);\n                    }\n                }\n\n                GetBestScore(board, remainingNumbers, remainingSpots, out bestIdx, bDebugMode);\n            }\n\n            if (bDebugMode) { Logger.WriteLine(\"> best: \" + bestIdx); }\n\n            timer.Stop();\n            Logger.WriteLine(\"FindNextCircle: \" + timer.ElapsedMilliseconds + \"ms (missing: \" + remainingSpots.Count + \")\");\n\n            return bestIdx;\n        }\n\n        public static void FindBestLine(int[] board, out int fromIdx, out int toIdx, bool bDebugMode = false)\n        {\n            List<int> remainingNumbers = new List<int>();\n            for (int Idx = 1; Idx <= 9; Idx++)\n            {\n                if (Array.IndexOf(board, Idx) < 0)\n                {\n                    remainingNumbers.Add(Idx);\n                }\n            }\n\n            fromIdx = -1;\n            toIdx = -1;\n\n            if (remainingNumbers.Count <= 5)\n            {\n                List<int> dummyList = new List<int>();\n                GetBestScore(board, remainingNumbers, dummyList, out int bestLine);\n\n                switch (bestLine)\n                {\n                    case 0: fromIdx = 0; toIdx = 2; break;\n                    case 1: fromIdx = 3; toIdx = 5; break;\n                    case 2: fromIdx = 6; toIdx = 8; break;\n                    case 3: fromIdx = 0; toIdx = 6; break;\n                    case 4: fromIdx = 1; toIdx = 7; break;\n                    case 5: fromIdx = 2; toIdx = 8; break;\n                    case 6: fromIdx = 0; toIdx = 8; break;\n                    case 7: fromIdx = 2; toIdx = 6; break;\n                    default: break;\n                }\n            }\n        }\n\n        public static void BuildCachedData()\n        {\n            int[] board = new int[9] { 0, 0, 0, 0, 0, 0, 0, 0, 0 };\n            string cacheStr = \"{ \";\n\n            for (int PosIdx = 0; PosIdx < 9; PosIdx++)\n            {\n                cacheStr += \"{ \";\n                for (int NumberIdx = 1; NumberIdx <= 9; NumberIdx++)\n                {\n                    board[PosIdx] = NumberIdx;\n\n                    int bestCircleIdx = FindNextCircle(board);\n                    cacheStr += bestCircleIdx + (NumberIdx < 9 ? \", \" : \"\");\n                }\n\n                cacheStr += \" }\" + (PosIdx < 8 ? \", \" : \"\");\n                board[PosIdx] = 0;\n            }\n\n            cacheStr += \" };\";\n            Logger.WriteLine(\"int[,] cachedSolverData = new int[,]\" + cacheStr);\n        }\n    }\n}\n"
  },
  {
    "path": "sources/gamelogic/TriadCard.cs",
    "content": "﻿using System;\n\nnamespace FFTriadBuddy\n{\n    public enum ETriadCardRarity\n    {\n        Common,\n        Uncommon,\n        Rare,\n        Epic,\n        Legendary\n    }\n\n    public enum ETriadCardType\n    {\n        None,\n        Beastman,\n        Primal,\n        Scion,\n        Garlean,\n    }\n\n    public enum ETriadCardOwner\n    {\n        Unknown,\n        Blue,\n        Red,\n    }\n\n    public enum ETriadGameSide\n    {\n        Up,\n        Left,\n        Down,\n        Right,\n    }\n\n    public class TriadCard : IEquatable<TriadCard>\n    {\n        public int Id;\n        public LocString Name;\n        public ETriadCardRarity Rarity;\n        public ETriadCardType Type;\n        public int[] Sides;\n        public int SameNumberId;\n        public int SortOrder;\n        public int Group;\n        public float OptimizerScore;\n\n        public int SmallIconId => 88000 + Id;\n        public int BigIconId => 87000 + Id;\n\n        public TriadCard()\n        {\n            Id = -1;\n            Sides = new int[4] { 0, 0, 0, 0 };\n            SameNumberId = -1;\n            SortOrder = 0;\n            Group = 0;\n            OptimizerScore = 0.0f;\n        }\n\n        public TriadCard(int id, ETriadCardRarity rarity, ETriadCardType type, int numUp, int numDown, int numLeft, int numRight, int sortOrder, int group)\n        {\n            Id = id;\n            Name = LocalizationDB.Get().FindOrAddLocString(ELocStringType.CardName, id);\n            Rarity = rarity;\n            Type = type;\n            Sides = new int[4] { numUp, numLeft, numDown, numRight };\n            SameNumberId = -1;\n            SortOrder = sortOrder;\n            Group = group;\n\n            if (group != 0 && SortOrder < 1000)\n            {\n                SortOrder += 1000;\n            }\n\n            OptimizerScore = TriadDeckOptimizer.GetCardScore(this);\n        }\n\n        public override bool Equals(object obj)\n        {\n            return Equals(obj as TriadCard);\n        }\n\n        public bool Equals(TriadCard other)\n        {\n            return (other != null) && (Id == other.Id);\n        }\n\n        public override int GetHashCode()\n        {\n            return 2108858624 + Id.GetHashCode();\n        }\n\n        public bool IsValid()\n        {\n            return (Id >= 0) &&\n                (Sides[0] >= 1) && (Sides[0] <= 10) &&\n                (Sides[1] >= 1) && (Sides[1] <= 10) &&\n                (Sides[2] >= 1) && (Sides[2] <= 10) &&\n                (Sides[3] >= 1) && (Sides[3] <= 10);\n        }\n\n        public string ToShortCodeString()\n        {\n            return \"[\" + Id + \":\" + Name.GetCodeName() + \"]\";\n        }\n\n        public string ToShortLocalizedString()\n        {\n            return \"[\" + Id + \":\" + Name.GetLocalized() + \"]\";\n        }\n\n        public string ToLocalizedString()\n        {\n            return string.Format(\"[{0}] {1} {2} [{3}, {4}, {5}, {6}]\",\n                Id, Name.GetLocalized(),\n                new string('*', (int)Rarity + 1),\n                Sides[0], Sides[1], Sides[2], Sides[3],\n                (Type != ETriadCardType.None) ? \" [\" + LocalizationDB.Get().LocCardTypes[(int)Type] + \"]\" : \"\");\n        }\n\n        public override string ToString()\n        {\n            return string.Format(\"[{0}] {1} {2} [{3}, {4}, {5}, {6}]\",\n                Id, Name.GetCodeName(),\n                new string('*', (int)Rarity + 1),\n                Sides[0], Sides[1], Sides[2], Sides[3],\n                (Type != ETriadCardType.None) ? \" [\" + Type + \"]\" : \"\");\n        }\n    }\n\n    public class TriadCardInstance\n    {\n        public readonly TriadCard card;\n        public ETriadCardOwner owner;\n        public int scoreModifier;\n\n        public TriadCardInstance(TriadCard card, ETriadCardOwner owner)\n        {\n            this.card = card;\n            this.owner = owner;\n            scoreModifier = 0;\n        }\n\n        public TriadCardInstance(TriadCardInstance copyFrom)\n        {\n            card = copyFrom.card;\n            owner = copyFrom.owner;\n            scoreModifier = copyFrom.scoreModifier;\n        }\n\n        public override string ToString()\n        {\n            return owner + \" \" + card +\n                ((scoreModifier > 0) ? (\" +\" + scoreModifier) :\n                 (scoreModifier < 0) ? (\" -\" + scoreModifier) :\n                 \"\");\n        }\n\n        public int GetRawNumber(ETriadGameSide side)\n        {\n            return card.Sides[(int)side];\n        }\n\n        public int GetNumber(ETriadGameSide side)\n        {\n            return Math.Min(Math.Max(GetRawNumber(side) + scoreModifier, 1), 10);\n        }\n\n        public int GetOppositeNumber(ETriadGameSide side)\n        {\n            return GetNumber((ETriadGameSide)(((int)side + 2) % 4));\n        }\n    }\n}\n"
  },
  {
    "path": "sources/gamelogic/TriadDeck.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\n\nnamespace FFTriadBuddy\n{\n    public enum ETriadDeckState\n    {\n        Valid,\n        MissingCards,\n        HasDuplicates,\n        TooMany4Star,\n        TooMany5Star,\n    };\n\n    public class TriadDeck\n    {\n        public List<TriadCard> knownCards;\n        public List<TriadCard> unknownCardPool;\n        public string deckId;\n\n        public TriadDeck()\n        {\n            knownCards = new List<TriadCard>();\n            unknownCardPool = new List<TriadCard>();\n        }\n\n        public TriadDeck(List<TriadCard> knownCards, List<TriadCard> unknownCardPool)\n        {\n            this.knownCards = new List<TriadCard>();\n            this.unknownCardPool = new List<TriadCard>();\n\n            this.knownCards.AddRange(knownCards);\n            this.unknownCardPool.AddRange(unknownCardPool);\n            UpdateDeckId();\n        }\n\n        public TriadDeck(IEnumerable<TriadCard> knownCards)\n        {\n            this.knownCards = new List<TriadCard>();\n            unknownCardPool = new List<TriadCard>();\n\n            this.knownCards.AddRange(knownCards);\n            UpdateDeckId();\n        }\n\n        public TriadDeck(IEnumerable<int> knownCardIds, IEnumerable<int> unknownCardlIds)\n        {\n            TriadCardDB cardDB = TriadCardDB.Get();\n\n            knownCards = new List<TriadCard>();\n            foreach (int id in knownCardIds)\n            {\n                TriadCard card = cardDB.cards[id];\n                if (card != null && card.IsValid())\n                {\n                    knownCards.Add(card);\n                }\n            }\n\n            unknownCardPool = new List<TriadCard>();\n            foreach (int id in unknownCardlIds)\n            {\n                TriadCard card = cardDB.cards[id];\n                if (card != null && card.IsValid())\n                {\n                    unknownCardPool.Add(card);\n                }\n            }\n\n            UpdateDeckId();\n        }\n\n        public TriadDeck(IEnumerable<int> knownCardIds)\n        {\n            TriadCardDB cardDB = TriadCardDB.Get();\n\n            knownCards = new List<TriadCard>();\n            foreach (int id in knownCardIds)\n            {\n                TriadCard card = cardDB.cards[id];\n                if (card != null && card.IsValid())\n                {\n                    knownCards.Add(card);\n                }\n            }\n\n            unknownCardPool = new List<TriadCard>();\n            UpdateDeckId();\n        }\n\n        public ETriadDeckState GetDeckState()\n        {\n            PlayerSettingsDB playerDB = PlayerSettingsDB.Get();\n            int[] rarityCounters = new int[5];\n\n            for (int DeckIdx = 0; DeckIdx < knownCards.Count; DeckIdx++)\n            {\n                TriadCard deckCard = knownCards[DeckIdx];\n                bool bIsOwned = playerDB.ownedCards.Contains(deckCard);\n                if (!bIsOwned)\n                {\n                    return ETriadDeckState.MissingCards;\n                }\n\n                for (int TestIdx = 0; TestIdx < knownCards.Count; TestIdx++)\n                {\n                    if ((TestIdx != DeckIdx) && knownCards[TestIdx].Equals(deckCard))\n                    {\n                        return ETriadDeckState.HasDuplicates;\n                    }\n                }\n\n                rarityCounters[(int)deckCard.Rarity]++;\n            }\n\n            int numRare5 = rarityCounters[(int)ETriadCardRarity.Legendary];\n            int numRare45 = rarityCounters[(int)ETriadCardRarity.Epic] + numRare5;\n\n            if (numRare5 > 1)\n            {\n                return ETriadDeckState.TooMany5Star;\n            }\n            else if (numRare45 > 2)\n            {\n                return ETriadDeckState.TooMany4Star;\n            }\n\n            return ETriadDeckState.Valid;\n        }\n\n        public TriadCard GetCard(int Idx)\n        {\n            if (Idx < knownCards.Count)\n            {\n                return knownCards[Idx];\n            }\n            else if (Idx < (knownCards.Count + unknownCardPool.Count))\n            {\n                return unknownCardPool[Idx - knownCards.Count];\n            }\n\n            return null;\n        }\n\n        public int GetCardIndex(TriadCard card)\n        {\n            int cardIdx = knownCards.IndexOf(card);\n            if (cardIdx >= 0)\n            {\n                return cardIdx;\n            }\n\n            cardIdx = unknownCardPool.IndexOf(card);\n            if (cardIdx >= 0)\n            {\n                return cardIdx + knownCards.Count;\n            }\n\n            return -1;\n        }\n\n        public bool SetCard(int Idx, TriadCard card)\n        {\n            bool bResult = false;\n            if (Idx < knownCards.Count)\n            {\n                knownCards[Idx] = card;\n                bResult = true;\n            }\n            else if (Idx < (knownCards.Count + unknownCardPool.Count))\n            {\n                unknownCardPool[Idx - knownCards.Count] = card;\n                bResult = true;\n            }\n\n            return bResult;\n        }\n\n        public int GetPower()\n        {\n            int SumRating = 0;\n            foreach (TriadCard card in knownCards)\n            {\n                SumRating += (int)card.Rarity + 1;\n            }\n\n            foreach (TriadCard card in unknownCardPool)\n            {\n                SumRating += (int)card.Rarity + 1;\n            }\n\n            int NumCards = knownCards.Count + unknownCardPool.Count;\n            int DeckPower = (NumCards > 0) ? System.Math.Min(System.Math.Max((SumRating * 2 / NumCards), 1), 10) : 1;\n\n            return DeckPower;\n        }\n\n        public void UpdateDeckId()\n        {\n            deckId = \"K\";\n\n            List<TriadCard> sortedList = new List<TriadCard>();\n            sortedList.AddRange(knownCards);\n            sortedList.Sort((a, b) => a.Id.CompareTo(b.Id));\n\n            foreach (TriadCard card in sortedList)\n            {\n                deckId += \":\";\n                deckId += card.Id;\n            }\n\n            deckId += \"U\";\n\n            sortedList.Clear();\n            sortedList.AddRange(unknownCardPool);\n            sortedList.Sort((a, b) => a.Id.CompareTo(b.Id));\n\n            foreach (TriadCard card in sortedList)\n            {\n                deckId += \":\";\n                deckId += card.Id;\n            }\n        }\n\n        public override bool Equals(object obj)\n        {\n            return Equals(obj as TriadDeck);\n        }\n\n        public bool Equals(TriadDeck otherDeck)\n        {\n            if (deckId != null && otherDeck.deckId != null)\n            {\n                return deckId.Equals(otherDeck.deckId);\n            }\n\n            if ((knownCards.Count != otherDeck.knownCards.Count) ||\n                (unknownCardPool.Count != otherDeck.unknownCardPool.Count))\n            {\n                return false;\n            }\n\n            for (int Idx = 0; Idx < knownCards.Count; Idx++)\n            {\n                if (!knownCards[Idx].Equals(otherDeck.knownCards[Idx]))\n                {\n                    return false;\n                }\n            }\n\n            for (int Idx = 0; Idx < unknownCardPool.Count; Idx++)\n            {\n                if (!unknownCardPool[Idx].Equals(otherDeck.unknownCardPool[Idx]))\n                {\n                    return false;\n                }\n            }\n\n            return true;\n        }\n\n        public override int GetHashCode()\n        {\n            var hashCode = 739328532;\n            hashCode = hashCode * -1521134295 + EqualityComparer<List<TriadCard>>.Default.GetHashCode(knownCards);\n            hashCode = hashCode * -1521134295 + EqualityComparer<List<TriadCard>>.Default.GetHashCode(unknownCardPool);\n            return hashCode;\n        }\n\n        public override string ToString()\n        {\n            string desc = \"\";\n            foreach (TriadCard card in knownCards)\n            {\n                desc += card.ToShortCodeString() + \", \";\n            }\n\n            desc = (desc.Length > 2) ? desc.Remove(desc.Length - 2, 2) : \"(none)\";\n\n            if (unknownCardPool.Count > 0)\n            {\n                desc += \" + unknown(\";\n                foreach (TriadCard card in unknownCardPool)\n                {\n                    desc += card.ToShortCodeString() + \", \";\n                }\n\n                desc = desc.Remove(desc.Length - 2, 2);\n                desc += \")\";\n            }\n\n            int power = GetPower();\n            desc += \", power:\" + power;\n\n            return desc;\n        }\n    }\n\n    public class TriadDeckNamed : TriadDeck\n    {\n        public string Name;\n\n        public TriadDeckNamed() { }\n        public TriadDeckNamed(TriadDeck copyFrom) : base(copyFrom.knownCards)\n        {\n        }\n    }\n\n    public abstract class TriadDeckInstance\n    {\n        public abstract void OnCardPlacedFast(int Idx);\n        public abstract int GetFirstAvailableCardFast();\n        public abstract TriadCard GetCard(int Idx);\n        public abstract int GetCardIndex(TriadCard card);\n        public abstract TriadDeckInstance CreateCopy();\n\n        public TriadDeck deck;\n        public int availableCardMask;\n        public int numUnknownPlaced;\n        public int numPlaced;\n\n        // manual, player = 5\n        // manual, npc = (up to 5 fixed) + (up to 5 variable)\n        // screen, npc = (up to 5 hidden) + (up to 5 fixed) + (up to 5 variable)\n        public const int maxAvailableCards = 15;\n\n        public bool IsPlaced(int cardIdx)\n        {\n            return (availableCardMask & (1 << cardIdx)) == 0;\n        }\n\n        public TriadCard GetFirstAvailableCard()\n        {\n            int cardIdx = GetFirstAvailableCardFast();\n            return (cardIdx < 0) ? null : GetCard(cardIdx);\n        }\n\n        public List<TriadCard> GetAvailableCards()\n        {\n            List<TriadCard> cards = new List<TriadCard>();\n            for (int Idx = 0; Idx < maxAvailableCards; Idx++)\n            {\n                bool bIsAvailable = (availableCardMask & (1 << Idx)) != 0;\n                if (bIsAvailable)\n                {\n                    TriadCard card = GetCard(Idx);\n                    cards.Add(card);\n                }\n            }\n\n            return cards;\n        }\n    }\n\n    public class TriadDeckInstanceManual : TriadDeckInstance\n    {\n        public TriadDeckInstanceManual(TriadDeck deck)\n        {\n            this.deck = deck;\n            numUnknownPlaced = 0;\n            numPlaced = 0;\n            availableCardMask = (1 << (deck.knownCards.Count + deck.unknownCardPool.Count)) - 1;\n        }\n\n        public TriadDeckInstanceManual(TriadDeckInstanceManual copyFrom)\n        {\n            deck = copyFrom.deck;\n            numUnknownPlaced = copyFrom.numUnknownPlaced;\n            numPlaced = copyFrom.numPlaced;\n            availableCardMask = copyFrom.availableCardMask;\n        }\n\n        public override TriadDeckInstance CreateCopy()\n        {\n            TriadDeckInstanceManual deckCopy = new TriadDeckInstanceManual(this);\n            return deckCopy;\n        }\n\n        public override void OnCardPlacedFast(int cardIdx)\n        {\n            availableCardMask &= ~(1 << cardIdx);\n            numPlaced++;\n\n            if (cardIdx >= deck.knownCards.Count)\n            {\n                const int maxCardsToPlace = ((TriadGameSimulationState.boardSize * TriadGameSimulationState.boardSize) / 2) + 1;\n                int maxUnknownToPlace = maxCardsToPlace - deck.knownCards.Count;\n\n                numUnknownPlaced++;\n                if (numUnknownPlaced >= maxUnknownToPlace)\n                {\n                    availableCardMask &= ((1 << deck.knownCards.Count) - 1);\n                }\n            }\n        }\n\n        public override int GetFirstAvailableCardFast()\n        {\n            for (int Idx = 0; Idx < deck.knownCards.Count; Idx++)\n            {\n                bool bIsAvailable = (availableCardMask & (1 << Idx)) != 0;\n                if (bIsAvailable)\n                {\n                    return Idx;\n                }\n            }\n\n            return -1;\n        }\n\n        public override TriadCard GetCard(int Idx)\n        {\n            return deck.GetCard(Idx);\n        }\n\n        public override int GetCardIndex(TriadCard card)\n        {\n            int cardIdx = deck.knownCards.IndexOf(card);\n            if (cardIdx < 0)\n            {\n                cardIdx = deck.unknownCardPool.IndexOf(card) + deck.knownCards.Count;\n            }\n\n            return cardIdx;\n        }\n\n        public override string ToString()\n        {\n            string desc = \"Placed: \" + numPlaced + \", Available: \";\n\n            if (availableCardMask > 0)\n            {\n                for (int Idx = 0; Idx < maxAvailableCards; Idx++)\n                {\n                    bool bIsAvailable = (availableCardMask & (1 << Idx)) != 0;\n                    if (bIsAvailable)\n                    {\n                        TriadCard card = GetCard(Idx);\n                        desc += card.ToShortCodeString() + \", \";\n                    }\n                }\n\n                desc = desc.Remove(desc.Length - 2, 2);\n            }\n            else\n            {\n                desc += \"none\";\n            }\n\n            return desc;\n        }\n    }\n\n    public class TriadDeckInstanceScreen : TriadDeckInstance\n    {\n        public TriadCard[] cards;\n        public TriadCard swappedCard;\n        public int unknownPoolMask;\n        public int swappedCardIdx;\n\n        public TriadDeckInstanceScreen()\n        {\n            cards = new TriadCard[5];\n            availableCardMask = 0;\n            unknownPoolMask = 0;\n            numUnknownPlaced = 0;\n            numPlaced = 0;\n            swappedCardIdx = -1;\n            swappedCard = null;\n        }\n\n        public TriadDeckInstanceScreen(TriadDeckInstanceScreen copyFrom)\n        {\n            cards = new TriadCard[copyFrom.cards.Length];\n            for (int Idx = 0; Idx < copyFrom.cards.Length; Idx++)\n            {\n                cards[Idx] = copyFrom.cards[Idx];\n            }\n\n            deck = copyFrom.deck;\n            numUnknownPlaced = copyFrom.numUnknownPlaced;\n            numPlaced = copyFrom.numPlaced;\n            availableCardMask = copyFrom.availableCardMask;\n            unknownPoolMask = copyFrom.unknownPoolMask;\n            swappedCardIdx = copyFrom.swappedCardIdx;\n            swappedCard = copyFrom.swappedCard;\n        }\n\n        public override TriadDeckInstance CreateCopy()\n        {\n            TriadDeckInstanceScreen deckCopy = new TriadDeckInstanceScreen(this);\n            return deckCopy;\n        }\n\n        public override int GetFirstAvailableCardFast()\n        {\n            for (int Idx = 0; Idx < cards.Length; Idx++)\n            {\n                bool bIsAvailable = (availableCardMask & (1 << Idx)) != 0;\n                if (bIsAvailable)\n                {\n                    return Idx;\n                }\n            }\n\n            return -1;\n        }\n\n        public void UpdateAvailableCards(TriadCard[] screenCards)\n        {\n            availableCardMask = 0;\n            numPlaced = 0;\n            numUnknownPlaced = 0;\n\n            Array.Copy(screenCards, cards, 5);\n\n            int hiddenCardId = TriadCardDB.Get().hiddenCard.Id;\n            for (int Idx = 0; Idx < cards.Length; Idx++)\n            {\n                if (cards[Idx] != null)\n                {\n                    if (cards[Idx].Id != hiddenCardId)\n                    {\n                        availableCardMask |= (1 << Idx);\n                    }\n                }\n                else\n                {\n                    numPlaced++;\n                }\n            }\n        }\n\n        public void SetSwappedCard(TriadCard swappedCard, int swappedCardIdx)\n        {\n            this.swappedCard = swappedCard;\n            this.swappedCardIdx = swappedCardIdx;\n            unknownPoolMask &= ~(1 << swappedCardIdx);\n        }\n\n        public override void OnCardPlacedFast(int cardIdx)\n        {\n            int cardMask = (1 << cardIdx);\n            availableCardMask &= ~cardMask;\n\n            if (deck != null)\n            {\n                bool bIsUnknown = (unknownPoolMask & cardMask) != 0;\n                if (bIsUnknown)\n                {\n                    numUnknownPlaced++;\n\n                    int maxUnknownToUse = cards.Length - deck.knownCards.Count;\n                    if (numUnknownPlaced >= maxUnknownToUse)\n                    {\n                        availableCardMask &= ~unknownPoolMask;\n                    }\n                }\n            }\n        }\n\n        public override TriadCard GetCard(int Idx)\n        {\n            return (Idx < 0) ? null :\n                (Idx == swappedCardIdx) ? swappedCard :\n                (Idx < cards.Length) ? cards[Idx] :\n                (deck != null) ? deck.GetCard(Idx - cards.Length) :\n                null;\n        }\n\n        public override int GetCardIndex(TriadCard card)\n        {\n            if (card == swappedCard)\n            {\n                return swappedCardIdx;\n            }\n\n            int cardIdx = Array.IndexOf(cards, card);\n            if (cardIdx < 0 && deck != null)\n            {\n                cardIdx = deck.GetCardIndex(card);\n                if (cardIdx >= 0)\n                {\n                    cardIdx += cards.Length;\n                }\n            }\n\n            return cardIdx;\n        }\n\n        public override string ToString()\n        {\n            string desc = \"[SCREEN] Available: \";\n\n            if (availableCardMask > 0)\n            {\n                for (int Idx = 0; Idx < cards.Length; Idx++)\n                {\n                    bool bIsAvailable = (availableCardMask & (1 << Idx)) != 0;\n                    if (bIsAvailable)\n                    {\n                        TriadCard card = GetCard(Idx);\n                        desc += (card != null ? card.Name.GetCodeName() : \"\") + (Idx == swappedCardIdx ? \":SWAP\" : \"\") + \", \";\n                    }\n                }\n\n                desc = desc.Remove(desc.Length - 2, 2);\n            }\n            else\n            {\n                desc += \"none\";\n            }\n\n            int visibleCardsMask = (cards != null) ? ((1 << cards.Length) - 1) : 0;\n            bool hasHiddenCards = (availableCardMask & ~visibleCardsMask) != 0;\n            if (hasHiddenCards)\n            {\n                desc += \", Unknown: \";\n                if (deck != null)\n                {\n                    for (int Idx = cards.Length; Idx < maxAvailableCards; Idx++)\n                    {\n                        bool bIsAvailable = (availableCardMask & (1 << Idx)) != 0;\n                        if (bIsAvailable)\n                        {\n                            TriadCard card = GetCard(Idx);\n                            bool bIsKnownPool = (unknownPoolMask & (1 << Idx)) == 0;\n                            desc += card.ToShortCodeString() + \":\" + Idx + \":\" + (bIsKnownPool ? \"K\" : \"U\") + (Idx == swappedCardIdx ? \":SWAP\" : \"\") + \", \";\n                        }\n                    }\n\n                    desc = desc.Remove(desc.Length - 2, 2);\n                }\n                else\n                {\n                    desc += \"(missing deck!)\";\n                }\n            }\n\n            return desc;\n        }\n\n        public void LogAvailableCards(string deckName)\n        {\n            Logger.WriteLine(deckName + \" state> numPlaced:\" + numPlaced + \", numUnknownPlaced:\" + numUnknownPlaced);\n            for (int Idx = 0; Idx < maxAvailableCards; Idx++)\n            {\n                bool bIsAvailable = (availableCardMask & (1 << Idx)) != 0;\n                bool bIsUnknown = (unknownPoolMask & (1 << Idx)) != 0;\n                TriadCard card = GetCard(Idx);\n\n                Logger.WriteLine(\"   [\" + Idx + \"]:\" + (card != null ? card.Name.GetCodeName() : \"??\") +\n                    (Idx == swappedCardIdx ? \" (SWAP)\" : bIsUnknown ? \" (U)\" : \"\") +\n                    \" => \" + (bIsAvailable ? \"available\" : \"nope\"));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/gamelogic/TriadDeckOptimizer.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Globalization;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace FFTriadBuddy\n{\n    public class TriadDeckOptimizer\n    {\n        public TriadDeck optimizedDeck;\n\n        private TriadNpc npc;\n        private long numPossibleDecks;\n        private long numTestedDecks;\n        private long numMsElapsed;\n\n        private int numGamesToPlay;\n        private int numPriorityToBuild;\n        private int numCommonToBuild;\n        private int numCommonPctToDropPerPriSlot;\n        private Dictionary<ETriadCardRarity, int> maxSlotsPerRarity;\n        private ETriadCardRarity commonRarity;\n        private int[][] permutationList;\n        private bool bAbort;\n        private bool debugMode;\n\n        public delegate void FoundDeckDelegate(TriadDeck deck, float estWinChance);\n        public delegate void UpdatePossibleCount(string numPossibleDesc);\n        public event FoundDeckDelegate OnFoundDeck;\n\n        public float parallelLoadPct = -1.0f;\n\n        private struct CardPool\n        {\n            public TriadCard[][] priorityLists;\n            public TriadCard[] commonList;\n\n            public int[] deckSlotTypes;\n        }\n        private CardPool currentPool;\n        private TriadGameSolver currentSolver;\n        private bool isOrderImportant;\n\n        private const int DeckSlotCommon = -1;\n        private const int DeckSlotLocked = -2;\n\n        private ManualResetEvent loopPauseEvent = new ManualResetEvent(true);\n        private bool isPaused;\n\n        public TriadDeckOptimizer()\n        {\n            numGamesToPlay = 2000;\n            numPriorityToBuild = 10;\n            numCommonToBuild = 20;\n            numCommonPctToDropPerPriSlot = 10;\n\n            maxSlotsPerRarity = new Dictionary<ETriadCardRarity, int>();\n            maxSlotsPerRarity.Add(ETriadCardRarity.Legendary, 1);\n            maxSlotsPerRarity.Add(ETriadCardRarity.Epic, 2);\n            commonRarity = ETriadCardRarity.Rare;\n\n            debugMode = false;\n            bAbort = false;\n#if DEBUG\n            debugMode = true;\n#endif // DEBUG\n\n            // generate lookup for permutations used when deck order is important\n            // num entries = 5! = 120\n            permutationList = new int[120][];\n            int ListIdx = 0;\n            for (int IdxP0 = 0; IdxP0 < 5; IdxP0++)\n            {\n                for (int IdxP1 = 0; IdxP1 < 5; IdxP1++)\n                {\n                    if (IdxP1 == IdxP0) { continue; }\n                    for (int IdxP2 = 0; IdxP2 < 5; IdxP2++)\n                    {\n                        if (IdxP2 == IdxP0 || IdxP2 == IdxP1) { continue; }\n                        for (int IdxP3 = 0; IdxP3 < 5; IdxP3++)\n                        {\n                            if (IdxP3 == IdxP0 || IdxP3 == IdxP1 || IdxP3 == IdxP2) { continue; }\n                            for (int IdxP4 = 0; IdxP4 < 5; IdxP4++)\n                            {\n                                if (IdxP4 == IdxP0 || IdxP4 == IdxP1 || IdxP4 == IdxP2 || IdxP4 == IdxP3) { continue; }\n\n                                permutationList[ListIdx] = new int[5] { IdxP0, IdxP1, IdxP2, IdxP3, IdxP4 };\n                                ListIdx++;\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        public void Initialize(TriadNpc npc, TriadGameModifier[] regionMods, List<TriadCard> lockedCards)\n        {\n            this.npc = npc;\n            numPossibleDecks = 1;\n            numTestedDecks = 0;\n            numMsElapsed = 0;\n\n            PlayerSettingsDB playerDB = PlayerSettingsDB.Get();\n\n            currentSolver = new TriadGameSolver();\n            currentSolver.InitializeSimulation(npc.Rules, regionMods);\n\n            isOrderImportant = false;\n            foreach (TriadGameModifier mod in currentSolver.simulation.modifiers)\n            {\n                isOrderImportant = isOrderImportant || mod.IsDeckOrderImportant();\n            }\n\n            bool foundCards = FindCardPool(playerDB.ownedCards, currentSolver.simulation.modifiers, lockedCards);\n            if (foundCards)\n            {\n                UpdatePossibleDeckCount();\n            }\n        }\n\n        public Task Process(TriadNpc npc, TriadGameModifier[] regionMods, List<TriadCard> lockedCards)\n        {\n            this.npc = npc;\n            numTestedDecks = 0;\n            numMsElapsed = 0;\n            bAbort = false;\n\n            return Task.Run(() => { FindDecksScored(regionMods, lockedCards); });\n        }\n\n        public void AbortProcess()\n        {\n            bAbort = true;\n        }\n\n        public bool IsAborted()\n        {\n            return bAbort;\n        }\n\n        public void GuessDeck(List<TriadCard> lockedCards)\n        {\n            if (currentPool.commonList == null && currentPool.priorityLists == null)\n            {\n                Logger.WriteLine(\"Skip deck building, everything was locked\");\n\n                optimizedDeck = new TriadDeck(lockedCards);\n            }\n            else\n            {\n                SlotIterator slotIterator = new SlotIterator(currentPool, lockedCards);\n                optimizedDeck = null;\n\n                long skipCounter = 0;\n                long randomSkipRange = numPossibleDecks / 100;\n                if (randomSkipRange > 0)\n                {\n                    var rand = new Random();\n                    skipCounter = rand.Next((int)randomSkipRange);\n\n                    Logger.WriteLine(\"GuessDeck: {0} / {1}\", skipCounter, randomSkipRange);\n                }\n\n                var deckList = slotIterator.GetDecks(skipCounter);\n                foreach (var deckInfo in deckList)\n                {\n                    if (deckInfo.IsValid())\n                    {\n                        optimizedDeck = new TriadDeck(deckInfo.Cards);\n                        break;\n                    }\n                }\n\n                if (optimizedDeck == null)\n                {\n                    optimizedDeck = new TriadDeck(PlayerSettingsDB.Get().starterCards);\n                }\n            }\n        }\n\n        private void UpdatePossibleDeckCount()\n        {\n            numPossibleDecks = 1;\n\n            // common slot loops will avoid repeating the same items, include in num iterations\n            int numCommonSlots = 0;\n            for (int idx = 0; idx < currentPool.deckSlotTypes.Length; idx++)\n            {\n                int slotType = currentPool.deckSlotTypes[idx];\n                if (slotType == DeckSlotCommon)\n                {\n                    numCommonSlots++;\n                }\n                else if (slotType >= 0)\n                {\n                    numPossibleDecks *= currentPool.priorityLists[slotType].Length;\n                }\n            }\n\n            // num combinations without repetition:\n            //  = (pool! / (pool - common)!) / common!\n            //  = (pool * (pool - 1) * .. * (pool - common + 1) * (pool - common)! / (pool - common)!) / common!\n            //  = (pool * (pool - 1) * .. * (pool - common + 1)) / common!\n\n            if (numCommonSlots > 0)\n            {\n                int FactNumCommon = 1;\n                for (int Idx = 0; Idx < numCommonSlots; Idx++)\n                {\n                    numPossibleDecks *= (currentPool.commonList.Length - Idx);\n                    FactNumCommon *= (Idx + 1);\n                }\n\n                numPossibleDecks /= FactNumCommon;\n            }\n        }\n\n        private int GetRandomSeed(int Idx0, int Idx1, int Idx2, int Idx3, int Idx4)\n        {\n            int Hash = 13;\n            Hash = (Hash * 37) + Idx0;\n            Hash = (Hash * 37) + Idx1;\n            Hash = (Hash * 37) + Idx2;\n            Hash = (Hash * 37) + Idx3;\n            Hash = (Hash * 37) + Idx4;\n\n            return Hash;\n        }\n\n        private int GetDeckScore(TriadGameSolver solver, TriadDeck testDeck, int randomSeed, int numGamesDiv)\n        {\n            var agentRandom = new TriadGameAgentRandom(solver, randomSeed);\n            int deckScore = 0;\n\n            int maxGames = (numGamesToPlay / numGamesDiv) / 2;\n            for (int IdxGame = 0; IdxGame < maxGames; IdxGame++)\n            {\n                var gameStateR = solver.StartSimulation(testDeck, npc.Deck, ETriadGameState.InProgressRed);\n                solver.RunSimulation(gameStateR, agentRandom, agentRandom);\n                deckScore += (gameStateR.state == ETriadGameState.BlueWins) ? 2 : (gameStateR.state == ETriadGameState.BlueDraw) ? 1 : 0;\n\n                var gameStateB = solver.StartSimulation(testDeck, npc.Deck, ETriadGameState.InProgressBlue);\n                solver.RunSimulation(gameStateB, agentRandom, agentRandom);\n                deckScore += (gameStateB.state == ETriadGameState.BlueWins) ? 2 : (gameStateB.state == ETriadGameState.BlueDraw) ? 1 : 0;\n            }\n\n            return deckScore;\n        }\n\n        private struct CardScoreData : IComparable<CardScoreData>\n        {\n            public TriadCard card;\n            public float score;\n\n            public int CompareTo(CardScoreData other)\n            {\n                return -score.CompareTo(other.score);\n            }\n\n            public override string ToString()\n            {\n                return card.ToShortCodeString() + \", score: \" + score;\n            }\n        }\n\n        private void ApplyAscentionFilter(List<CardScoreData> commonScoredList, List<List<CardScoreData>> priScoredList)\n        {\n            Func<CardScoreData, float> FindCardAscValue = scoredEntry => scoredEntry.score;\n\n            int maxCardTypes = Enum.GetValues(typeof(ETriadCardType)).Length;\n            int maxLists = priScoredList.Count + 1;\n            List<float>[,] mapCardAscValues = new List<float>[maxCardTypes, maxLists];\n\n            for (int idxL = 0; idxL < priScoredList.Count + 1; idxL++)\n            {\n                for (int idxT = 0; idxT < maxCardTypes; idxT++)\n                {\n                    mapCardAscValues[idxT, idxL] = new List<float>();\n                }\n\n                if (idxL > 0)\n                {\n                    foreach (var scoredEntry in priScoredList[idxL - 1])\n                    {\n                        mapCardAscValues[(int)scoredEntry.card.Type, idxL].Add(FindCardAscValue(scoredEntry));\n                    }\n                }\n            }\n\n            foreach (var scoredEntry in commonScoredList)\n            {\n                if (scoredEntry.card.Type != ETriadCardType.None)\n                {\n                    mapCardAscValues[(int)scoredEntry.card.Type, 0].Add(FindCardAscValue(scoredEntry));\n                }\n            }\n\n            ETriadCardType bestType = ETriadCardType.None;\n            float bestScore = 0;\n\n            if (debugMode) { Logger.WriteLine(\"Ascension filter...\"); }\n            for (int idxT = 0; idxT < maxCardTypes; idxT++)\n            {\n                if (idxT == (int)ETriadCardType.None)\n                {\n                    continue;\n                }\n\n                float[] typePartialScores = new float[maxLists];\n                float typeScore = 0;\n                for (int idxL = 0; idxL < maxLists; idxL++)\n                {\n                    for (int cardIdx = 0; cardIdx < mapCardAscValues[idxT, idxL].Count; cardIdx++)\n                    {\n                        typePartialScores[idxL] += mapCardAscValues[idxT, idxL][cardIdx];\n                    }\n\n                    if (mapCardAscValues[idxT, idxL].Count == 0)\n                    {\n                        typePartialScores[idxL] = 0;\n                    }\n                    else\n                    {\n                        typePartialScores[idxL] /= mapCardAscValues[idxT, idxL].Count;\n                    }\n\n                    typeScore += typePartialScores[idxL];\n                }\n                typeScore /= maxLists;\n\n                if (debugMode) { Logger.WriteLine(\"  [{0}]: score:{1} ({2})\", (ETriadCardType)idxT, typeScore, string.Join(\", \", typePartialScores)); }\n                if (bestScore <= 0.0f || typeScore > bestScore)\n                {\n                    bestScore = typeScore;\n                    bestType = (ETriadCardType)idxT;\n                }\n            }\n\n            if (bestType != ETriadCardType.None)\n            {\n                if (debugMode) { Logger.WriteLine(\"  best: {0}\", bestType); }\n                Action<CardScoreData, ETriadCardType> IncreaseScoreForType = (scoredEntry, cardType) =>\n                {\n                    if (scoredEntry.card.Type == cardType)\n                    {\n                        scoredEntry.score += 1000.0f;\n                    }\n                };\n\n                foreach (var scoredEntry in commonScoredList)\n                {\n                    IncreaseScoreForType(scoredEntry, bestType);\n                }\n\n                foreach (var priList in priScoredList)\n                {\n                    foreach (var scoredEntry in priList)\n                    {\n                        IncreaseScoreForType(scoredEntry, bestType);\n                    }\n                }\n            }\n        }\n\n        private bool FindCardPool(List<TriadCard> allCards, List<TriadGameModifier> modifiers, List<TriadCard> lockedCards)\n        {\n            currentPool = new CardPool();\n\n            int maxRarityNum = Enum.GetValues(typeof(ETriadCardRarity)).Length;\n            int priRarityNum = (int)commonRarity + 1;\n            int[] mapAvailRarity = new int[maxRarityNum];\n\n            var modifiersCopy = new List<TriadGameModifier>();\n            modifiersCopy.AddRange(modifiers);\n\n            int reverseModIdx = modifiersCopy.FindIndex(mod => mod.GetType() == typeof(TriadGameModifierReverse));\n            bool hasReverseMod = reverseModIdx >= 0;\n\n            int ascentionModIdx = modifiersCopy.FindIndex(mod => mod.GetType() == typeof(TriadGameModifierAscention));\n            bool hasAscensionMod = ascentionModIdx >= 0; ;\n\n            int descentionModIdx = modifiersCopy.FindIndex(mod => mod.GetType() == typeof(TriadGameModifierDescention));\n            bool hasDescentionMod = descentionModIdx >= 0; ;\n\n            // special cases:\n            // - reverse: don't include any rare slots if there's enough cards in common list\n            // - reverse + ascention: swap for descention rule to additionally penalize picking cards with type\n            // - reverse + descention: swap for ascention rule to select cards of shared type\n            if (hasReverseMod && hasAscensionMod)\n            {\n                hasAscensionMod = false;\n                modifiersCopy.RemoveAt(ascentionModIdx);\n                modifiersCopy.Add(TriadGameModifierDB.Get().mods.Find(mod => mod.GetType() == typeof(TriadGameModifierDescention)));\n            }\n            else if (hasReverseMod && hasDescentionMod)\n            {\n                hasAscensionMod = true;\n                modifiersCopy.RemoveAt(descentionModIdx);\n                modifiersCopy.Add(TriadGameModifierDB.Get().mods.Find(mod => mod.GetType() == typeof(TriadGameModifierAscention)));\n            }\n\n            // find number of priority lists based on unique rarity limits \n            List<ETriadCardRarity> priRarityThr = new List<ETriadCardRarity>();\n            for (int idxR = priRarityNum; idxR < maxRarityNum; idxR++)\n            {\n                ETriadCardRarity testRarity = (ETriadCardRarity)idxR;\n                if (!hasReverseMod && maxSlotsPerRarity.ContainsKey(testRarity) && maxSlotsPerRarity[testRarity] > 0)\n                {\n                    mapAvailRarity[idxR] = maxSlotsPerRarity[testRarity];\n                    mapAvailRarity[idxR - 1] -= maxSlotsPerRarity[testRarity];\n\n                    priRarityThr.Add(testRarity);\n                }\n            }\n\n            if (debugMode)\n            {\n                Logger.WriteLine(\"FindCardPool> priRarityThr:{0}, maxAvail:[{1},{2},{3},{4},{5}], reverse:{6}, ascention:{7}\", priRarityThr.Count,\n                    mapAvailRarity[0], mapAvailRarity[1], mapAvailRarity[2], mapAvailRarity[3], mapAvailRarity[4],\n                    hasReverseMod, hasAscensionMod);\n            }\n\n            // check rarity of locked cards, eliminate pri list when threshold is matched\n            // when multiple pri rarities are locked, start eliminating from pool above\n            // e.g. 2x 4 star locked => 4 star out, 5 star out\n            currentPool.deckSlotTypes = new int[lockedCards.Count];\n            int numLockedCards = 0;\n\n            for (int idx = 0; idx < lockedCards.Count; idx++)\n            {\n                TriadCard card = lockedCards[idx];\n                if (card != null)\n                {\n                    if (card.Rarity > commonRarity)\n                    {\n                        for (int testR = (int)card.Rarity; testR <= maxRarityNum; testR++)\n                        {\n                            if (mapAvailRarity[testR] > 0)\n                            {\n                                mapAvailRarity[testR]--;\n                                break;\n                            }\n                        }\n                    }\n\n                    currentPool.deckSlotTypes[idx] = DeckSlotLocked;\n                    numLockedCards++;\n                }\n                else\n                {\n                    currentPool.deckSlotTypes[idx] = DeckSlotCommon;\n                }\n            }\n\n            if (debugMode) { Logger.WriteLine(\">> adjusted for locking, numLocked:{0}, maxAvail:[{1},{2},{3},{4},{5}]\", numLockedCards, mapAvailRarity[0], mapAvailRarity[1], mapAvailRarity[2], mapAvailRarity[3], mapAvailRarity[4]); }\n            if (numLockedCards == lockedCards.Count)\n            {\n                return false;\n            }\n\n            List<CardScoreData> commonScoredList = new List<CardScoreData>();\n            List<List<CardScoreData>> priScoredList = new List<List<CardScoreData>>();\n            for (int idxP = 0; idxP < priRarityThr.Count; idxP++)\n            {\n                priScoredList.Add(new List<CardScoreData>());\n            }\n\n            // reverse priority thresholds, idx:0 becomes strongest card\n            priRarityThr.Reverse();\n\n            // assign each owned card to scored lists\n            foreach (TriadCard card in allCards)\n            {\n                if (card == null || !card.IsValid()) { continue; }\n\n                CardScoreData scoredCard = new CardScoreData() { card = card, score = card.OptimizerScore };\n                foreach (TriadGameModifier mod in modifiersCopy)\n                {\n                    mod.OnScoreCard(card, ref scoredCard.score);\n                }\n\n                for (int idxP = 0; idxP < priRarityThr.Count; idxP++)\n                {\n                    if (card.Rarity <= priRarityThr[idxP])\n                    {\n                        priScoredList[idxP].Add(scoredCard);\n                    }\n                }\n\n                if (card.Rarity <= commonRarity)\n                {\n                    commonScoredList.Add(scoredCard);\n                }\n            }\n\n            if (debugMode) { Logger.WriteLine(\">> card lists sorted, common:{0}\", commonScoredList.Count); }\n            bool isPoolValid = (commonScoredList.Count > 0);\n            if (isPoolValid)\n            {\n                int numPriLists = 0;\n                int deckSlotIdx = isOrderImportant ? 1 : 0;\n\n                for (int idx = 0; idx < priScoredList.Count; idx++)\n                {\n                    int numAvail = mapAvailRarity[(int)priRarityThr[idx]];\n                    if (debugMode) { Logger.WriteLine(\"  pri list[{0}]:{1}, rarity:{2}, avail:{3}\", idx, priScoredList[idx].Count, priRarityThr[idx], numAvail); }\n                    if ((numAvail > 0) && (priScoredList[idx].Count > 0))\n                    {\n                        // initial deckSlotIdx should be already past only available spot (e.g. all slots but [0] are locked), make sure to wrap around\n                        // find fist available Common slot to overwrite with priority list, repeat numAvail times\n                        for (int idxAvail = 0; idxAvail < numAvail; idxAvail++)\n                        {\n                            for (int idxD = 0; idxD < currentPool.deckSlotTypes.Length; idxD++)\n                            {\n                                if (currentPool.deckSlotTypes[deckSlotIdx] == DeckSlotCommon)\n                                {\n                                    break;\n                                }\n\n                                deckSlotIdx++;\n                            }\n\n                            currentPool.deckSlotTypes[deckSlotIdx] = numPriLists;\n                        }\n\n                        numPriLists++;\n                    }\n                    else\n                    {\n                        priScoredList[idx].Clear();\n                    }\n                }\n\n                // ascension modifier special case: same type across all pools is best\n                // aply after priority lists were trimmed\n                if (hasAscensionMod)\n                {\n                    ApplyAscentionFilter(commonScoredList, priScoredList);\n                }\n\n                if (numPriLists > 0)\n                {\n                    currentPool.priorityLists = new TriadCard[numPriLists][];\n                    if (debugMode) { Logger.WriteLine(\">> num priority lists:{0}\", numPriLists); }\n\n                    int idxP = 0;\n                    for (int idxL = 0; idxL < priScoredList.Count; idxL++)\n                    {\n                        int maxPriorityToUse = Math.Min(numPriorityToBuild, priScoredList[idxL].Count);\n                        if (maxPriorityToUse > 0)\n                        {\n                            currentPool.priorityLists[idxP] = new TriadCard[maxPriorityToUse];\n                            priScoredList[idxL].Sort();\n\n                            for (int idxC = 0; idxC < maxPriorityToUse; idxC++)\n                            {\n                                currentPool.priorityLists[idxP][idxC] = priScoredList[idxL][idxC].card;\n                            }\n\n                            idxP++;\n                        }\n                    }\n                }\n\n                // adjust pool of common cards based on avail common slots\n                // - all common: use requested size\n                // - scale down 20% per every priority list slot \n                int numPriSlots = 0;\n                for (int idx = 0; idx < currentPool.deckSlotTypes.Length; idx++)\n                {\n                    numPriSlots += (currentPool.deckSlotTypes[idx] >= 0) ? 1 : 0;\n                }\n\n                int maxCommonToUse = Math.Min(numCommonToBuild - (numCommonToBuild * numPriSlots * numCommonPctToDropPerPriSlot / 100), commonScoredList.Count);\n                if (debugMode) { Logger.WriteLine(\">> adjusting common pool based on priSlots:{0} and drop:{1}% => {2}\", numPriSlots, numCommonPctToDropPerPriSlot, maxCommonToUse); }\n\n                currentPool.commonList = new TriadCard[maxCommonToUse];\n                commonScoredList.Sort();\n\n                for (int idx = 0; idx < currentPool.commonList.Length; idx++)\n                {\n                    currentPool.commonList[idx] = commonScoredList[idx].card;\n                }\n            }\n\n            if (debugMode) { Logger.WriteLine(\">> deck slot types:[{0}, {1}, {2}, {3}, {4}]\", currentPool.deckSlotTypes[0], currentPool.deckSlotTypes[1], currentPool.deckSlotTypes[2], currentPool.deckSlotTypes[3], currentPool.deckSlotTypes[4]); }\n            return isPoolValid;\n        }\n\n        private class SlotIterator\n        {\n            public const int numSlots = 5;\n            public TriadCard[][] slotLists = new TriadCard[numSlots][];\n            private bool[] isSlotCommon = new bool[numSlots];\n\n            public struct ItemInfo\n            {\n                public int Idx0;\n                public int Idx1;\n                public int Idx2;\n                public int Idx3;\n                public int Idx4;\n                public TriadCard[] Cards;\n\n                public ItemInfo(int idx0, int idx1, int idx2, int idx3, int idx4, SlotIterator iterator)\n                {\n                    Idx0 = idx0;\n                    Idx1 = idx1;\n                    Idx2 = idx2;\n                    Idx3 = idx3;\n                    Idx4 = idx4;\n                    Cards = new TriadCard[5] { iterator.slotLists[0][Idx0], iterator.slotLists[1][Idx1], iterator.slotLists[2][Idx2], iterator.slotLists[3][Idx3], iterator.slotLists[4][Idx4] };\n                }\n\n                public bool IsValid()\n                {\n                    return (Cards[0] != Cards[1]) && (Cards[0] != Cards[2]) && (Cards[0] != Cards[3]) && (Cards[0] != Cards[4]) &&\n                        (Cards[1] != Cards[2]) && (Cards[1] != Cards[3]) && (Cards[1] != Cards[4]) &&\n                        (Cards[2] != Cards[3]) && (Cards[2] != Cards[4]) &&\n                        (Cards[3] != Cards[4]);\n                }\n            }\n\n            public SlotIterator(CardPool cardPool, List<TriadCard> lockedCards)\n            {\n                for (int idx = 0; idx < numSlots; idx++)\n                {\n                    slotLists[idx] =\n                        (cardPool.deckSlotTypes[idx] == DeckSlotCommon) ? cardPool.commonList :\n                        (cardPool.deckSlotTypes[idx] >= 0) ? cardPool.priorityLists[cardPool.deckSlotTypes[idx]] :\n                        new TriadCard[1] { lockedCards[idx] };\n\n                    isSlotCommon[idx] = (cardPool.deckSlotTypes[idx] == DeckSlotCommon);\n                }\n            }\n\n            private int FindLoopStart(int SlotIdx, int IdxS0, int IdxS1, int IdxS2, int IdxS3)\n            {\n                if (!isSlotCommon[SlotIdx]) { return 0; }\n\n                if (SlotIdx >= 4 && isSlotCommon[3]) { return IdxS3 + 1; }\n                if (SlotIdx >= 3 && isSlotCommon[2]) { return IdxS2 + 1; }\n                if (SlotIdx >= 2 && isSlotCommon[1]) { return IdxS1 + 1; }\n                if (SlotIdx >= 1 && isSlotCommon[0]) { return IdxS0 + 1; }\n\n                return 0;\n            }\n\n            public IEnumerable<ItemInfo> GetDecks(long skipIdx)\n            {\n                long skipCounter = skipIdx;\n                for (int IdxS0 = 0; IdxS0 < slotLists[0].Length; IdxS0++)\n                {\n                    int startS1 = FindLoopStart(1, IdxS0, -1, -1, -1);\n                    for (int IdxS1 = startS1; IdxS1 < slotLists[1].Length; IdxS1++)\n                    {\n                        int startS2 = FindLoopStart(2, IdxS0, IdxS1, -1, -1);\n                        for (int IdxS2 = startS2; IdxS2 < slotLists[2].Length; IdxS2++)\n                        {\n                            int startS3 = FindLoopStart(3, IdxS0, IdxS1, IdxS2, -1);\n                            for (int IdxS3 = startS3; IdxS3 < slotLists[3].Length; IdxS3++)\n                            {\n                                int startS4 = FindLoopStart(4, IdxS0, IdxS1, IdxS2, IdxS3);\n                                for (int IdxS4 = startS4; IdxS4 < slotLists[4].Length; IdxS4++)\n                                {\n                                    // i'm lazy.\n                                    if (skipCounter > 0)\n                                    {\n                                        skipCounter--;\n                                        continue;\n                                    }\n\n                                    yield return new ItemInfo(IdxS0, IdxS1, IdxS2, IdxS3, IdxS4, this);\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        private void FindDecksScored(TriadGameModifier[] regionMods, List<TriadCard> lockedCards)\n        {\n            Stopwatch stopwatch = new Stopwatch();\n            stopwatch.Start();\n\n            if (currentPool.commonList == null && currentPool.priorityLists == null)\n            {\n                stopwatch.Stop();\n                Logger.WriteLine(\"Skip deck building, everything was locked\");\n\n                optimizedDeck = new TriadDeck(lockedCards);\n                return;\n            }\n\n            object lockOb = new object();\n            int bestScore = 0;\n            TriadDeck bestDeck = new TriadDeck(PlayerSettingsDB.Get().starterCards);\n\n            // no more flexible slot count after this point => loop land\n            SlotIterator slotIterator = new SlotIterator(currentPool, lockedCards);\n            long lowestPauseIdx = 0;\n            bool canFinishLoop = false;\n\n            ParallelOptions options = new ParallelOptions();\n            if (parallelLoadPct > 0.0f)\n            {\n                options.MaxDegreeOfParallelism = Math.Max(1, (int)(Environment.ProcessorCount * parallelLoadPct));\n                Logger.WriteLine(\"MaxDegreeOfParallelism: {0}\", options.MaxDegreeOfParallelism);\n            }\n\n            do\n            {\n                var loopResult = Parallel.ForEach(slotIterator.GetDecks(lowestPauseIdx), options, (deckInfo, state) =>\n                {\n                    if (isPaused) { state.Break(); }\n                    else if (bAbort) { state.Stop(); }\n\n                    if (deckInfo.IsValid())\n                    {\n                        int randomSeed = GetRandomSeed(deckInfo.Idx0, deckInfo.Idx1, deckInfo.Idx2, deckInfo.Idx3, deckInfo.Idx4);\n                        // TODO: custom permutation lookup\n                        {\n                            TriadDeck testDeck = new TriadDeck(deckInfo.Cards);\n                            int testScore = GetDeckScore(currentSolver, testDeck, randomSeed, 1);\n                            if (testScore > bestScore)\n                            {\n                                lock (lockOb)\n                                {\n                                    bestScore = testScore;\n                                    bestDeck = testDeck;\n\n                                    // score: num games * (2 if win, 1 if draw, 0 if lose)\n                                    // max score = 100% win = num games * 2\n                                    float estWinChance = 1.0f * testScore / (numGamesToPlay * 2);\n                                    OnFoundDeck.Invoke(testDeck, estWinChance);\n                                }\n                            }\n                        }\n\n                        Interlocked.Increment(ref numTestedDecks);\n                    }\n                });\n\n                if (isPaused)\n                {\n                    loopPauseEvent.WaitOne();\n                    lowestPauseIdx += loopResult.LowestBreakIteration ?? 0;\n                    Interlocked.Exchange(ref numTestedDecks, Math.Min(lowestPauseIdx, numPossibleDecks));\n                }\n\n                canFinishLoop = bAbort || loopResult.IsCompleted || loopResult.LowestBreakIteration == null;\n\n            } while (!canFinishLoop);\n\n            stopwatch.Stop();\n            Logger.WriteLine(\"Building list of decks: \" + stopwatch.ElapsedMilliseconds + \"ms, num:\" + numPossibleDecks);\n            optimizedDeck = bestDeck;\n        }\n\n        public int GetProgress()\n        {\n            if (numPossibleDecks > 0)\n            {\n                string desc = (100 * Interlocked.Read(ref numTestedDecks) / numPossibleDecks).ToString();\n                int progressPct = int.Parse(desc);\n                return Math.Max(0, Math.Min(100, progressPct));\n            }\n\n            return 0;\n        }\n\n        public string GetNumTestedDesc()\n        {\n            return Interlocked.Read(ref numTestedDecks).ToString(\"N0\", CultureInfo.InvariantCulture);\n        }\n\n        public string GetNumPossibleDecksDesc()\n        {\n            return numPossibleDecks.ToString(\"N0\", CultureInfo.InvariantCulture);\n        }\n\n        public int GetSecondsRemaining(int ElapsedMs)\n        {\n            int numSeconds = int.MaxValue;\n            numMsElapsed += ElapsedMs;\n\n            long numTestedDecksSafe = Interlocked.Read(ref numTestedDecks);\n            long numTestedPerMs = numTestedDecksSafe / numMsElapsed;\n            long numMsPerTest = (numTestedDecksSafe == 0) ? 1 : (numMsElapsed / numTestedDecksSafe);\n            long numTestsRemaning = numPossibleDecks - numTestedDecksSafe;\n\n            long numSecRemaning = (numTestedPerMs > 0) ?\n                ((numTestsRemaning / numTestedPerMs) / 1000) :\n                ((numTestsRemaning * numMsPerTest) / 1000);\n\n            string numIntervalsDesc = numSecRemaning.ToString();\n            int.TryParse(numIntervalsDesc, out numSeconds);\n\n            return Math.Max(0, numSeconds);\n        }\n\n        public void SetPaused(bool wantsPaused)\n        {\n            if (wantsPaused && !isPaused)\n            {\n                loopPauseEvent.Reset();\n                isPaused = true;\n            }\n            else if (!wantsPaused && isPaused)\n            {\n                isPaused = false;\n                loopPauseEvent.Set();\n            }\n        }\n\n        private const float optimizerScoreAvgSides = 1.0f;\n        private const float optimizerScoreMaxSides = 0.75f;\n        private const float optimizerScoreRarity = 0.2f;\n        private const float optimizerMaxScore = optimizerScoreAvgSides + optimizerScoreMaxSides + optimizerScoreRarity;\n\n        public static float GetCardScore(TriadCard card)\n        {\n            // try to guess how good card will perform\n            // - avg of sides\n            // - max of sides\n            // - rarity (should be reflected by sides already)\n\n            int numberMax = Math.Max(Math.Max(card.Sides[0], card.Sides[1]), Math.Max(card.Sides[2], card.Sides[3]));\n            int numberSum = card.Sides[0] + card.Sides[1] + card.Sides[2] + card.Sides[3];\n            float numberAvg = numberSum / 4.0f;\n\n            float cardScore =\n                ((numberAvg / 10.0f) * optimizerScoreAvgSides) +\n                ((numberMax / 10.0f) * optimizerScoreMaxSides) +\n                (((int)card.Rarity / (float)ETriadCardRarity.Legendary) * optimizerScoreRarity);\n\n            return cardScore / optimizerMaxScore;\n        }\n    }\n}\n"
  },
  {
    "path": "sources/gamelogic/TriadGameAgent.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System;\nusing System.Diagnostics;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace FFTriadBuddy\n{\n    public abstract class TriadGameAgent\n    {\n        [Flags]\n        public enum DebugFlags\n        {\n            None = 0,\n            AgentInitialize = 0x1,\n            ShowMoveResult = 0x2,\n            ShowMoveStart = 0x4,\n            ShowMoveDetails = 0x8,\n            ShowMoveDetailsRng = 0x10,\n        }\n        public DebugFlags debugFlags;\n        public string agentName = \"??\";\n\n        public virtual void Initialize(TriadGameSolver solver, int sessionSeed) { }\n        public virtual bool IsInitialized() { return true; }\n        public virtual float GetProgress() { return 0.0f; }\n        public virtual void OnSimulationStart() { }\n\n        public abstract bool FindNextMove(TriadGameSolver solver, TriadGameSimulationState gameState, out int cardIdx, out int boardPos, out SolverResult solverResult);\n    }\n\n    /// <summary>\n    /// Random pick from all possible actions \n    /// </summary>\n    public class TriadGameAgentRandom : TriadGameAgent\n    {\n        public static bool UseEqualDistribution = false;\n        private Random randGen;\n\n        public TriadGameAgentRandom() { }\n        public TriadGameAgentRandom(TriadGameSolver solver, int sessionSeed)\n        {\n            Initialize(solver, sessionSeed);\n        }\n\n        public override void Initialize(TriadGameSolver solver, int sessionSeed)\n        {\n            randGen = new Random(sessionSeed);\n            agentName = \"Random\";\n        }\n\n        public override bool IsInitialized()\n        {\n            return randGen != null;\n        }\n\n        public override bool FindNextMove(TriadGameSolver solver, TriadGameSimulationState gameState, out int cardIdx, out int boardPos, out SolverResult solverResult)\n        {\n#if DEBUG\n            if ((debugFlags & DebugFlags.ShowMoveStart) != DebugFlags.None) { Logger.WriteLine($\"FindNextMove, numPlaced:{gameState.numCardsPlaced}\"); }\n#endif // DEBUG\n\n            cardIdx = -1;\n            boardPos = -1;\n            solverResult = SolverResult.Zero;\n\n            if (!IsInitialized())\n            {\n                return false;\n            }\n\n            if (UseEqualDistribution)\n            {\n                // proper solution, but ends up lowering initial win chance by A LOT\n\n                solver.FindAvailableActions(gameState, out int availBoardMask, out int numAvailBoard, out int availCardsMask, out int numAvailCards);\n                if (numAvailCards > 0 && numAvailBoard > 0)\n                {\n                    cardIdx = PickBitmaskIndex(availCardsMask, numAvailCards);\n                    boardPos = PickBitmaskIndex(availBoardMask, numAvailBoard);\n                }\n            }\n            else\n            {\n                // OLD IMPLEMENTATION for comparison\n                // doesn't guarantee equal distribution = opponent simulation is biased => reported win chance is too high\n                // stays for now until i can make CarloScored usable\n                //\n                const int boardPosMax = TriadGameSimulationState.boardSizeSq;\n                if (gameState.numCardsPlaced < TriadGameSimulationState.boardSizeSq)\n                {\n                    int testPos = randGen.Next(boardPosMax);\n                    for (int passIdx = 0; passIdx < boardPosMax; passIdx++)\n                    {\n                        testPos = (testPos + 1) % boardPosMax;\n                        if (gameState.board[testPos] == null)\n                        {\n                            boardPos = testPos;\n                            break;\n                        }\n                    }\n                }\n\n                cardIdx = -1;\n                TriadDeckInstance useDeck = (gameState.state == ETriadGameState.InProgressBlue) ? gameState.deckBlue : gameState.deckRed;\n                if (useDeck.availableCardMask > 0)\n                {\n                    int testIdx = randGen.Next(TriadDeckInstance.maxAvailableCards);\n                    for (int passIdx = 0; passIdx < TriadDeckInstance.maxAvailableCards; passIdx++)\n                    {\n                        testIdx = (testIdx + 1) % TriadDeckInstance.maxAvailableCards;\n                        if ((useDeck.availableCardMask & (1 << testIdx)) != 0)\n                        {\n                            cardIdx = testIdx;\n                            break;\n                        }\n                    }\n                }\n            }\n\n            return (boardPos >= 0) && (cardIdx >= 0);\n        }\n\n        protected int PickBitmaskIndex(int mask, int numSet)\n        {\n            int stepIdx = randGen.Next(numSet);\n            return PickRandomBitFromMask(mask, stepIdx);\n        }\n\n        public static int PickRandomBitFromMask(int mask, int randStep)\n        {\n            int bitIdx = 0;\n            int testMask = 1 << bitIdx;\n            while (testMask <= mask)\n            {\n                if ((testMask & mask) != 0)\n                {\n                    randStep--;\n                    if (randStep < 0)\n                    {\n                        return bitIdx;\n                    }\n                }\n\n                bitIdx++;\n                testMask <<= 1;\n            }\n\n#if DEBUG\n            // more bits set than mask allows?\n            Debugger.Break();\n#endif\n            return -1;\n        }\n    }\n\n    /// <summary>\n    /// Base class for agents recursively exploring action graph\n    /// </summary>\n    public abstract class TriadGameAgentGraphExplorer : TriadGameAgent\n    {\n        protected float currentProgress = 0;\n        protected int sessionSeed = 0;\n        private Random failsafeRandStream = null;\n\n        public override float GetProgress()\n        {\n            return currentProgress;\n        }\n\n        public override void Initialize(TriadGameSolver solver, int sessionSeed)\n        {\n            this.sessionSeed = sessionSeed;\n        }\n\n        public override bool FindNextMove(TriadGameSolver solver, TriadGameSimulationState gameState, out int cardIdx, out int boardPos, out SolverResult solverResult)\n        {\n#if DEBUG\n            if ((debugFlags & DebugFlags.ShowMoveStart) != DebugFlags.None) { Logger.WriteLine($\"FindNextMove, numPlaced:{gameState.numCardsPlaced}\"); }\n#endif // DEBUG\n\n            cardIdx = -1;\n            boardPos = -1;\n\n            bool isFinished = IsFinished(gameState, out solverResult);\n            if (!isFinished && IsInitialized())\n            {\n                _ = SearchActionSpace(solver, gameState, 0, out cardIdx, out boardPos, out solverResult);\n            }\n\n            return (cardIdx >= 0) && (boardPos >= 0);\n        }\n\n        protected bool IsFinished(TriadGameSimulationState gameState, out SolverResult gameResult)\n        {\n            // end game conditions, owner always fixed as blue\n            switch (gameState.state)\n            {\n                case ETriadGameState.BlueWins:\n                    gameResult = new SolverResult(1, 0, 1);\n                    return true;\n\n                case ETriadGameState.BlueDraw:\n                    gameResult = new SolverResult(0, 1, 1);\n                    return true;\n\n                case ETriadGameState.BlueLost:\n                    gameResult = new SolverResult(0, 0, 1);\n                    return true;\n\n                default: break;\n            }\n\n            gameResult = SolverResult.Zero;\n            return false;\n        }\n\n        protected virtual SolverResult SearchActionSpace(TriadGameSolver solver, TriadGameSimulationState gameState, int searchLevel, out int bestCardIdx, out int bestBoardPos, out SolverResult bestActionResult)\n        {\n            // don't check finish condition at start! \n            // this is done before caling this function (from FindNextMove / recursive), so it doesn't have to be duplicated in every derrived class\n\n            bestCardIdx = -1;\n            bestBoardPos = -1;\n            bestActionResult = SolverResult.Zero;\n\n            // game in progress, explore actions\n            bool isRootLevel = searchLevel == 0;\n            if (isRootLevel)\n            {\n                currentProgress = 0.0f;\n            }\n\n            float numWinsTotal = 0;\n            float numDrawsTotal = 0;\n            long numGamesTotal = 0;\n\n            solver.FindAvailableActions(gameState, out int availBoardMask, out int numAvailBoard, out int availCardsMask, out int numAvailCards);\n            if (numAvailCards > 0 && numAvailBoard > 0)\n            {\n                var turnOwner = (gameState.state == ETriadGameState.InProgressBlue) ? ETriadCardOwner.Blue : ETriadCardOwner.Red;\n                int cardProgressCounter = 0;\n                bool hasValidPlacements = false;\n\n                for (int cardIdx = 0; cardIdx < TriadDeckInstance.maxAvailableCards; cardIdx++)\n                {\n                    bool cardNotAvailable = (availCardsMask & (1 << cardIdx)) == 0;\n                    if (cardNotAvailable)\n                    {\n                        continue;\n                    }\n\n                    if (isRootLevel)\n                    {\n                        currentProgress = 1.0f * cardProgressCounter / numAvailCards;\n                        cardProgressCounter++;\n                    }\n\n                    for (int boardIdx = 0; boardIdx < gameState.board.Length; boardIdx++)\n                    {\n                        bool boardNotAvailable = (availBoardMask & (1 << boardIdx)) == 0;\n                        if (boardNotAvailable)\n                        {\n                            continue;\n                        }\n\n                        var gameStateCopy = new TriadGameSimulationState(gameState);\n                        var useDeck = (gameStateCopy.state == ETriadGameState.InProgressBlue) ? gameStateCopy.deckBlue : gameStateCopy.deckRed;\n\n                        bool isPlaced = solver.simulation.PlaceCard(gameStateCopy, cardIdx, useDeck, turnOwner, boardIdx);\n                        if (isPlaced)\n                        {\n                            // check if finished before going deeper\n                            bool isFinished = IsFinished(gameStateCopy, out var branchResult);\n                            if (!isFinished)\n                            {\n                                gameStateCopy.forcedCardIdx = -1;\n                                branchResult = SearchActionSpace(solver, gameStateCopy, searchLevel + 1, out _, out _, out _);\n                            }\n\n#if DEBUG\n                            if ((debugFlags & DebugFlags.ShowMoveDetails) != DebugFlags.None && isRootLevel) { Logger.WriteLine($\"  board[{boardIdx}], card[{cardIdx}] = {branchResult}\"); }\n#endif // DEBUG\n                            if (branchResult.IsBetterThan(bestActionResult) || !hasValidPlacements)\n                            {\n                                bestActionResult = branchResult;\n                                bestCardIdx = cardIdx;\n                                bestBoardPos = boardIdx;\n                            }\n\n                            numWinsTotal += branchResult.numWins;\n                            numDrawsTotal += branchResult.numDraws;\n                            numGamesTotal += branchResult.numGames;\n                            hasValidPlacements = true;\n                        }\n                    }\n                }\n\n                if (!hasValidPlacements)\n                {\n                    // failsafe in case simulation runs into any issues\n                    if (failsafeRandStream == null)\n                    {\n                        failsafeRandStream = new Random(sessionSeed);\n                    }\n\n                    bestCardIdx = TriadGameAgentRandom.PickRandomBitFromMask(availCardsMask, failsafeRandStream.Next(numAvailCards));\n                    bestBoardPos = TriadGameAgentRandom.PickRandomBitFromMask(availBoardMask, failsafeRandStream.Next(numAvailBoard));\n                }\n\n#if DEBUG\n                if ((debugFlags & DebugFlags.ShowMoveResult) != DebugFlags.None && isRootLevel)\n                {\n                    string namePrefix = string.IsNullOrEmpty(solver.name) ? \"\" : (\"[\" + solver.name + \"] \");\n                    Logger.WriteLine(\"{0}Solver {11}win:{1:P2} (draw:{2:P2}), blue[{3}], red[{4}], turn:{5}, availBoard:{6} ({7:x}), availCards:{8} ({9}:{10:x})\",\n                        namePrefix,\n                        bestActionResult.winChance, bestActionResult.drawChance,\n                        gameState.deckBlue, gameState.deckRed, turnOwner,\n                        numAvailBoard, availBoardMask,\n                        numAvailCards, gameState.state == ETriadGameState.InProgressBlue ? \"B\" : \"R\", availCardsMask,\n                        hasValidPlacements ? \"[FAILSAFE] \" : \"\");\n                }\n#endif // DEBUG\n            }\n            else\n            {\n#if DEBUG\n                if ((debugFlags & DebugFlags.ShowMoveResult) != DebugFlags.None && isRootLevel)\n                {\n                    string namePrefix = string.IsNullOrEmpty(solver.name) ? \"\" : (\"[\" + solver.name + \"] \");\n                    Logger.WriteLine(\"{0}Can't find move! availBoard:{1} ({2:x}), availCards:{3} ({4}:{5:x})\",\n                        namePrefix,\n                        numAvailBoard, availBoardMask,\n                        numAvailCards, gameState.state == ETriadGameState.InProgressBlue ? \"B\" : \"R\", availCardsMask);\n                }\n#endif // DEBUG\n            }\n\n            // what to do with results depend on current move owner:\n            //   Agent's player (search levels: 0, 2, 4, ...)\n            //   - result of processing this level is MAX(result branch)\n            //\n            //   opponent player (search levels: 1, 3, ...)\n            //   - min/max says MIN, but let's go with AVG instead to be more optimistic\n            //   - result of processing this level is AVG, use total counters to create chance data\n\n            bool isOwnerTurn = (searchLevel % 2) == 0;\n            return isOwnerTurn ? bestActionResult : new SolverResult(numWinsTotal, numDrawsTotal, numGamesTotal);\n        }\n    }\n\n    /// <summary>\n    /// Single level MCTS, each available action spins 2000 random games and best one is selected \n    /// </summary>\n    public class TriadGameAgentDerpyCarlo : TriadGameAgentGraphExplorer\n    {\n        protected int numWorkers = 2000;\n        protected TriadGameAgentRandom[] workerAgents;\n\n        public override void Initialize(TriadGameSolver solver, int sessionSeed)\n        {\n            base.Initialize(solver, sessionSeed);\n            agentName = \"DerpyCarlo\";\n\n            // initialize all random streams just once, it's enough for seeing and having unique stream for each worker\n            workerAgents = new TriadGameAgentRandom[numWorkers];\n            for (int idx = 0; idx < numWorkers; idx++)\n            {\n                workerAgents[idx] = new TriadGameAgentRandom(solver, sessionSeed + idx);\n            }\n        }\n\n        public override bool IsInitialized()\n        {\n            return workerAgents != null;\n        }\n\n        protected override SolverResult SearchActionSpace(TriadGameSolver solver, TriadGameSimulationState gameState, int searchLevel, out int bestCardIdx, out int bestBoardPos, out SolverResult bestActionResult)\n        {\n            bool runWorkers = CanRunRandomExploration(solver, gameState, searchLevel);\n            if (runWorkers)\n            {\n                bestCardIdx = -1;\n                bestBoardPos = -1;\n                bestActionResult = FindWinningProbability(solver, gameState);\n#if DEBUG\n                if ((debugFlags & DebugFlags.ShowMoveDetailsRng) != DebugFlags.None)\n                {\n                    Logger.WriteLine($\"level:{searchLevel}, numPlaced:{gameState.numCardsPlaced} => random workers:{bestActionResult}\");\n                }\n#endif // DEBUG\n\n                return bestActionResult;\n            }\n\n            var result = base.SearchActionSpace(solver, gameState, searchLevel, out bestCardIdx, out bestBoardPos, out bestActionResult);\n#if DEBUG\n            if ((debugFlags & DebugFlags.ShowMoveDetails) != DebugFlags.None && searchLevel == 0)\n            {\n                Logger.WriteLine($\"level:{searchLevel}, numPlaced:{gameState.numCardsPlaced} => result:{bestActionResult}\");\n            }\n#endif // DEBUG\n            return result;\n        }\n\n        protected virtual bool CanRunRandomExploration(TriadGameSolver solver, TriadGameSimulationState gameState, int searchLevel)\n        {\n            return searchLevel > 0;\n        }\n\n        protected virtual SolverResult FindWinningProbability(TriadGameSolver solver, TriadGameSimulationState gameState)\n        {\n            int numWinningWorkers = 0;\n            int numDrawingWorkers = 0;\n\n            _ = Parallel.For(0, numWorkers, workerIdx =>\n            //for (int workerIdx = 0; workerIdx < solverWorkers; workerIdx++)\n            {\n                var gameStateCopy = new TriadGameSimulationState(gameState);\n                var agent = workerAgents[workerIdx];\n\n                solver.RunSimulation(gameStateCopy, agent, agent);\n\n                if (gameStateCopy.state == ETriadGameState.BlueWins)\n                {\n                    _ = Interlocked.Add(ref numWinningWorkers, 1);\n                }\n                else if (gameStateCopy.state == ETriadGameState.BlueDraw)\n                {\n                    _ = Interlocked.Add(ref numDrawingWorkers, 1);\n                }\n            });\n\n            // return normalized score so it can be compared \n            return new SolverResult(1.0f * numWinningWorkers / numWorkers, 1.0f * numDrawingWorkers / numWorkers, 1);\n        }\n    }\n\n    /// <summary>\n    /// Switches between derpy MCTS and full exploration depending on size of game space \n    /// </summary>\n    public class TriadGameAgentCarloTheExplorer : TriadGameAgentDerpyCarlo\n    {\n        // 10k seems to be sweet spot\n        // - 1k: similar time, lower accuracy\n        // - 100k: 8x longer, similar accuracy\n        public const long MaxStatesToExplore = 10 * 1000;\n\n        private int minPlacedToExplore = 10;\n        private int minPlacedToExploreWithForced = 10;\n\n        public override void Initialize(TriadGameSolver solver, int sessionSeed)\n        {\n            base.Initialize(solver, sessionSeed);\n            agentName = \"CarloTheExplorer\";\n\n            // cache number of possible states depending on cards placed\n            // 0: (5 * 9) * (5 * 8) * (4 * 7) * (4 * 6) * ...                   = (5 * 5 * 4 * 4 * 3 * 3 * 2 * 2 * 1) * 9! = (5! * 5!) * 9!\n            // 1: (5 * 8) * (4 * 7) * (4 * 6) * ...                             = (5 * 4 * 4 * 3 * 3 * 2 * 2 * 1) * 8!     = (4! * 5!) * 8!\n            // ...\n            // 6: (2 * 3) * (1 * 2) * (1 * 1)\n            // 7: (1 * 2) * (1 * 1)\n            // 8: (1 * 1)\n            // 9: 0\n            //\n            // num states = num board positions * num cards, \n            // - board(num placed) => x == 0 ? 0 : x!\n            // - card(num placed) => forced ? 1 : ((x + 2) / 2)! * ((x + 1) / 2)!\n\n            long numStatesForced = 1;\n            long numStates = 1;\n\n            const int maxToPlace = TriadGameSimulationState.boardSizeSq;\n            for (int numToPlace = 1; numToPlace <= maxToPlace; numToPlace++)\n            {\n                int numPlaced = maxToPlace - numToPlace;\n\n                numStatesForced *= numToPlace;\n                if (numStatesForced <= MaxStatesToExplore)\n                {\n                    minPlacedToExploreWithForced = numPlaced;\n                }\n\n                numStates *= numToPlace * ((numToPlace + 2) / 2) * ((numToPlace + 1) / 2);\n                if (numStates <= MaxStatesToExplore)\n                {\n                    minPlacedToExplore = numPlaced;\n                }\n            }\n\n#if DEBUG\n            if ((debugFlags & DebugFlags.AgentInitialize) != DebugFlags.None) { Logger.WriteLine($\"{agentName}: minPlacedToExplore:{minPlacedToExplore}, minPlacedToExploreWithForced:{minPlacedToExploreWithForced}\"); }\n#endif // DEBUG\n        }\n\n        protected override bool CanRunRandomExploration(TriadGameSolver solver, TriadGameSimulationState gameState, int searchLevel)\n        {\n            int numPlacedThr = (gameState.forcedCardIdx < 0) ? minPlacedToExplore : minPlacedToExploreWithForced;\n\n            return (searchLevel > 0) && (gameState.numCardsPlaced < numPlacedThr);\n        }\n    }\n\n    /// <summary>\n    /// Aguments random search phase with score of game state to increase diffs between probability of initial steps\n    /// </summary>\n    public class TriadGameAgentCarloScored : TriadGameAgentCarloTheExplorer\n    {\n        public const float StateWeight = 0.75f;\n        public const float StateWeightDecay = 0.25f;\n\n        public const float PriorityDefense = 1.0f;\n        public const float PriorityDeck = 2.0f;\n        public const float PriorityCapture = 3.5f;\n\n        public override void Initialize(TriadGameSolver solver, int sessionSeed)\n        {\n            base.Initialize(solver, sessionSeed);\n            agentName = \"CarloScored\";\n        }\n\n        protected override SolverResult FindWinningProbability(TriadGameSolver solver, TriadGameSimulationState gameState)\n        {\n            var result = base.FindWinningProbability(solver, gameState);\n            var stateScore = CalculateStateScore(solver, gameState);\n            var useWeight = Math.Max(0.0f, StateWeight - ((gameState.deckBlue.numPlaced - 1) * StateWeightDecay));\n\n            var numWinsModified = ((result.numWins / result.numGames) * (1.0f - useWeight)) + (stateScore * useWeight);\n            return new SolverResult(Math.Min(1.0f, numWinsModified), result.numDraws / result.numGames, 1);\n        }\n\n        public float CalculateStateScore(TriadGameSolver solver, TriadGameSimulationState gameState)\n        {\n            var (blueDefenseScore, blueCaptureScore) = CalculateBoardScore(solver, gameState);\n            var deckScore = CalculateBlueDeckScore(solver, gameState);\n\n#if DEBUG\n            if ((debugFlags & DebugFlags.ShowMoveDetailsRng) != DebugFlags.None)\n            {\n                Logger.WriteLine($\"stateScore => def:{blueDefenseScore}, capture:{blueCaptureScore}, deck:{deckScore}\");\n            }\n#endif // DEBUG\n\n            return ((blueDefenseScore * PriorityDefense) + (blueCaptureScore * PriorityCapture) + (deckScore * PriorityDeck)) / (PriorityDefense + PriorityDeck + PriorityCapture);\n        }\n\n        private (float, float) CalculateBoardScore(TriadGameSolver solver, TriadGameSimulationState gameState)\n        {\n            // for each blue card:\n            //   for each side:\n            //     find all numbers that can capture it\n            //   normalize count of capturing numbers\n            // normalize card capturing value\n            // inverse => blue cards defensive value\n            //\n            // pct of blue in all cards => capture score\n\n            float capturingSum = 0.0f;\n            int numBlueCards = 0;\n\n            for (int idx = 0; idx < gameState.board.Length; idx++)\n            {\n                var cardInst = gameState.board[idx];\n                if (cardInst == null)\n                {\n                    continue;\n                }\n\n                if (cardInst.owner == ETriadCardOwner.Blue)\n                {\n                    int[] neis = TriadGameSimulation.cachedNeis[idx];\n\n                    int numCapturingValues = 0;\n                    int numValidSides = 0;\n                    for (int side = 0; side < 4; side++)\n                    {\n                        if ((neis[side] >= 0) && (gameState.board[neis[side]] == null))\n                        {\n                            int cardNumber = cardInst.GetNumber((ETriadGameSide)side);\n                            int numCaptures = 0;\n                            for (int testValue = 1; testValue <= 10; testValue++)\n                            {\n                                bool canCapture = CanBeCapturedWith(solver.simulation, cardNumber, testValue);\n                                numCaptures += canCapture ? 1 : 0;\n                            }\n\n                            //Logger.WriteLine($\"[{idx}].side:{side} card:{cardNumber} <- captures:{numCaptures}\");\n                            numValidSides++;\n                            numCapturingValues += numCaptures;\n                        }\n                    }\n\n                    capturingSum += (numValidSides > 0) ? (numCapturingValues / (numValidSides * 10.0f)) : 0.0f;\n                    numBlueCards++;\n                }\n            }\n\n            float defenseScore = (numBlueCards > 0) ? (1.0f - (capturingSum / numBlueCards)) : 0.0f;\n            float captureScore = Math.Min(1.0f, numBlueCards / 5.0f);\n\n            return (defenseScore, captureScore);\n        }\n\n        private float CalculateBlueDeckScore(TriadGameSolver solver, TriadGameSimulationState gameState)\n        {\n            float blueCardScore = 0.0f;\n            int numScoredBlueCards = 0;\n\n            for (int idx = 0; idx < TriadDeckInstance.maxAvailableCards; idx++)\n            {\n                if ((gameState.deckBlue.availableCardMask & (1 << idx)) != 0)\n                {\n                    var testCard = gameState.deckBlue.GetCard(idx);\n                    float cardScore = testCard.OptimizerScore;\n\n                    foreach (TriadGameModifier mod in solver.simulation.modifiers)\n                    {\n                        mod.OnScoreCard(testCard, ref cardScore);\n                    }\n\n                    blueCardScore += cardScore;\n                    numScoredBlueCards++;\n                }\n            }\n\n            return (numScoredBlueCards > 0) ? (blueCardScore / numScoredBlueCards) : 0.0f;\n        }\n\n        private bool CanBeCapturedWith(TriadGameSimulation simulation, int defendingNum, int capturingNum)\n        {\n            if ((simulation.modFeatures & TriadGameModifier.EFeature.CaptureWeights) != 0)\n            {\n                bool isReverseActive = (simulation.modFeatures & TriadGameModifier.EFeature.CaptureMath) != 0;\n\n                foreach (TriadGameModifier mod in simulation.modifiers)\n                {\n                    mod.OnCheckCaptureCardWeights(null, -1, -1, isReverseActive, ref capturingNum, ref defendingNum);\n                }\n            }\n\n            bool isCaptured = (capturingNum > defendingNum);\n\n            if ((simulation.modFeatures & TriadGameModifier.EFeature.CaptureMath) != 0)\n            {\n                foreach (TriadGameModifier mod in simulation.modifiers)\n                {\n                    mod.OnCheckCaptureCardMath(null, -1, -1, capturingNum, defendingNum, ref isCaptured);\n                }\n            }\n\n            return isCaptured;\n        }\n    }\n}\n"
  },
  {
    "path": "sources/gamelogic/TriadGameModifier.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\nusing System.Reflection;\n\nnamespace FFTriadBuddy\n{\n    [Flags]\n    public enum ETriadGameSpecialMod\n    {\n        None = 0,\n        SelectVisible3 = 0x1,\n        SelectVisible5 = 0x2,\n        RandomizeRule = 0x4,\n        RandomizeBlueDeck = 0x8,\n        SwapCards = 0x10,\n        BlueCardSelection = 0x20,\n        IgnoreOwnedCheck = 0x40,\n    }\n\n    public class TriadGameModifier : IComparable\n    {\n        [Flags]\n        public enum EFeature\n        {\n            None = 0,\n            CardPlaced = 1,\n            CaptureNei = 2,\n            CaptureWeights = 4,\n            CaptureMath = 8,\n            PostCapture = 16,\n            AllPlaced = 32,\n            FilterNext = 64,\n        }\n\n        protected string RuleName;\n        protected LocString LocRuleName;\n        protected bool bAllowCombo = false;\n        protected bool bIsDeckOrderImportant = false;\n        protected bool bHasLastRedReminder = false;\n        protected ETriadGameSpecialMod SpecialMod = ETriadGameSpecialMod.None;\n        protected EFeature Features = EFeature.None;\n\n        public virtual string GetCodeName() { return RuleName; }\n        public virtual string GetLocalizedName() { return LocRuleName.GetLocalized(); }\n        public int GetLocalizationId() { return LocRuleName.Id; }\n        public virtual bool AllowsCombo() { return bAllowCombo; }\n        public virtual bool IsDeckOrderImportant() { return bIsDeckOrderImportant; }\n        public virtual ETriadGameSpecialMod GetSpecialRules() { return SpecialMod; }\n        public virtual EFeature GetFeatures() { return Features; }\n        public virtual bool HasLastRedReminder() { return bHasLastRedReminder; }\n        public override string ToString() { return GetCodeName(); }\n\n        public virtual void OnCardPlaced(TriadGameSimulationState gameData, int boardPos) { }\n        public virtual void OnCheckCaptureNeis(TriadGameSimulationState gameData, int boardPos, int[] neiPos, List<int> captureList) { }\n        public virtual void OnCheckCaptureCardWeights(TriadGameSimulationState gameData, int boardPos, int neiPos, bool isReverseActive, ref int cardNum, ref int neiNum) { }\n        public virtual void OnCheckCaptureCardMath(TriadGameSimulationState gameData, int boardPos, int neiPos, int cardNum, int neiNum, ref bool isCaptured) { }\n        public virtual void OnPostCaptures(TriadGameSimulationState gameData, int boardPos) { }\n        public virtual void OnScreenUpdate(TriadGameSimulationState gameData) { }\n        public virtual void OnAllCardsPlaced(TriadGameSimulationState gameData) { }\n        public virtual void OnFilterNextCards(TriadGameSimulationState gameData, ref int allowedCardsMask) { }\n        public virtual void OnMatchInit() { }\n        public virtual void OnScoreCard(TriadCard card, ref float score) { }\n\n        public int CompareTo(TriadGameModifier otherMod)\n        {\n            if (otherMod != null)\n            {\n                string locStrA = GetLocalizedName();\n                string locStrB = otherMod.GetLocalizedName();\n                return locStrA.CompareTo(locStrB);\n            }\n            return 0;\n        }\n\n        public int CompareTo(object obj)\n        {\n            return CompareTo((TriadGameModifier)obj);\n        }\n\n        public virtual TriadGameModifier Clone()\n        {\n            return (TriadGameModifier)this.MemberwiseClone();\n        }\n\n        public override bool Equals(object obj)\n        {\n            var otherMod = obj as TriadGameModifier;\n            return (otherMod != null) && (GetLocalizationId() == otherMod.GetLocalizationId());\n        }\n\n        public override int GetHashCode()\n        {\n            return GetLocalizationId();\n        }\n    }\n\n    public class TriadGameModifierNone : TriadGameModifier\n    {\n        public TriadGameModifierNone()\n        {\n            RuleName = \"None\";\n            LocRuleName = LocalizationDB.Get().FindOrAddLocString(ELocStringType.RuleName, 0);\n        }\n        // no special logic\n    }\n\n    public class TriadGameModifierRoulette : TriadGameModifier\n    {\n        protected TriadGameModifier RuleInst;\n\n        public TriadGameModifierRoulette()\n        {\n            RuleName = \"Roulette\";\n            LocRuleName = LocalizationDB.Get().FindOrAddLocString(ELocStringType.RuleName, 1);\n            SpecialMod = ETriadGameSpecialMod.RandomizeRule;\n        }\n\n        public override string GetCodeName() { return base.GetCodeName() + (RuleInst != null ? (\" (\" + RuleInst.GetCodeName() + \")\") : \"\"); }\n        public override string GetLocalizedName() { return base.GetLocalizedName() + (RuleInst != null ? (\" (\" + RuleInst.GetLocalizedName() + \")\") : \"\"); }\n        public override bool AllowsCombo() { return (RuleInst != null) ? RuleInst.AllowsCombo() : base.AllowsCombo(); }\n        public override bool IsDeckOrderImportant() { return (RuleInst != null) ? RuleInst.IsDeckOrderImportant() : base.IsDeckOrderImportant(); }\n        public override ETriadGameSpecialMod GetSpecialRules() { return base.GetSpecialRules() | ((RuleInst != null) ? RuleInst.GetSpecialRules() : ETriadGameSpecialMod.None); }\n        public override EFeature GetFeatures() { return (RuleInst != null) ? RuleInst.GetFeatures() : EFeature.None; }\n        public override bool HasLastRedReminder() { return (RuleInst != null) ? RuleInst.HasLastRedReminder() : base.HasLastRedReminder(); }\n\n        public override void OnCardPlaced(TriadGameSimulationState gameData, int boardPos)\n        {\n            if (RuleInst != null) { RuleInst.OnCardPlaced(gameData, boardPos); }\n        }\n\n        public override void OnCheckCaptureNeis(TriadGameSimulationState gameData, int boardPos, int[] neiPos, List<int> captureList)\n        {\n            if (RuleInst != null) { RuleInst.OnCheckCaptureNeis(gameData, boardPos, neiPos, captureList); }\n        }\n\n        public override void OnCheckCaptureCardWeights(TriadGameSimulationState gameData, int boardPos, int neiPos, bool isReverseActive, ref int cardNum, ref int neiNum)\n        {\n            if (RuleInst != null) { RuleInst.OnCheckCaptureCardWeights(gameData, boardPos, neiPos, isReverseActive, ref cardNum, ref neiNum); }\n        }\n\n        public override void OnCheckCaptureCardMath(TriadGameSimulationState gameData, int boardPos, int neiPos, int cardNum, int neiNum, ref bool isCaptured)\n        {\n            if (RuleInst != null) { RuleInst.OnCheckCaptureCardMath(gameData, boardPos, neiPos, cardNum, neiNum, ref isCaptured); }\n        }\n\n        public override void OnPostCaptures(TriadGameSimulationState gameData, int boardPos)\n        {\n            if (RuleInst != null) { RuleInst.OnPostCaptures(gameData, boardPos); }\n        }\n\n        public override void OnAllCardsPlaced(TriadGameSimulationState gameData)\n        {\n            if (RuleInst != null) { RuleInst.OnAllCardsPlaced(gameData); }\n        }\n\n        public override void OnFilterNextCards(TriadGameSimulationState gameData, ref int allowedCardsMask)\n        {\n            if (RuleInst != null) { RuleInst.OnFilterNextCards(gameData, ref allowedCardsMask); }\n        }\n\n        public override void OnMatchInit()\n        {\n            SetRuleInstance(null);\n        }\n\n        public void SetRuleInstance(TriadGameModifier RuleInstance)\n        {\n            RuleInst = RuleInstance;\n        }\n    }\n\n    public class TriadGameModifierAllOpen : TriadGameModifier\n    {\n        public TriadGameModifierAllOpen()\n        {\n            RuleName = \"All Open\";\n            LocRuleName = LocalizationDB.Get().FindOrAddLocString(ELocStringType.RuleName, 2);\n            SpecialMod = ETriadGameSpecialMod.SelectVisible5;\n        }\n\n        // shared with three open\n        public static void StaticMakeKnown(TriadGameSimulationState gameData, List<int> redIndices)\n        {\n            const int deckSize = 5;\n\n            TriadDeckInstanceManual deckRedEx = gameData.deckRed as TriadDeckInstanceManual;\n            if (deckRedEx != null && redIndices.Count <= deckSize)\n            {\n                if (gameData.bDebugRules)\n                {\n                    Logger.WriteLine(\">> Open:{0}! red indices:{1}\", redIndices.Count, string.Join(\", \", redIndices));\n                }\n\n                TriadDeck redDeckVisible = new TriadDeck(deckRedEx.deck.knownCards, deckRedEx.deck.unknownCardPool);\n                for (int idx = 0; idx < redIndices.Count; idx++)\n                {\n                    int cardIdx = redIndices[idx];\n                    if (cardIdx < deckRedEx.deck.knownCards.Count)\n                    {\n                        // already known, ignore\n                    }\n                    else\n                    {\n                        int idxU = cardIdx - deckRedEx.deck.knownCards.Count;\n                        var cardOb = deckRedEx.deck.unknownCardPool[idxU];\n                        redDeckVisible.knownCards.Add(cardOb);\n                        redDeckVisible.unknownCardPool.Remove(cardOb);\n                    }\n                }\n\n                // safety for impossible state\n                for (int idx = 0; (idx < redDeckVisible.knownCards.Count) && (redDeckVisible.knownCards.Count > deckSize); idx++)\n                {\n                    var cardOb = redDeckVisible.knownCards[idx];\n                    int orgIdx = deckRedEx.GetCardIndex(cardOb);\n                    if (!redIndices.Contains(orgIdx))\n                    {\n                        redDeckVisible.knownCards.RemoveAt(idx);\n                        idx--;\n                    }\n                }\n\n                gameData.deckRed = new TriadDeckInstanceManual(redDeckVisible);\n            }\n        }\n    }\n\n    public class TriadGameModifierThreeOpen : TriadGameModifier\n    {\n        public TriadGameModifierThreeOpen()\n        {\n            RuleName = \"Three Open\";\n            LocRuleName = LocalizationDB.Get().FindOrAddLocString(ELocStringType.RuleName, 3);\n            SpecialMod = ETriadGameSpecialMod.SelectVisible3;\n        }\n        // no special logic\n    }\n\n    public class TriadGameModifierSuddenDeath : TriadGameModifier\n    {\n        public TriadGameModifierSuddenDeath()\n        {\n            RuleName = \"Sudden Death\";\n            LocRuleName = LocalizationDB.Get().FindOrAddLocString(ELocStringType.RuleName, 4);\n            bHasLastRedReminder = true;\n            Features = EFeature.AllPlaced;\n        }\n\n        public override void OnAllCardsPlaced(TriadGameSimulationState gameData)\n        {\n            if (gameData.state == ETriadGameState.BlueDraw && gameData.numRestarts < 3)\n            {\n                // TODO: don't follow this more than once when simulating in solver?\n                //       can get stuck in pretty long loops\n\n                // implement this rule only for manual mode, screen captures get everything automatically\n                TriadDeckInstanceManual deckBlueEx = gameData.deckBlue as TriadDeckInstanceManual;\n                TriadDeckInstanceManual deckRedEx = gameData.deckRed as TriadDeckInstanceManual;\n                if (deckBlueEx != null && deckRedEx != null)\n                {\n                    List<TriadCard> blueCards = new List<TriadCard>();\n                    List<TriadCard> redCards = new List<TriadCard>();\n                    List<TriadCard> redUnknownCards = new List<TriadCard>();\n                    string redCardsDebug = \"\";\n\n                    for (int Idx = 0; Idx < gameData.board.Length; Idx++)\n                    {\n                        if (gameData.board[Idx].owner == ETriadCardOwner.Blue)\n                        {\n                            blueCards.Add(gameData.board[Idx].card);\n                        }\n                        else\n                        {\n                            redCards.Add(gameData.board[Idx].card);\n                        }\n\n                        gameData.board[Idx] = null;\n                    }\n\n                    if (deckBlueEx.numPlaced < deckRedEx.numPlaced)\n                    {\n                        // blue has cards on hand, all known\n                        for (int Idx = 0; Idx < deckBlueEx.deck.knownCards.Count; Idx++)\n                        {\n                            bool bIsAvailable = !deckBlueEx.IsPlaced(Idx);\n                            if (bIsAvailable)\n                            {\n                                blueCards.Add(deckBlueEx.deck.knownCards[Idx]);\n                                break;\n                            }\n                        }\n\n                        gameData.state = ETriadGameState.InProgressBlue;\n                    }\n                    else\n                    {\n                        // red has cards on hand, check known vs unknown\n                        for (int Idx = 0; Idx < deckRedEx.deck.knownCards.Count; Idx++)\n                        {\n                            bool bIsAvailable = !deckRedEx.IsPlaced(Idx);\n                            if (bIsAvailable)\n                            {\n                                redCards.Add(deckRedEx.deck.knownCards[Idx]);\n                                redCardsDebug += deckRedEx.deck.knownCards[Idx].Name.GetCodeName() + \":K, \";\n                                break;\n                            }\n                        }\n\n                        if (redCards.Count < blueCards.Count)\n                        {\n                            for (int Idx = 0; Idx < deckRedEx.deck.unknownCardPool.Count; Idx++)\n                            {\n                                int cardIdx = Idx + deckRedEx.deck.knownCards.Count;\n                                bool bIsAvailable = !deckRedEx.IsPlaced(cardIdx);\n                                if (bIsAvailable)\n                                {\n                                    redUnknownCards.Add(deckRedEx.deck.unknownCardPool[Idx]);\n                                    redCardsDebug += deckRedEx.deck.unknownCardPool[Idx].Name.GetCodeName() + \":U, \";\n                                }\n                            }\n                        }\n\n                        gameData.state = ETriadGameState.InProgressRed;\n                    }\n\n                    gameData.deckBlue = new TriadDeckInstanceManual(new TriadDeck(blueCards));\n                    gameData.deckRed = new TriadDeckInstanceManual(new TriadDeck(redCards, redUnknownCards));\n                    gameData.numCardsPlaced = 0;\n                    gameData.numRestarts++;\n\n                    for (int Idx = 0; Idx < gameData.typeMods.Length; Idx++)\n                    {\n                        gameData.typeMods[Idx] = 0;\n                    }\n\n                    if (gameData.bDebugRules)\n                    {\n                        redCardsDebug = (redCardsDebug.Length > 0) ? redCardsDebug.Remove(redCardsDebug.Length - 2, 2) : \"(board only)\";\n                        ETriadCardOwner nextTurnOwner = (gameData.state == ETriadGameState.InProgressBlue) ? ETriadCardOwner.Blue : ETriadCardOwner.Red;\n                        Logger.WriteLine(\">> \" + RuleName + \"! next turn:\" + nextTurnOwner + \", red:\" + redCardsDebug);\n                    }\n                }\n            }\n        }\n    }\n\n    public class TriadGameModifierReverse : TriadGameModifier\n    {\n        public TriadGameModifierReverse()\n        {\n            RuleName = \"Reverse\";\n            LocRuleName = LocalizationDB.Get().FindOrAddLocString(ELocStringType.RuleName, 5);\n            Features = EFeature.CaptureMath;\n        }\n\n        public override void OnCheckCaptureCardMath(TriadGameSimulationState gameData, int boardPos, int neiPos, int cardNum, int neiNum, ref bool isCaptured)\n        {\n            isCaptured = cardNum < neiNum;\n        }\n\n        public override void OnScoreCard(TriadCard card, ref float score)\n        {\n            const float MaxSum = 40.0f;\n            int numberSum = card.Sides[0] + card.Sides[1] + card.Sides[2] + card.Sides[3];\n            score = 1.0f - (numberSum / MaxSum);\n        }\n    }\n\n    public class TriadGameModifierFallenAce : TriadGameModifier\n    {\n        public TriadGameModifierFallenAce()\n        {\n            RuleName = \"Fallen Ace\";\n            LocRuleName = LocalizationDB.Get().FindOrAddLocString(ELocStringType.RuleName, 6);\n            Features = EFeature.CaptureWeights;\n        }\n\n        public override void OnCheckCaptureCardWeights(TriadGameSimulationState gameData, int boardPos, int neiPos, bool isReverseActive, ref int cardNum, ref int neiNum)\n        {\n            // note: check if cardNum at [boardPos] can capture neiNum at [neiPos]\n            // cardNum:1 vs neiNum:A => override weights to force capture\n            // cardNum:A vs neiNum:1 => capture, no need to change weights\n            //\n            // due to asymetry, it needs to know about active reverse rule and swap sides\n\n            if (isReverseActive)\n            {\n                if ((cardNum == 10) && (neiNum == 1))\n                {\n                    cardNum = 0;\n                }\n            }\n            else\n            {\n                if ((cardNum == 1) && (neiNum == 10))\n                {\n                    neiNum = 0;\n                }\n            }\n        }\n    }\n\n    public class TriadGameModifierSame : TriadGameModifier\n    {\n        public TriadGameModifierSame()\n        {\n            RuleName = \"Same\";\n            LocRuleName = LocalizationDB.Get().FindOrAddLocString(ELocStringType.RuleName, 7);\n            bAllowCombo = true;\n            Features = EFeature.CaptureNei | EFeature.CardPlaced;\n        }\n\n        public override void OnCheckCaptureNeis(TriadGameSimulationState gameData, int boardPos, int[] neiPos, List<int> captureList)\n        {\n            TriadCardInstance checkCard = gameData.board[boardPos];\n            int numSame = 0;\n            int neiCaptureMask = 0;\n            for (int sideIdx = 0; sideIdx < 4; sideIdx++)\n            {\n                int testNeiPos = neiPos[sideIdx];\n                if (testNeiPos >= 0 && gameData.board[testNeiPos] != null)\n                {\n                    TriadCardInstance neiCard = gameData.board[testNeiPos];\n\n                    int numPos = checkCard.GetNumber((ETriadGameSide)sideIdx);\n                    int numOther = neiCard.GetOppositeNumber((ETriadGameSide)sideIdx);\n                    if (numPos == numOther)\n                    {\n                        numSame++;\n\n                        if (neiCard.owner != checkCard.owner)\n                        {\n                            neiCaptureMask |= (1 << sideIdx);\n                        }\n                    }\n                }\n            }\n\n            if (numSame >= 2)\n            {\n                for (int sideIdx = 0; sideIdx < 4; sideIdx++)\n                {\n                    int testNeiPos = neiPos[sideIdx];\n                    if ((neiCaptureMask & (1 << sideIdx)) != 0)\n                    {\n                        TriadCardInstance neiCard = gameData.board[testNeiPos];\n                        neiCard.owner = checkCard.owner;\n                        captureList.Add(testNeiPos);\n\n                        if (gameData.bDebugRules)\n                        {\n                            Logger.WriteLine(\">> \" + RuleName + \"! [\" + testNeiPos + \"] \" + neiCard.card.Name.GetCodeName() + \" => \" + neiCard.owner);\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    public class TriadGameModifierPlus : TriadGameModifier\n    {\n        public TriadGameModifierPlus()\n        {\n            RuleName = \"Plus\";\n            LocRuleName = LocalizationDB.Get().FindOrAddLocString(ELocStringType.RuleName, 8);\n            bAllowCombo = true;\n            Features = EFeature.CaptureNei | EFeature.CardPlaced;\n        }\n\n        public override void OnCheckCaptureNeis(TriadGameSimulationState gameData, int boardPos, int[] neiPos, List<int> captureList)\n        {\n            TriadCardInstance checkCard = gameData.board[boardPos];\n            for (int sideIdx = 0; sideIdx < 4; sideIdx++)\n            {\n                int testNeiPos = neiPos[sideIdx];\n                if (testNeiPos >= 0 && gameData.board[testNeiPos] != null)\n                {\n                    TriadCardInstance neiCard = gameData.board[testNeiPos];\n                    if (checkCard.owner != neiCard.owner)\n                    {\n                        int numPosPattern = checkCard.GetNumber((ETriadGameSide)sideIdx);\n                        int numOtherPattern = neiCard.GetOppositeNumber((ETriadGameSide)sideIdx);\n                        int sumPattern = numPosPattern + numOtherPattern;\n                        bool bIsCaptured = false;\n\n                        for (int vsSideIdx = 0; vsSideIdx < 4; vsSideIdx++)\n                        {\n                            int vsNeiPos = neiPos[vsSideIdx];\n                            if (vsNeiPos >= 0 && sideIdx != vsSideIdx && gameData.board[vsNeiPos] != null)\n                            {\n                                TriadCardInstance vsCard = gameData.board[vsNeiPos];\n\n                                int numPosVs = checkCard.GetNumber((ETriadGameSide)vsSideIdx);\n                                int numOtherVs = vsCard.GetOppositeNumber((ETriadGameSide)vsSideIdx);\n                                int sumVs = numPosVs + numOtherVs;\n\n                                if (sumPattern == sumVs)\n                                {\n                                    bIsCaptured = true;\n\n                                    if (vsCard.owner != checkCard.owner)\n                                    {\n                                        vsCard.owner = checkCard.owner;\n                                        captureList.Add(vsNeiPos);\n\n                                        if (gameData.bDebugRules)\n                                        {\n                                            Logger.WriteLine(\">> \" + RuleName + \"! [\" + vsNeiPos + \"] \" + vsCard.card.Name.GetCodeName() + \" => \" + vsCard.owner);\n                                        }\n                                    }\n                                }\n                            }\n                        }\n\n                        if (bIsCaptured)\n                        {\n                            neiCard.owner = checkCard.owner;\n                            captureList.Add(testNeiPos);\n\n                            if (gameData.bDebugRules)\n                            {\n                                Logger.WriteLine(\">> \" + RuleName + \"! [\" + testNeiPos + \"] \" + neiCard.card.Name.GetCodeName() + \" => \" + neiCard.owner);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    public class TriadGameModifierAscention : TriadGameModifier\n    {\n        public TriadGameModifierAscention()\n        {\n            RuleName = \"Ascension\";\n            LocRuleName = LocalizationDB.Get().FindOrAddLocString(ELocStringType.RuleName, 9);\n            Features = EFeature.CardPlaced | EFeature.PostCapture;\n        }\n\n        public override void OnCardPlaced(TriadGameSimulationState gameData, int boardPos)\n        {\n            TriadCardInstance checkCard = gameData.board[boardPos];\n            if (checkCard.card.Type != ETriadCardType.None)\n            {\n                int scoreMod = gameData.typeMods[(int)checkCard.card.Type];\n                if (scoreMod != 0)\n                {\n                    checkCard.scoreModifier = scoreMod;\n\n                    if (gameData.bDebugRules)\n                    {\n                        Logger.WriteLine(\">> \" + RuleName + \"! [\" + boardPos + \"] \" + checkCard.card.Name.GetCodeName() + \" is: \" + ((scoreMod > 0) ? \"+\" : \"\") + scoreMod);\n                    }\n                }\n            }\n        }\n\n        public override void OnPostCaptures(TriadGameSimulationState gameData, int boardPos)\n        {\n            TriadCardInstance checkCard = gameData.board[boardPos];\n            if (checkCard.card.Type != ETriadCardType.None)\n            {\n                int scoreMod = checkCard.scoreModifier + 1;\n                gameData.typeMods[(int)checkCard.card.Type] = scoreMod;\n\n                for (int Idx = 0; Idx < gameData.board.Length; Idx++)\n                {\n                    TriadCardInstance otherCard = gameData.board[Idx];\n                    if ((otherCard != null) && (checkCard.card.Type == otherCard.card.Type))\n                    {\n                        otherCard.scoreModifier = scoreMod;\n                        if (gameData.bDebugRules)\n                        {\n                            Logger.WriteLine(\">> \" + RuleName + \"! [\" + Idx + \"] \" + otherCard.card.Name.GetCodeName() + \" is: \" + ((scoreMod > 0) ? \"+\" : \"\") + scoreMod);\n                        }\n                    }\n                }\n            }\n        }\n\n        public override void OnScreenUpdate(TriadGameSimulationState gameData)\n        {\n            for (int Idx = 0; Idx < gameData.typeMods.Length; Idx++)\n            {\n                gameData.typeMods[Idx] = 0;\n            }\n\n            for (int Idx = 0; Idx < gameData.board.Length; Idx++)\n            {\n                TriadCardInstance checkCard = gameData.board[Idx];\n                if (checkCard != null && checkCard.card.Type != ETriadCardType.None)\n                {\n                    gameData.typeMods[(int)checkCard.card.Type] += 1;\n                }\n            }\n\n            for (int Idx = 0; Idx < gameData.board.Length; Idx++)\n            {\n                TriadCardInstance checkCard = gameData.board[Idx];\n                if (checkCard != null && checkCard.card.Type != ETriadCardType.None)\n                {\n                    checkCard.scoreModifier = gameData.typeMods[(int)checkCard.card.Type];\n                }\n            }\n        }\n\n        public override void OnScoreCard(TriadCard card, ref float score)\n        {\n            const float ScoreMult = 0.8f;\n            score *= ScoreMult;\n\n            bool bHasType = card.Type != ETriadCardType.None;\n            if (bHasType)\n            {\n                score += (1.0f - ScoreMult);\n            }\n        }\n    }\n\n    public class TriadGameModifierDescention : TriadGameModifier\n    {\n        public TriadGameModifierDescention()\n        {\n            RuleName = \"Descension\";\n            LocRuleName = LocalizationDB.Get().FindOrAddLocString(ELocStringType.RuleName, 10);\n            Features = EFeature.CardPlaced | EFeature.PostCapture;\n        }\n\n        public override void OnCardPlaced(TriadGameSimulationState gameData, int boardPos)\n        {\n            TriadCardInstance checkCard = gameData.board[boardPos];\n            if (checkCard.card.Type != ETriadCardType.None)\n            {\n                int scoreMod = gameData.typeMods[(int)checkCard.card.Type];\n                if (scoreMod != 0)\n                {\n                    checkCard.scoreModifier = scoreMod;\n\n                    if (gameData.bDebugRules)\n                    {\n                        Logger.WriteLine(\">> \" + RuleName + \"! [\" + boardPos + \"] \" + checkCard.card.Name.GetCodeName() + \" is: \" + ((scoreMod > 0) ? \"+\" : \"\") + scoreMod);\n                    }\n                }\n            }\n        }\n\n        public override void OnPostCaptures(TriadGameSimulationState gameData, int boardPos)\n        {\n            TriadCardInstance checkCard = gameData.board[boardPos];\n            if (checkCard.card.Type != ETriadCardType.None)\n            {\n                int scoreMod = checkCard.scoreModifier - 1;\n                gameData.typeMods[(int)checkCard.card.Type] = scoreMod;\n\n                for (int Idx = 0; Idx < gameData.board.Length; Idx++)\n                {\n                    TriadCardInstance otherCard = gameData.board[Idx];\n                    if ((otherCard != null) && (checkCard.card.Type == otherCard.card.Type))\n                    {\n                        otherCard.scoreModifier = scoreMod;\n                        if (gameData.bDebugRules)\n                        {\n                            Logger.WriteLine(\">> \" + RuleName + \"! [\" + Idx + \"] \" + otherCard.card.Name.GetCodeName() + \" is: \" + ((scoreMod > 0) ? \"+\" : \"\") + scoreMod);\n                        }\n                    }\n                }\n            }\n        }\n\n        public override void OnScreenUpdate(TriadGameSimulationState gameData)\n        {\n            for (int Idx = 0; Idx < gameData.typeMods.Length; Idx++)\n            {\n                gameData.typeMods[Idx] = 0;\n            }\n\n            for (int Idx = 0; Idx < gameData.board.Length; Idx++)\n            {\n                TriadCardInstance checkCard = gameData.board[Idx];\n                if (checkCard != null && checkCard.card.Type != ETriadCardType.None)\n                {\n                    gameData.typeMods[(int)checkCard.card.Type] -= 1;\n                }\n            }\n\n            for (int Idx = 0; Idx < gameData.board.Length; Idx++)\n            {\n                TriadCardInstance checkCard = gameData.board[Idx];\n                if (checkCard != null && checkCard.card.Type != ETriadCardType.None)\n                {\n                    checkCard.scoreModifier = gameData.typeMods[(int)checkCard.card.Type];\n                }\n            }\n        }\n\n        public override void OnScoreCard(TriadCard card, ref float score)\n        {\n            const float ScoreMult = 0.5f;\n            score *= ScoreMult;\n\n            bool bNoType = card.Type == ETriadCardType.None;\n            if (bNoType)\n            {\n                score += (1.0f - ScoreMult);\n            }\n        }\n    }\n\n    public class TriadGameModifierOrder : TriadGameModifier\n    {\n        public TriadGameModifierOrder()\n        {\n            RuleName = \"Order\";\n            LocRuleName = LocalizationDB.Get().FindOrAddLocString(ELocStringType.RuleName, 11);\n            bIsDeckOrderImportant = true; Features = EFeature.FilterNext;\n        }\n\n        public override void OnFilterNextCards(TriadGameSimulationState gameData, ref int allowedCardsMask)\n        {\n            if ((gameData.state == ETriadGameState.InProgressBlue) && (allowedCardsMask != 0))\n            {\n                int firstBlueIdx = gameData.deckBlue.GetFirstAvailableCardFast();\n                allowedCardsMask = (firstBlueIdx < 0) ? 0 : (1 << firstBlueIdx);\n\n                if (gameData.bDebugRules)\n                {\n                    TriadCard firstBlueCard = gameData.deckBlue.GetCard(firstBlueIdx);\n                    Logger.WriteLine(\">> \" + RuleName + \"! next card: \" + (firstBlueCard != null ? firstBlueCard.Name.GetCodeName() : \"none\"));\n                }\n            }\n        }\n    }\n\n    public class TriadGameModifierChaos : TriadGameModifier\n    {\n        public TriadGameModifierChaos()\n        {\n            RuleName = \"Chaos\";\n            LocRuleName = LocalizationDB.Get().FindOrAddLocString(ELocStringType.RuleName, 12);\n            SpecialMod = ETriadGameSpecialMod.BlueCardSelection;\n        }\n\n        // special logic, covered by GUI\n    }\n\n    public class TriadGameModifierSwap : TriadGameModifier\n    {\n        public TriadGameModifierSwap()\n        {\n            RuleName = \"Swap\";\n            LocRuleName = LocalizationDB.Get().FindOrAddLocString(ELocStringType.RuleName, 13);\n            SpecialMod = ETriadGameSpecialMod.SwapCards;\n        }\n\n        // special logic, covered by GUI\n        public static void StaticSwapCards(TriadGameSimulationState gameData, TriadCard swapFromBlue, int blueSlotIdx, TriadCard swapFromRed, int redSlotIdx)\n        {\n            // implement this rule only for manual mode, screen captures get everything automatically\n            TriadDeckInstanceManual deckBlueEx = gameData.deckBlue as TriadDeckInstanceManual;\n            TriadDeckInstanceManual deckRedEx = gameData.deckRed as TriadDeckInstanceManual;\n            if (deckBlueEx != null && deckRedEx != null)\n            {\n                bool bIsRedFromKnown = redSlotIdx < deckRedEx.deck.knownCards.Count;\n                if (gameData.bDebugRules)\n                {\n                    TriadGameModifierSwap DummyOb = new TriadGameModifierSwap();\n                    Logger.WriteLine(\">> \" + DummyOb.RuleName + \"! blue[\" + blueSlotIdx + \"]:\" + swapFromBlue.Name.GetCodeName() +\n                        \" <-> red[\" + redSlotIdx + (bIsRedFromKnown ? \"\" : \":Opt\") + \"]:\" + swapFromRed.Name.GetCodeName());\n                }\n\n                TriadDeck blueDeckSwapped = new TriadDeck(deckBlueEx.deck.knownCards, deckBlueEx.deck.unknownCardPool);\n                TriadDeck redDeckSwapped = new TriadDeck(deckRedEx.deck.knownCards, deckRedEx.deck.unknownCardPool);\n\n                // ignore order in red deck\n                redDeckSwapped.knownCards.Add(swapFromBlue);\n                redDeckSwapped.knownCards.Remove(swapFromRed);\n                redDeckSwapped.unknownCardPool.Remove(swapFromRed);\n\n                // preserve order in blue deck\n                blueDeckSwapped.knownCards[blueSlotIdx] = swapFromRed;\n\n                gameData.deckBlue = new TriadDeckInstanceManual(blueDeckSwapped);\n                gameData.deckRed = new TriadDeckInstanceManual(redDeckSwapped);\n            }\n        }\n    }\n\n    public class TriadGameModifierRandom : TriadGameModifier\n    {\n        public TriadGameModifierRandom()\n        {\n            RuleName = \"Random\";\n            LocRuleName = LocalizationDB.Get().FindOrAddLocString(ELocStringType.RuleName, 14);\n            SpecialMod = ETriadGameSpecialMod.RandomizeBlueDeck;\n        }\n\n        // special logic, covered by GUI\n        public static void StaticRandomized(TriadGameSimulationState gameData)\n        {\n            if (gameData.bDebugRules)\n            {\n                TriadGameModifierRandom DummyOb = new TriadGameModifierRandom();\n                Logger.WriteLine(\">> \" + DummyOb.RuleName + \"! blue deck:\" + gameData.deckBlue);\n            }\n        }\n    }\n\n    public class TriadGameModifierDraft : TriadGameModifier\n    {\n        public TriadGameModifierDraft()\n        {\n            RuleName = \"Draft\";\n            LocRuleName = LocalizationDB.Get().FindOrAddLocString(ELocStringType.RuleName, 15);\n            SpecialMod = ETriadGameSpecialMod.IgnoreOwnedCheck;\n        }\n        // no special logic\n    }\n\n    public class TriadGameModifierDB\n    {\n        public List<TriadGameModifier> mods;\n\n        private static TriadGameModifierDB instance = new TriadGameModifierDB();\n        public static TriadGameModifierDB Get() { return instance; }\n\n        public TriadGameModifierDB()\n        {\n            mods = new List<TriadGameModifier>();\n            foreach (Type type in Assembly.GetAssembly(typeof(TriadGameModifier)).GetTypes())\n            {\n                if (type.IsSubclassOf(typeof(TriadGameModifier)))\n                {\n                    TriadGameModifier modOb = (TriadGameModifier)Activator.CreateInstance(type);\n                    mods.Add(modOb);\n                }\n            }\n\n            mods.Sort((a, b) => (a.GetLocalizationId().CompareTo(b.GetLocalizationId())));\n\n            for (int idx = 0; idx < mods.Count; idx++)\n            {\n                if (mods[idx].GetLocalizationId() != idx)\n                {\n                    Logger.WriteLine(\"FAILED to initialize modifiers!\");\n                    break;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/gamelogic/TriadGameScreenMemory.cs",
    "content": "using MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\n\nnamespace FFTriadBuddy\n{\n    public class TriadGameScreenMemory\n    {\n        [Flags]\n        public enum EUpdateFlags\n        {\n            None = 0,\n            Modifiers = 1,\n            Board = 2,\n            RedDeck = 4,\n            BlueDeck = 8,\n            SwapWarning = 16,\n            SwapHints = 32,\n        }\n\n        public TriadGameSimulationState gameState;\n        public TriadGameSolver gameSolver;\n        public TriadDeckInstanceScreen deckBlue;\n        public TriadDeckInstanceScreen deckRed;\n        private List<TriadCard[]> blueDeckHistory;\n        private TriadCard[] playerDeckPattern;\n        private TriadNpc lastScanNpc;\n        private bool bHasSwapRule;\n        private bool bHasRestartRule;\n        private bool bHasOpenRule;\n        public int swappedBlueCardIdx;\n        public bool logScan;\n\n        public TriadGameScreenMemory()\n        {\n            gameSolver = new TriadGameSolver();\n            gameState = new TriadGameSimulationState();\n            deckBlue = new TriadDeckInstanceScreen();\n            deckRed = new TriadDeckInstanceScreen();\n            blueDeckHistory = new List<TriadCard[]>();\n            bHasSwapRule = false;\n            swappedBlueCardIdx = -1;\n            lastScanNpc = null;\n            logScan = true;\n        }\n\n        public EUpdateFlags OnNewScan(ScannerTriad.GameState screenGame, TriadNpc selectedNpc)\n        {\n            EUpdateFlags updateFlags = EUpdateFlags.None;\n            if (screenGame == null)\n            {\n                return updateFlags;\n            }\n\n            // check if game from screenshot can be continuation of cached one\n            // is current state a continuation of last one?\n            // ideally each blue turn is a capture until game resets = any card disappears from board\n            // guess work for adding sense of persistence to screen decks\n            bool bContinuesPrevState = (deckRed.deck == selectedNpc.Deck) && (lastScanNpc == selectedNpc);\n            if (bContinuesPrevState)\n            {\n                for (int Idx = 0; Idx < gameState.board.Length; Idx++)\n                {\n                    bool bWasNull = gameState.board[Idx] == null;\n                    bool bIsNull = screenGame.board[Idx] == null;\n\n                    if (!bWasNull && bIsNull)\n                    {\n                        bContinuesPrevState = false;\n                        if (logScan) { Logger.WriteLine(\"Can't continue previous state: board[\" + Idx + \"] disappeared \"); }\n                    }\n                }\n            }\n            else\n            {\n                if (logScan) { Logger.WriteLine(\"Can't continue previous state: npc changed\"); }\n            }\n\n            bool bModsChanged = (gameSolver.simulation.modifiers.Count != screenGame.mods.Count) || !gameSolver.simulation.modifiers.All(screenGame.mods.Contains);\n            if (bModsChanged)\n            {\n                bHasSwapRule = false;\n                bHasRestartRule = false;\n                bHasOpenRule = false;\n                gameSolver.simulation.modifiers.Clear();\n                gameSolver.simulation.modifiers.AddRange(screenGame.mods);\n                gameSolver.simulation.specialRules = ETriadGameSpecialMod.None;\n                gameSolver.simulation.modFeatures = TriadGameModifier.EFeature.None;\n                foreach (TriadGameModifier mod in gameSolver.simulation.modifiers)\n                {\n                    gameSolver.simulation.modFeatures |= mod.GetFeatures();\n\n                    // swap rule is bad for screenshot based analysis, no good way of telling what is out of place\n                    if (mod is TriadGameModifierSwap)\n                    {\n                        bHasSwapRule = true;\n                    }\n                    else if (mod is TriadGameModifierSuddenDeath)\n                    {\n                        bHasRestartRule = true;\n                    }\n                    else if (mod is TriadGameModifierAllOpen)\n                    {\n                        bHasOpenRule = true;\n                    }\n                }\n\n                updateFlags |= EUpdateFlags.Modifiers;\n                bContinuesPrevState = false;\n                if (logScan) { Logger.WriteLine(\"Can't continue previous state: modifiers changed\"); }\n\n                deckRed.SetSwappedCard(null, -1);\n            }\n\n            // wipe blue deck history when playing with new npc (or region modifiers have changed)\n            bool bRemoveBlueHistory = bModsChanged || (lastScanNpc != selectedNpc);\n            if (bRemoveBlueHistory)\n            {\n                blueDeckHistory.Clear();\n                if (bHasSwapRule && logScan) { Logger.WriteLine(\"Blue deck history cleared\"); }\n            }\n\n            bool bRedDeckChanged = (lastScanNpc != selectedNpc) || !IsDeckMatching(deckRed, screenGame.redDeck) || (deckRed.deck != selectedNpc.Deck);\n            if (bRedDeckChanged)\n            {\n                updateFlags |= EUpdateFlags.RedDeck;\n                deckRed.deck = selectedNpc.Deck;\n                lastScanNpc = selectedNpc;\n\n                // needs to happen before any changed to board (gameState)\n                UpdateAvailableRedCards(deckRed, screenGame.redDeck, screenGame.blueDeck,\n                    screenGame.board, deckBlue.cards, gameState.board, bContinuesPrevState);\n            }\n\n            bool bBlueDeckChanged = !IsDeckMatching(deckBlue, screenGame.blueDeck);\n            if (bBlueDeckChanged)\n            {\n                updateFlags |= EUpdateFlags.BlueDeck;\n                deckBlue.UpdateAvailableCards(screenGame.blueDeck);\n            }\n\n            gameState.state = ETriadGameState.InProgressBlue;\n            gameState.deckBlue = deckBlue;\n            gameState.deckRed = deckRed;\n            gameState.numCardsPlaced = 0;\n            gameState.forcedCardIdx = deckBlue.GetCardIndex(screenGame.forcedBlueCard);\n\n            bool bBoardChanged = false;\n            for (int Idx = 0; Idx < gameState.board.Length; Idx++)\n            {\n                bool bWasNull = gameState.board[Idx] == null;\n                bool bIsNull = screenGame.board[Idx] == null;\n\n                if (bWasNull && !bIsNull)\n                {\n                    bBoardChanged = true;\n                    gameState.board[Idx] = new TriadCardInstance(screenGame.board[Idx], screenGame.boardOwner[Idx]);\n                    if (logScan) { Logger.WriteLine(\"  board update: [\" + Idx + \"] \" + gameState.board[Idx].owner + \": \" + gameState.board[Idx].card.Name.GetCodeName()); }\n                }\n                else if (!bWasNull && bIsNull)\n                {\n                    bBoardChanged = true;\n                    gameState.board[Idx] = null;\n                }\n                else if (!bWasNull && !bIsNull)\n                {\n                    if (gameState.board[Idx].owner != screenGame.boardOwner[Idx] ||\n                        gameState.board[Idx].card != screenGame.board[Idx])\n                    {\n                        bBoardChanged = true;\n                        gameState.board[Idx] = new TriadCardInstance(screenGame.board[Idx], screenGame.boardOwner[Idx]);\n                    }\n                }\n\n                gameState.numCardsPlaced += (gameState.board[Idx] != null) ? 1 : 0;\n            }\n\n            if (bBoardChanged)\n            {\n                updateFlags |= EUpdateFlags.Board;\n\n                foreach (TriadGameModifier mod in gameSolver.simulation.modifiers)\n                {\n                    mod.OnScreenUpdate(gameState);\n                }\n            }\n\n            // start of game, do additional checks when swap rule is active\n            if (bHasSwapRule && gameState.numCardsPlaced <= 1)\n            {\n                updateFlags |= DetectSwapOnGameStart();\n            }\n\n            if (logScan)\n            {\n                Logger.WriteLine(\"OnNewScan> board:\" + (bBoardChanged ? \"changed\" : \"same\") +\n                    \", blue:\" + (bBlueDeckChanged ? \"changed\" : \"same\") +\n                    \", red:\" + (bRedDeckChanged ? \"changed\" : \"same\") +\n                    \", mods:\" + (bModsChanged ? \"changed\" : \"same\") +\n                    \", continuePrev:\" + bContinuesPrevState +\n                    \" => \" + ((updateFlags != EUpdateFlags.None) ? (\"UPDATE[\" + updateFlags + \"]\") : \"skip\"));\n            }\n\n            return updateFlags;\n        }\n\n        private bool IsDeckMatching(TriadDeckInstanceScreen deckInstance, TriadCard[] cards)\n        {\n            bool bIsMatching = false;\n            if ((deckInstance != null) && (cards != null) && (deckInstance.cards.Length >= cards.Length))\n            {\n                bIsMatching = true;\n                for (int Idx = 0; Idx < cards.Length; Idx++)\n                {\n                    bIsMatching = bIsMatching && (cards[Idx] == deckInstance.cards[Idx]);\n                }\n            }\n\n            return bIsMatching;\n        }\n\n        private void UpdateAvailableRedCards(TriadDeckInstanceScreen redDeck,\n            TriadCard[] screenCardsRed, TriadCard[] screenCardsBlue, TriadCard[] screenBoard,\n            TriadCard[] prevCardsBlue, TriadCardInstance[] prevBoard, bool bContinuePrevState)\n        {\n            bool bDebugMode = false;\n            int hiddenCardId = TriadCardDB.Get().hiddenCard.Id;\n            int numVisibleCards = deckRed.cards.Length;\n\n            redDeck.numPlaced = 0;\n            if (!bContinuePrevState)\n            {\n                redDeck.numUnknownPlaced = 0;\n            }\n\n            int maxUnknownToUse = redDeck.cards.Length - redDeck.deck.knownCards.Count;\n            int firstUnknownPoolIdx = redDeck.cards.Length + redDeck.deck.knownCards.Count;\n            if (redDeck.deck.unknownCardPool.Count > 0)\n            {\n                redDeck.unknownPoolMask = ((1 << redDeck.deck.unknownCardPool.Count) - 1) << firstUnknownPoolIdx;\n\n                for (int Idx = 0; Idx < screenCardsRed.Length; Idx++)\n                {\n                    if ((screenCardsRed[Idx] != null) && (screenCardsRed[Idx].Id != hiddenCardId) && redDeck.deck.unknownCardPool.Contains(screenCardsRed[Idx]))\n                    {\n                        redDeck.unknownPoolMask |= (1 << Idx);\n                    }\n                }\n            }\n\n            int allDeckAvailableMask = ((1 << (redDeck.deck.knownCards.Count + redDeck.deck.unknownCardPool.Count)) - 1) << numVisibleCards;\n\n            bool bCanCompareWithPrevData = (screenCardsRed.Length == redDeck.cards.Length) && (screenCardsBlue.Length == prevCardsBlue.Length) && (screenBoard.Length == prevBoard.Length);\n            if (bCanCompareWithPrevData && !bContinuePrevState)\n            {\n                // special case: 1st turn\n\n                int numCardsOnBoard = 0;\n                for (int Idx = 0; Idx < screenBoard.Length; Idx++)\n                {\n                    if (screenBoard[Idx] != null)\n                    {\n                        numCardsOnBoard++;\n                    }\n                }\n\n                if (numCardsOnBoard <= 1)\n                {\n                    bCanCompareWithPrevData = true;\n                    prevBoard = new TriadCardInstance[screenBoard.Length];\n                    prevCardsBlue = new TriadCard[numVisibleCards];\n                    deckRed.cards = new TriadCard[numVisibleCards];\n                    deckRed.availableCardMask = allDeckAvailableMask;\n                    deckRed.numPlaced = 0;\n                    deckRed.numUnknownPlaced = 0;\n                }\n                else\n                {\n                    bCanCompareWithPrevData = false;\n                }\n            }\n\n            if (bDebugMode)\n            {\n                Logger.WriteLine(\"Red deck update, diff mode check... \" +\n                    \"bContinuePrevState:\" + bContinuePrevState +\n                    \", cards(screen:\" + screenCardsRed.Length + \", prev:\" + deckRed.cards.Length + \")=\" + ((screenCardsRed.Length == deckRed.cards.Length) ? \"ok\" : \"nope\") +\n                    \", other(screen:\" + screenCardsBlue.Length + \", prev:\" + prevCardsBlue.Length + \")=\" + ((screenCardsBlue.Length == prevCardsBlue.Length) ? \"ok\" : \"nope\") +\n                    \", board(screen:\" + screenBoard.Length + \", prev:\" + prevBoard.Length + \")=\" + ((screenBoard.Length == prevBoard.Length) ? \"ok\" : \"nope\"));\n            }\n\n            if (bCanCompareWithPrevData)\n            {\n                // create diffs, hopefully prev state comes from last turn and is just 2 cards away\n                List<int> usedCardsIndices = new List<int>();\n                List<TriadCard> usedCardsOther = new List<TriadCard>();\n\n                int numKnownOnHand = 0;\n                int numUnknownOnHand = 0;\n                int numHidden = 0;\n                int numOnHand = 0;\n                for (int Idx = 0; Idx < deckRed.cards.Length; Idx++)\n                {\n                    if (screenCardsRed[Idx] == null)\n                    {\n                        TriadCard prevCard = deckRed.cards[Idx];\n                        if ((prevCard != null) && (prevCard.Id != hiddenCardId))\n                        {\n                            if (bDebugMode) { Logger.WriteLine(\"  card[\" + Idx + \"]:\" + prevCard.Name.GetCodeName() + \" => mark as used, disappeared from prev state\"); }\n                            usedCardsIndices.Add(Idx);\n                        }\n\n                        deckRed.availableCardMask &= ~(1 << Idx);\n                        deckRed.numPlaced++;\n                    }\n                    else\n                    {\n                        if (screenCardsRed[Idx].Id != hiddenCardId)\n                        {\n                            bool bIsUnknown = (deckRed.unknownPoolMask & (1 << Idx)) != 0;\n                            numUnknownOnHand += bIsUnknown ? 1 : 0;\n                            numKnownOnHand += bIsUnknown ? 0 : 1;\n                            numOnHand++;\n                            deckRed.availableCardMask |= (1 << Idx);\n\n                            int knownCardIdx = deckRed.deck.knownCards.IndexOf(screenCardsRed[Idx]);\n                            int unknownCardIdx = deckRed.deck.unknownCardPool.IndexOf(screenCardsRed[Idx]);\n                            if (knownCardIdx >= 0)\n                            {\n                                deckRed.availableCardMask &= ~(1 << (knownCardIdx + deckRed.cards.Length));\n                            }\n                            else if (unknownCardIdx >= 0)\n                            {\n                                deckRed.availableCardMask &= ~(1 << (unknownCardIdx + deckRed.cards.Length + deckRed.deck.knownCards.Count));\n                            }\n\n                            if (bDebugMode)\n                            {\n                                TriadCard cardOb = screenCardsRed[Idx];\n                                Logger.WriteLine(\" card[\" + Idx + \"]:\" + (cardOb != null ? cardOb.Name.GetCodeName() : \"??\") +\n                                    \" => numUnknown:\" + numUnknownOnHand + \", numKnown:\" + numKnownOnHand + \", numHidden:\" + numHidden);\n                            }\n                        }\n                        else\n                        {\n                            numHidden++;\n                        }\n                    }\n                }\n\n                for (int Idx = 0; Idx < prevCardsBlue.Length; Idx++)\n                {\n                    if ((prevCardsBlue[Idx] != null) && (screenCardsBlue[Idx] == null))\n                    {\n                        usedCardsOther.Add(prevCardsBlue[Idx]);\n                        if (bDebugMode) { Logger.WriteLine(\"  blue[\" + Idx + \"]:\" + prevCardsBlue[Idx].Name.GetCodeName() + \" => mark as used\"); }\n                    }\n                }\n\n                for (int Idx = 0; Idx < prevBoard.Length; Idx++)\n                {\n                    TriadCard testCard = screenBoard[Idx];\n                    if ((prevBoard[Idx] == null || prevBoard[Idx].card == null) && (testCard != null))\n                    {\n                        int testCardIdx = deckRed.GetCardIndex(testCard);\n                        if (!usedCardsOther.Contains(testCard) && (testCardIdx >= 0))\n                        {\n                            usedCardsIndices.Add(testCardIdx);\n                            if (bDebugMode) { Logger.WriteLine(\"  card[\" + testCardIdx + \"]:\" + testCard.Name.GetCodeName() + \" => mark as used, appeared on board[\" + Idx + \"], not used by blue\"); }\n                        }\n                    }\n                }\n\n                Array.Copy(screenCardsRed, deckRed.cards, 5);\n\n                for (int Idx = 0; Idx < usedCardsIndices.Count; Idx++)\n                {\n                    int cardMask = 1 << usedCardsIndices[Idx];\n                    deckRed.availableCardMask &= ~cardMask;\n\n                    bool bIsUnknownPool = (deckRed.unknownPoolMask & cardMask) != 0;\n                    if (bIsUnknownPool)\n                    {\n                        deckRed.numUnknownPlaced++;\n                    }\n\n                    if (bDebugMode)\n                    {\n                        TriadCard cardOb = deckRed.GetCard(usedCardsIndices[Idx]);\n                        Logger.WriteLine(\" card[\" + usedCardsIndices[Idx] + \"]:\" + (cardOb != null ? cardOb.Name.GetCodeName() : \"??\") + \" => used\");\n                    }\n                }\n\n                if ((numHidden == 0) && ((numOnHand + deckRed.numPlaced) == numVisibleCards))\n                {\n                    deckRed.availableCardMask &= (1 << numVisibleCards) - 1;\n                    if (bDebugMode) { Logger.WriteLine(\"   all cards are on hand and visible\"); }\n                }\n                else if ((deckRed.numUnknownPlaced + numUnknownOnHand) >= maxUnknownToUse ||\n                    ((numKnownOnHand >= (numVisibleCards - maxUnknownToUse)) && (numHidden == 0)))\n                {\n                    deckRed.availableCardMask &= (1 << (numVisibleCards + deckRed.deck.knownCards.Count)) - 1;\n\n                    if (bDebugMode)\n                    {\n                        Logger.WriteLine(\"   removing all unknown cards, numUnknownPlaced:\" + deckRed.numUnknownPlaced +\n                            \", numUnknownOnHand:\" + numUnknownOnHand + \", numKnownOnHand:\" + numKnownOnHand +\n                            \", numHidden:\" + numHidden + \", maxUnknownToUse:\" + maxUnknownToUse);\n                    }\n                }\n            }\n            else\n            {\n                // TriadDeckInstanceScreen is mostly stateless (created from scratch on screen capture)\n                // this makes guessing which cards were placed hard, especially when there's no good\n                // history data to compare with. \n                // Ignore board data here, cards could be placed by blue and are still available for red deck\n\n                deckRed.UpdateAvailableCards(screenCardsRed);\n                deckRed.availableCardMask = allDeckAvailableMask;\n            }\n\n            if (bDebugMode)\n            {\n                redDeck.LogAvailableCards(\"Red deck\");\n            }\n        }\n\n        public void UpdatePlayerDeck(TriadDeck playerDeck)\n        {\n            playerDeckPattern = playerDeck.knownCards.ToArray();\n        }\n\n        private bool FindSwappedCard(TriadCard[] screenCards, TriadCard[] expectedCards, TriadDeckInstanceScreen otherDeck, out int swappedCardIdx, out int swappedOtherIdx, out TriadCard swappedCard)\n        {\n            swappedCardIdx = -1;\n            swappedOtherIdx = -1;\n            swappedCard = null;\n            TriadCard swappedBlueCard = null;\n\n            int numDiffs = 0;\n            int numPotentialSwaps = 0;\n            for (int Idx = 0; Idx < screenCards.Length; Idx++)\n            {\n                if ((screenCards[Idx] != expectedCards[Idx]) && (screenCards[Idx] != null))\n                {\n                    numDiffs++;\n                    swappedCardIdx = Idx;\n                    swappedOtherIdx = otherDeck.GetCardIndex(screenCards[Idx]);\n                    swappedBlueCard = screenCards[Idx];\n                    swappedCard = expectedCards[Idx];\n                    Logger.WriteLine(\"FindSwappedCard[\" + Idx + \"]: screen:\" + screenCards[Idx].Name.GetCodeName() + \", expected:\" + expectedCards[Idx].Name.GetCodeName() + \", redIdxScreen:\" + swappedOtherIdx);\n\n                    if (swappedOtherIdx >= 0)\n                    {\n                        numPotentialSwaps++;\n                    }\n                }\n            }\n\n            bool bHasSwapped = (numDiffs == 1) && (numPotentialSwaps == 1);\n            Logger.WriteLine(\"FindSwappedCard: blue[\" + swappedCardIdx + \"]:\" + (swappedBlueCard != null ? swappedBlueCard.Name.GetCodeName() : \"??\") +\n                \" <=> red[\" + swappedOtherIdx + \"]:\" + (swappedCard != null ? swappedCard.Name.GetCodeName() : \"??\") +\n                \", diffs:\" + numDiffs + \", potentialSwaps:\" + numPotentialSwaps +\n                \" => \" + (bHasSwapped ? \"SWAP\" : \"ignore\"));\n\n            return bHasSwapped;\n        }\n\n        private bool FindSwappedCardVisible(TriadCard[] screenCards, TriadCardInstance[] board, TriadDeckInstanceScreen otherDeck, out int swappedCardIdx, out int swappedOtherIdx, out TriadCard swappedCard)\n        {\n            swappedCardIdx = -1;\n            swappedOtherIdx = -1;\n            swappedCard = null;\n\n            int numDiffs = 0;\n            int numOnHand = 0;\n\n            int hiddenCardId = TriadCardDB.Get().hiddenCard.Id;\n            for (int Idx = 0; Idx < otherDeck.cards.Length; Idx++)\n            {\n                if (otherDeck.cards[Idx] != null && otherDeck.cards[Idx].Id != hiddenCardId)\n                {\n                    // find in source deck, not in instance\n                    int cardIdx = otherDeck.deck.GetCardIndex(otherDeck.cards[Idx]);\n                    if (cardIdx < 0)\n                    {\n                        swappedOtherIdx = Idx;\n                        swappedCard = otherDeck.cards[Idx];\n                        for (int ScreenIdx = 0; ScreenIdx < screenCards.Length; ScreenIdx++)\n                        {\n                            cardIdx = otherDeck.deck.GetCardIndex(screenCards[ScreenIdx]);\n                            if (cardIdx >= 0)\n                            {\n                                swappedCardIdx = ScreenIdx;\n                                numDiffs++;\n                            }\n                        }\n                    }\n                }\n\n                numOnHand += (otherDeck.cards[Idx] != null) ? 1 : 0;\n            }\n\n            bool bBoardMode = false;\n            if (numOnHand < screenCards.Length)\n            {\n                for (int Idx = 0; Idx < board.Length; Idx++)\n                {\n                    if (board[Idx] != null && board[Idx].owner == ETriadCardOwner.Red)\n                    {\n                        // find in source deck, not in instance\n                        int cardIdx = otherDeck.deck.GetCardIndex(board[Idx].card);\n                        if (cardIdx < 0)\n                        {\n                            swappedCard = board[Idx].card;\n                            swappedOtherIdx = 100;                  // something way outside, it's not going to be used directly as card was already placed\n\n                            for (int ScreenIdx = 0; ScreenIdx < screenCards.Length; ScreenIdx++)\n                            {\n                                cardIdx = otherDeck.deck.GetCardIndex(screenCards[ScreenIdx]);\n                                if (cardIdx >= 0)\n                                {\n                                    swappedCardIdx = ScreenIdx;\n                                    bBoardMode = true;\n                                    numDiffs++;\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n\n            bool bHasSwapped = (numDiffs == 1);\n            Logger.WriteLine(\"FindSwappedCardVisible: blue[\" + swappedCardIdx + \"]:\" + (swappedCardIdx >= 0 ? screenCards[swappedCardIdx].Name.GetCodeName() : \"??\") +\n                \" <=> red[\" + swappedOtherIdx + \"]:\" + (swappedCard != null ? swappedCard.Name.GetCodeName() : \"??\") +\n                \", boardMode:\" + bBoardMode + \", diffs:\" + numDiffs + \" => \" + (bHasSwapped ? \"SWAP\" : \"ignore\"));\n\n            return bHasSwapped;\n        }\n\n        private TriadCard[] FindCommonCards(List<TriadCard[]> deckHistory)\n        {\n            TriadCard[] result = null;\n            if (deckHistory.Count > 1)\n            {\n                result = new TriadCard[deckHistory[0].Length];\n                for (int SlotIdx = 0; SlotIdx < result.Length; SlotIdx++)\n                {\n                    Dictionary<TriadCard, int> slotCounter = new Dictionary<TriadCard, int>();\n                    TriadCard bestSlotCard = null;\n                    int bestSlotCount = 0;\n\n                    for (int HistoryIdx = 0; HistoryIdx < deckHistory.Count; HistoryIdx++)\n                    {\n                        TriadCard testCard = deckHistory[HistoryIdx][SlotIdx];\n                        if (slotCounter.ContainsKey(testCard))\n                        {\n                            slotCounter[testCard] += 1;\n                        }\n                        else\n                        {\n                            slotCounter.Add(testCard, 1);\n                        }\n\n                        if (slotCounter[testCard] > bestSlotCount)\n                        {\n                            bestSlotCount = slotCounter[testCard];\n                            bestSlotCard = testCard;\n                        }\n                    }\n\n                    Logger.WriteLine(\"FindCommonCards[\" + SlotIdx + \"]: \" + bestSlotCard.Name.GetCodeName() + \" x\" + bestSlotCount + (bestSlotCount < 2 ? \" => not enough to decide!\" : \"\"));\n                    if (bestSlotCount >= 2)\n                    {\n                        result[SlotIdx] = bestSlotCard;\n                    }\n                    else\n                    {\n                        result = null;\n                        break;\n                    }\n                }\n            }\n\n            return result;\n        }\n\n        private EUpdateFlags DetectSwapOnGameStart()\n        {\n            EUpdateFlags updateFlags = EUpdateFlags.None;\n\n            deckRed.SetSwappedCard(null, -1);\n\n            for (int Idx = 0; Idx < deckBlue.cards.Length; Idx++)\n            {\n                if (deckBlue.cards[Idx] == null)\n                {\n                    Logger.WriteLine(\"DetectSwapOnGameStart: found empty blue card, skipping\");\n                    return updateFlags;\n                }\n            }\n\n            bool bDetectedSuddenDeath = bHasRestartRule && IsSuddenDeathRestart(deckRed);\n            if (bDetectedSuddenDeath)\n            {\n                Logger.WriteLine(\">> ignore swap checks\");\n                return updateFlags;\n            }\n\n            // store initial blue deck\n            {\n                if (blueDeckHistory.Count > 10)\n                {\n                    blueDeckHistory.RemoveAt(0);\n                }\n\n                TriadCard[] copyCards = new TriadCard[deckBlue.cards.Length];\n                Array.Copy(deckBlue.cards, copyCards, copyCards.Length);\n\n                blueDeckHistory.Add(copyCards);\n                Logger.WriteLine(\"Storing blue deck at[\" + blueDeckHistory.Count + \"]: \" + deckBlue);\n            }\n\n            int blueSwappedCardIdx = -1;\n            int redSwappedCardIdx = -1;\n            TriadCard blueSwappedCard = null;\n            bool bHasSwappedCard = FindSwappedCardVisible(deckBlue.cards, gameState.board, deckRed, out blueSwappedCardIdx, out redSwappedCardIdx, out blueSwappedCard);\n            if (!bHasSwappedCard)\n            {\n                bHasSwappedCard = FindSwappedCard(deckBlue.cards, playerDeckPattern, deckRed, out blueSwappedCardIdx, out redSwappedCardIdx, out blueSwappedCard);\n                if (!bHasSwappedCard)\n                {\n                    TriadCard[] commonCards = FindCommonCards(blueDeckHistory);\n                    if (commonCards != null)\n                    {\n                        bHasSwappedCard = FindSwappedCard(deckBlue.cards, commonCards, deckRed, out blueSwappedCardIdx, out redSwappedCardIdx, out blueSwappedCard);\n                    }\n                }\n            }\n\n            if (bHasSwappedCard)\n            {\n                // deck blue doesn't need updates, it already has all cards visible\n                // deck red needs to know which card is not longer available and which one is new\n                deckRed.SetSwappedCard(blueSwappedCard, redSwappedCardIdx);\n                swappedBlueCardIdx = blueSwappedCardIdx;\n                updateFlags |= EUpdateFlags.SwapHints;\n            }\n            else\n            {\n                swappedBlueCardIdx = -1;\n                updateFlags |= EUpdateFlags.SwapWarning;\n            }\n\n            return updateFlags;\n        }\n\n        private bool IsSuddenDeathRestart(TriadDeckInstanceScreen deck)\n        {\n            // sudden death: all red cards visible, not matching npc deck at all\n            int numMismatchedCards = 0;\n            int numVisibleCards = 0;\n            int hiddenCardId = TriadCardDB.Get().hiddenCard.Id;\n\n            for (int Idx = 0; Idx < deck.cards.Length; Idx++)\n            {\n                int npcCardIdx = deck.deck.GetCardIndex(deck.cards[Idx]);\n                if (npcCardIdx < 0)\n                {\n                    numMismatchedCards++;\n                }\n\n                if (deck.cards[Idx] != null && deck.cards[Idx].Id != hiddenCardId)\n                {\n                    numVisibleCards++;\n                }\n            }\n\n            bool bHasOpenAndMismatched = (numVisibleCards >= 4 && numMismatchedCards > 1);\n            bool bHasOpenAndShouldnt = (numVisibleCards >= 4 && !bHasOpenRule);\n\n            Logger.WriteLine(\"IsSuddenDeathRestart? numMismatchedCards:\" + numMismatchedCards + \", numVisibleCards:\" + numVisibleCards);\n            return bHasOpenAndMismatched || bHasOpenAndShouldnt;\n        }\n    }\n}\n"
  },
  {
    "path": "sources/gamelogic/TriadGameSimulation.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\n\nnamespace FFTriadBuddy\n{\n    public enum ETriadGameState\n    {\n        InProgressBlue,\n        InProgressRed,\n        BlueWins,\n        BlueDraw,\n        BlueLost,\n    }\n\n    public class TriadGameSimulationState\n    {\n        public TriadCardInstance[] board;\n        public TriadDeckInstance deckBlue;\n        public TriadDeckInstance deckRed;\n        public ETriadGameState state;\n        public ETriadGameSpecialMod resolvedSpecial;\n        public int[] typeMods;\n        public int numCardsPlaced;\n        public int numRestarts;\n        public int forcedCardIdx;\n        public bool bDebugRules;\n\n        public const int boardSize = 3;\n        public const int boardSizeSq = boardSize * boardSize;\n\n        public TriadGameSimulationState()\n        {\n            board = new TriadCardInstance[boardSizeSq];\n            typeMods = new int[Enum.GetNames(typeof(ETriadCardType)).Length];\n            state = ETriadGameState.InProgressBlue;\n            resolvedSpecial = ETriadGameSpecialMod.None;\n            numCardsPlaced = 0;\n            numRestarts = 0;\n            forcedCardIdx = -1;\n            bDebugRules = false;\n\n            for (int Idx = 0; Idx < typeMods.Length; Idx++)\n            {\n                typeMods[Idx] = 0;\n            }\n        }\n\n        public TriadGameSimulationState(TriadGameSimulationState copyFrom)\n        {\n            board = new TriadCardInstance[copyFrom.board.Length];\n            for (int Idx = 0; Idx < board.Length; Idx++)\n            {\n                board[Idx] = (copyFrom.board[Idx] == null) ? null : new TriadCardInstance(copyFrom.board[Idx]);\n            }\n\n            typeMods = new int[copyFrom.typeMods.Length];\n            for (int Idx = 0; Idx < typeMods.Length; Idx++)\n            {\n                typeMods[Idx] = copyFrom.typeMods[Idx];\n            }\n\n            deckBlue = copyFrom.deckBlue.CreateCopy();\n            deckRed = copyFrom.deckRed.CreateCopy();\n            state = copyFrom.state;\n            numCardsPlaced = copyFrom.numCardsPlaced;\n            numRestarts = copyFrom.numRestarts;\n            resolvedSpecial = copyFrom.resolvedSpecial;\n            // bDebugRules not copied, only first step needs it\n        }\n    }\n\n    public class TriadGameSimulation\n    {\n        public List<TriadGameModifier> modifiers = new List<TriadGameModifier>();\n        public ETriadGameSpecialMod specialRules;\n        public TriadGameModifier.EFeature modFeatures = TriadGameModifier.EFeature.None;\n\n        public static int[][] cachedNeis = new int[9][];\n\n        public TriadGameSimulationState StartGame(TriadDeck deckBlue, TriadDeck deckRed, ETriadGameState state)\n        {\n            foreach (var mod in modifiers)\n            {\n                mod.OnMatchInit();\n            }\n\n            return new TriadGameSimulationState()\n            {\n                state = state,\n                deckBlue = new TriadDeckInstanceManual(deckBlue),\n                deckRed = new TriadDeckInstanceManual(deckRed)\n            };\n        }\n\n        public void Initialize(IEnumerable<TriadGameModifier> modsA, IEnumerable<TriadGameModifier> modsB = null)\n        {\n            modifiers.Clear();\n\n            if (modsA != null)\n            {\n                foreach (var mod in modsA)\n                {\n                    TriadGameModifier modCopy = (TriadGameModifier)Activator.CreateInstance(mod.GetType());\n                    modifiers.Add(modCopy);\n                }\n            }\n\n            if (modsB != null)\n            {\n                foreach (var mod in modsB)\n                {\n                    TriadGameModifier modCopy = (TriadGameModifier)Activator.CreateInstance(mod.GetType());\n                    modifiers.Add(modCopy);\n                }\n            }\n\n            UpdateSpecialRules();\n        }\n\n        public void UpdateSpecialRules()\n        {\n            specialRules = ETriadGameSpecialMod.None;\n            modFeatures = TriadGameModifier.EFeature.None;\n            foreach (TriadGameModifier mod in modifiers)\n            {\n                specialRules |= mod.GetSpecialRules();\n                modFeatures |= mod.GetFeatures();\n            }\n        }\n\n        public bool HasSpecialRule(ETriadGameSpecialMod specialRule)\n        {\n            return (specialRules & specialRule) != ETriadGameSpecialMod.None;\n        }\n\n        public bool PlaceCard(TriadGameSimulationState gameState, int cardIdx, TriadDeckInstance cardDeck, ETriadCardOwner owner, int boardPos)\n        {\n            bool bResult = false;\n\n            bool bIsAllowedOwner =\n                ((owner == ETriadCardOwner.Blue) && (gameState.state == ETriadGameState.InProgressBlue)) ||\n                ((owner == ETriadCardOwner.Red) && (gameState.state == ETriadGameState.InProgressRed));\n\n            TriadCard card = cardDeck.GetCard(cardIdx);\n            if (bIsAllowedOwner && (boardPos >= 0) && (gameState.board[boardPos] == null) && (card != null))\n            {\n                gameState.board[boardPos] = new TriadCardInstance(card, owner);\n                gameState.numCardsPlaced++;\n\n                if (owner == ETriadCardOwner.Blue)\n                {\n                    gameState.deckBlue.OnCardPlacedFast(cardIdx);\n                    gameState.state = ETriadGameState.InProgressRed;\n                }\n                else\n                {\n                    gameState.deckRed.OnCardPlacedFast(cardIdx);\n                    gameState.state = ETriadGameState.InProgressBlue;\n                }\n\n                // verify owner\n                bResult = (owner == ETriadCardOwner.Red) || !HasSpecialRule(ETriadGameSpecialMod.IgnoreOwnedCheck);\n\n                bool bAllowCombo = false;\n                if ((modFeatures & TriadGameModifier.EFeature.CardPlaced) != 0)\n                {\n                    foreach (TriadGameModifier mod in modifiers)\n                    {\n                        mod.OnCardPlaced(gameState, boardPos);\n                        bAllowCombo = bAllowCombo || mod.AllowsCombo();\n                    }\n                }\n\n                List<int> comboList = new List<int>();\n                int comboCounter = 0;\n                CheckCaptures(gameState, boardPos, comboList, comboCounter);\n\n                while (bAllowCombo && comboList.Count > 0)\n                {\n                    if (gameState.bDebugRules) { Logger.WriteLine(\">> combo step: {0}\", string.Join(\",\", comboList)); }\n\n                    List<int> nextCombo = new List<int>();\n                    comboCounter++;\n                    foreach (int pos in comboList)\n                    {\n                        CheckCaptures(gameState, pos, nextCombo, comboCounter);\n                    }\n\n                    comboList = nextCombo;\n                }\n\n                if ((modFeatures & TriadGameModifier.EFeature.PostCapture) != 0)\n                {\n                    foreach (TriadGameModifier mod in modifiers)\n                    {\n                        mod.OnPostCaptures(gameState, boardPos);\n                    }\n                }\n\n                if (gameState.numCardsPlaced == gameState.board.Length)\n                {\n                    OnAllCardsPlaced(gameState);\n                }\n            }\n\n            return bResult;\n        }\n\n        public bool PlaceCard(TriadGameSimulationState gameState, TriadCard card, ETriadCardOwner owner, int boardPos)\n        {\n            TriadDeckInstance useDeck = (owner == ETriadCardOwner.Blue) ? gameState.deckBlue : gameState.deckRed;\n            int cardIdx = useDeck.GetCardIndex(card);\n\n            return PlaceCard(gameState, cardIdx, useDeck, owner, boardPos);\n        }\n\n        public static int GetBoardPos(int x, int y)\n        {\n            return x + (y * TriadGameSimulationState.boardSize);\n        }\n\n        public static void GetBoardXY(int pos, out int x, out int y)\n        {\n            x = pos % TriadGameSimulationState.boardSize;\n            y = pos / TriadGameSimulationState.boardSize;\n        }\n\n        public static int[] GetNeighbors(TriadGameSimulationState gameState, int boardPos)\n        {\n            int boardPosX = 0;\n            int boardPosY = 0;\n            GetBoardXY(boardPos, out boardPosX, out boardPosY);\n\n            int[] resultNeis = new int[4];\n            resultNeis[(int)ETriadGameSide.Up] = (boardPosY > 0) ? GetBoardPos(boardPosX, boardPosY - 1) : -1;\n            resultNeis[(int)ETriadGameSide.Down] = (boardPosY < (TriadGameSimulationState.boardSize - 1)) ? GetBoardPos(boardPosX, boardPosY + 1) : -1;\n            resultNeis[(int)ETriadGameSide.Right] = (boardPosX > 0) ? GetBoardPos(boardPosX - 1, boardPosY) : -1;\n            resultNeis[(int)ETriadGameSide.Left] = (boardPosX < (TriadGameSimulationState.boardSize - 1)) ? GetBoardPos(boardPosX + 1, boardPosY) : -1;\n\n            return resultNeis;\n        }\n\n        private void CheckCaptures(TriadGameSimulationState gameState, int boardPos, List<int> comboList, int comboCounter)\n        {\n            // combo:\n            // - modifiers are active only in intial placement\n            // - ...except for reverse, this one stays active in chains too...\n            // - only card captured via modifiers can initiate combo (same, plus)\n            // - type modifiers (ascention, descention) values are baked in card and influence combo\n            // - can't proc another plus/same as a result of combo\n\n            int[] neis = cachedNeis[boardPos];\n            bool allowMods = comboCounter == 0;\n            if (allowMods && (modFeatures & TriadGameModifier.EFeature.CaptureNei) != 0)\n            {\n                foreach (TriadGameModifier mod in modifiers)\n                {\n                    mod.OnCheckCaptureNeis(gameState, boardPos, neis, comboList);\n                }\n            }\n\n            // CaptureMath: used only by reverse rule\n            bool isReverseActive = allowMods && ((modFeatures & TriadGameModifier.EFeature.CaptureMath) != 0);\n\n            TriadCardInstance checkCard = gameState.board[boardPos];\n            for (int sideIdx = 0; sideIdx < 4; sideIdx++)\n            {\n                int neiPos = neis[sideIdx];\n                if (neiPos >= 0 && gameState.board[neiPos] != null)\n                {\n                    TriadCardInstance neiCard = gameState.board[neiPos];\n                    if (checkCard.owner != neiCard.owner)\n                    {\n                        int numPos = checkCard.GetNumber((ETriadGameSide)sideIdx);\n                        int numOther = neiCard.GetOppositeNumber((ETriadGameSide)sideIdx);\n\n                        if (allowMods && (modFeatures & TriadGameModifier.EFeature.CaptureWeights) != 0)\n                        {\n                            // CaptureWeights: use only by fallen ace = asymetric rule, needs to know about active reverse\n                            foreach (TriadGameModifier mod in modifiers)\n                            {\n                                mod.OnCheckCaptureCardWeights(gameState, boardPos, neiPos, isReverseActive, ref numPos, ref numOther);\n                            }\n                        }\n\n                        bool bIsCaptured = (numPos > numOther);\n                        // special case: always allow CaptureMath (= reverse mod) during combo chain, see comments above\n                        {\n                            foreach (TriadGameModifier mod in modifiers)\n                            {\n                                mod.OnCheckCaptureCardMath(gameState, boardPos, neiPos, numPos, numOther, ref bIsCaptured);\n                            }\n                        }\n\n                        if (bIsCaptured)\n                        {\n                            neiCard.owner = checkCard.owner;\n                            if (comboCounter > 0)\n                            {\n                                comboList.Add(neiPos);\n                            }\n\n                            if (gameState.bDebugRules)\n                            {\n                                Logger.WriteLine(\">> \" + (comboCounter > 0 ? \"combo!\" : \"\") + \" [\" + neiPos + \"] \" + neiCard.card.Name.GetCodeName() + \" => \" + neiCard.owner);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        private void OnAllCardsPlaced(TriadGameSimulationState gameState)\n        {\n            int numBlue = (gameState.deckBlue.availableCardMask != 0) ? 1 : 0;\n            foreach (TriadCardInstance card in gameState.board)\n            {\n                if (card.owner == ETriadCardOwner.Blue)\n                {\n                    numBlue++;\n                }\n            }\n\n            int numBlueToWin = (gameState.board.Length / 2) + 1;\n            gameState.state = (numBlue > numBlueToWin) ? ETriadGameState.BlueWins :\n                (numBlue == numBlueToWin) ? ETriadGameState.BlueDraw :\n                ETriadGameState.BlueLost;\n\n            if (gameState.bDebugRules)\n            {\n                TriadCard availBlueCard = gameState.deckBlue.GetFirstAvailableCard();\n                Logger.WriteLine(\">> blue:\" + numBlue + \" (in deck:\" + ((availBlueCard != null) ? availBlueCard.Name.GetCodeName() : \"none\") + \"), required:\" + numBlueToWin + \" => \" + gameState.state);\n            }\n\n            if ((modFeatures & TriadGameModifier.EFeature.AllPlaced) != 0)\n            {\n                foreach (TriadGameModifier mod in modifiers)\n                {\n                    mod.OnAllCardsPlaced(gameState);\n                }\n            }\n        }\n\n        public static void StaticInitialize()\n        {\n            for (int idxPos = 0; idxPos < 9; idxPos++)\n            {\n                cachedNeis[idxPos] = GetNeighbors(null, idxPos);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/gamelogic/TriadGameSolver.cs",
    "content": "﻿using System.Collections.Generic;\n\nnamespace FFTriadBuddy\n{\n    public struct SolverResult\n    {\n        public float numWins;\n        public float numDraws;\n        public long numGames;\n\n        public float winChance;\n        public float drawChance;\n        public ETriadGameState expectedResult;\n        public float score;\n\n        public static SolverResult Zero = new SolverResult(0, 0, 0);\n\n        public SolverResult(float numWins, float numDraws, long numGames)\n        {\n            this.numWins = numWins;\n            this.numDraws = numDraws;\n            this.numGames = numGames;\n\n            winChance = (numGames <= 0) ? 0.0f : (numWins / numGames);\n            drawChance = (numGames <= 0) ? 0.0f : (numDraws / numGames);\n\n            if (winChance < 0.25f && drawChance < 0.25f)\n            {\n                score = winChance / 10.0f;\n                expectedResult = ETriadGameState.BlueLost;\n            }\n            else if (winChance < drawChance)\n            {\n                score = drawChance;\n                expectedResult = ETriadGameState.BlueDraw;\n            }\n            else\n            {\n                score = winChance + 10.0f;\n                expectedResult = ETriadGameState.BlueWins;\n            }\n        }\n\n        public bool IsBetterThan(SolverResult other)\n        {\n            return score > other.score;\n        }\n\n        public override string ToString()\n        {\n            return $\"{expectedResult}, score:{score}, win:{winChance:P0} ({numWins:0.##}/{numGames}), draw:{drawChance:P0} ({numDraws:0.##}/{numGames})\";\n        }\n    }\n\n    public class TriadGameSolver\n    {\n        public TriadGameSimulation simulation = new TriadGameSimulation();\n        public TriadGameAgent agent = new TriadGameAgentCarloTheExplorer();\n        public string name;\n\n        public TriadGameSolver()\n        {\n            agent.Initialize(this, 0);\n        }\n\n        public void InitializeSimulation(IEnumerable<TriadGameModifier> modsA, IEnumerable<TriadGameModifier> modsB) => simulation.Initialize(modsA, modsB);\n        public void InitializeSimulation(IEnumerable<TriadGameModifier> mods) => simulation.Initialize(mods, null);\n\n        public TriadGameSimulationState StartSimulation(TriadDeck deckBlue, TriadDeck deckRed, ETriadGameState state)\n        {\n            agent.OnSimulationStart();\n            return simulation.StartGame(deckBlue, deckRed, state);\n        }\n\n        public bool HasSimulationRule(ETriadGameSpecialMod specialRule) => simulation.HasSpecialRule(specialRule);\n\n        public float GetAgentProgress() => agent.GetProgress();\n\n        public bool FindNextMove(TriadGameSimulationState gameState, out int cardIdx, out int boardPos, out SolverResult solverResult) => agent.FindNextMove(this, gameState, out cardIdx, out boardPos, out solverResult);\n\n        public void RunSimulation(TriadGameSimulationState gameState, TriadGameAgent agentBlue, TriadGameAgent agentRed)\n        {\n            bool keepPlaying = true;\n            while (keepPlaying)\n            {\n                if (gameState.state == ETriadGameState.InProgressBlue)\n                {\n                    keepPlaying = agentBlue.FindNextMove(this, gameState, out int cardIdx, out int boardPos, out _);\n                    if (keepPlaying)\n                    {\n                        keepPlaying = simulation.PlaceCard(gameState, cardIdx, gameState.deckBlue, ETriadCardOwner.Blue, boardPos);\n                    }\n                }\n                else if (gameState.state == ETriadGameState.InProgressRed)\n                {\n                    keepPlaying = agentRed.FindNextMove(this, gameState, out int cardIdx, out int boardPos, out _);\n                    if (keepPlaying)\n                    {\n                        keepPlaying = simulation.PlaceCard(gameState, cardIdx, gameState.deckRed, ETriadCardOwner.Red, boardPos);\n                    }\n                }\n                else\n                {\n                    keepPlaying = false;\n                }\n            }\n        }\n\n        public void FindAvailableActions(TriadGameSimulationState gameState, out int availBoardMask, out int availCardsMask)\n        {\n            // prepare available board data\n            availBoardMask = 0;\n            for (int Idx = 0; Idx < gameState.board.Length; Idx++)\n            {\n                if (gameState.board[Idx] == null)\n                {\n                    availBoardMask |= (1 << Idx);\n                }\n            }\n\n            // prepare available cards data\n            availCardsMask =\n                (gameState.forcedCardIdx >= 0) ? (1 << gameState.forcedCardIdx) :\n                (gameState.state == ETriadGameState.InProgressBlue) ? gameState.deckBlue.availableCardMask :\n                gameState.deckRed.availableCardMask;\n\n            if ((simulation.modFeatures & TriadGameModifier.EFeature.FilterNext) != 0)\n            {\n                foreach (var mod in simulation.modifiers)\n                {\n                    mod.OnFilterNextCards(gameState, ref availCardsMask);\n                }\n            }\n        }\n\n        public void FindAvailableActions(TriadGameSimulationState gameState, out int availBoardMask, out int numAvailBoard, out int availCardsMask, out int numAvailCards)\n        {\n            FindAvailableActions(gameState, out availBoardMask, out availCardsMask);\n\n            numAvailBoard = CountSetBits(availBoardMask);\n            numAvailCards = CountSetBits(availCardsMask);\n\n            int CountSetBits(int value)\n            {\n                value = value - ((value >> 1) & 0x55555555);\n                value = (value & 0x33333333) + ((value >> 2) & 0x33333333);\n                return (((value + (value >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/gamelogic/tests/TriadGameScreenTests.cs",
    "content": "using MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\n\nnamespace FFTriadBuddy\n{\n    public class TriadGameScreenTests\n    {\n        private static Dictionary<string, TriadGameModifier> mapValidationRules;\n\n        private class VerifyMove\n        {\n            private ETriadCardOwner[] expectedState;\n            public TriadCard card;\n            public ETriadCardOwner owner;\n            public int boardPos;\n            public int cardIdx;\n\n            public void Load(JsonParser.ObjectValue configOb)\n            {\n                string ownerStr = configOb[\"player\"] as JsonParser.StringValue;\n                owner = (ownerStr == \"blue\") ? ETriadCardOwner.Blue : (ownerStr == \"red\") ? ETriadCardOwner.Red : ETriadCardOwner.Unknown;\n\n                boardPos = configOb[\"pos\"] as JsonParser.IntValue;\n                cardIdx = configOb[\"cardIdx\"] as JsonParser.IntValue;\n\n                if (configOb.entries.ContainsKey(\"board\"))\n                {\n                    string boardCode = configOb[\"board\"] as JsonParser.StringValue;\n                    boardCode = boardCode.Replace(\" \", \"\");\n\n                    expectedState = new ETriadCardOwner[9];\n                    for (int idx = 0; idx < expectedState.Length; idx++)\n                    {\n                        expectedState[idx] = (boardCode[idx] == 'R') ? ETriadCardOwner.Red : (boardCode[idx] == 'B') ? ETriadCardOwner.Blue : ETriadCardOwner.Unknown;\n                    }\n                }\n\n                var cardName = configOb[\"card\"] as JsonParser.StringValue;\n                if (cardName != null)\n                {\n                    card = TriadCardDB.Get().Find(cardName);\n                }\n                else\n                {\n                    var cardSides = configOb[\"card\"] as JsonParser.ArrayValue;\n\n                    int numU = cardSides[0] as JsonParser.IntValue;\n                    int numL = cardSides[1] as JsonParser.IntValue;\n                    int numD = cardSides[2] as JsonParser.IntValue;\n                    int numR = cardSides[3] as JsonParser.IntValue;\n\n                    card = TriadCardDB.Get().Find(numU, numL, numD, numR);\n                }\n            }\n\n            public bool VerifyState(TriadGameSimulationState gameState, bool debugMode)\n            {\n                if (expectedState != null)\n                {\n                    for (int idx = 0; idx < expectedState.Length; idx++)\n                    {\n                        if (gameState.board[idx].owner != expectedState[idx])\n                        {\n                            if (debugMode)\n                            {\n                                string expectedCode = \"\";\n                                string currentCode = \"\";\n                                Func<ETriadCardOwner, char> GetOwnerCode = (owner) => (owner == ETriadCardOwner.Blue) ? 'B' : (owner == ETriadCardOwner.Red) ? 'R' : '.';\n\n                                for (int codeIdx = 0; codeIdx < 9; codeIdx++)\n                                {\n                                    if (codeIdx == 3 || codeIdx == 6) { expectedCode += ' '; currentCode += ' '; }\n\n                                    expectedCode += GetOwnerCode(gameState.board[codeIdx].owner);\n                                    currentCode += GetOwnerCode(expectedState[codeIdx]);\n                                }\n\n                                Logger.WriteLine(\"Failed, mismatch at [{0}]! Expected:{1}, got{2}\", idx, expectedCode, currentCode);\n                            }\n                            return false;\n                        }\n                    }\n                }\n\n                return true;\n            }\n        }\n\n        private static void CopyGameStateToScreen(TriadGameSimulationState testGameData, ScannerTriad.GameState screenGame)\n        {\n            for (int idx = 0; idx < 9; idx++)\n            {\n                screenGame.board[idx] = testGameData.board[idx] != null ? testGameData.board[idx].card : null;\n                screenGame.boardOwner[idx] = testGameData.board[idx] != null ? testGameData.board[idx].owner : ETriadCardOwner.Unknown;\n            }\n\n            for (int idx = 0; idx < 5; idx++)\n            {\n                screenGame.blueDeck[idx] = testGameData.deckBlue.GetCard(idx);\n\n                if (testGameData.deckRed.IsPlaced(idx))\n                {\n                    screenGame.redDeck[idx] = null;\n                }\n            }\n        }\n\n        public static void RunTest(string configPath, bool debugMode)\n        {\n            string testName = System.IO.Path.GetFileNameWithoutExtension(configPath);\n\n            string configText = System.IO.File.ReadAllText(configPath);\n            JsonParser.ObjectValue rootOb = JsonParser.ParseJson(configText);\n            if (rootOb[\"type\"] != \"Screen\")\n            {\n                return;\n            }\n\n            ScannerTriad.VerifyConfig configData = new ScannerTriad.VerifyConfig();\n            configData.Load(rootOb);\n\n            // setup npc & modifiers\n            TriadNpc testNpc = TriadNpcDB.Get().Find(configData.npc);\n            if (testNpc == null)\n            {\n                string exceptionMsg = string.Format(\"Test {0} failed! Can't find npc: {1}\", testName, configData.npc);\n                throw new Exception(exceptionMsg);\n            }\n\n            ScannerTriad.GameState screenGame = new ScannerTriad.GameState();\n            if (mapValidationRules == null)\n            {\n                mapValidationRules = new Dictionary<string, TriadGameModifier>();\n                foreach (TriadGameModifier mod in ImageHashDB.Get().modObjects)\n                {\n                    mapValidationRules.Add(mod.GetCodeName(), mod);\n                }\n            }\n\n            foreach (string modName in configData.rules)\n            {\n                screenGame.mods.Add(mapValidationRules[modName]);\n            }\n\n            Func<ScannerTriad.VerifyCard, TriadCard> ConvertToTriadCard = configCard =>\n            {\n                if (configCard.state == ScannerTriad.ECardState.None) { return null; }\n                if (configCard.state == ScannerTriad.ECardState.Hidden) { return TriadCardDB.Get().hiddenCard; }\n\n                TriadCard matchingCard = !string.IsNullOrEmpty(configCard.name) ?\n                    TriadCardDB.Get().Find(configCard.name) :\n                    TriadCardDB.Get().Find(configCard.sides[0], configCard.sides[1], configCard.sides[2], configCard.sides[3]);\n\n                if (matchingCard == null)\n                {\n                    string exceptionMsg = string.Format(\"Test {0} failed! Can't match validation card: '{1}' [{2},{3},{4},{5}]\", testName,\n                        configCard.name, configCard.sides[0], configCard.sides[1], configCard.sides[2], configCard.sides[3]);\n                    throw new Exception(exceptionMsg);\n                }\n\n                return matchingCard;\n            };\n\n            bool needsLockedBlue = false;\n            for (int idx = 0; idx < 5; idx++)\n            {\n                screenGame.blueDeck[idx] = ConvertToTriadCard(configData.deckBlue[idx]);\n                screenGame.redDeck[idx] = ConvertToTriadCard(configData.deckRed[idx]);\n\n                if (configData.deckBlue[idx].state == ScannerTriad.ECardState.Locked)\n                {\n                    needsLockedBlue = true;\n                }\n            }\n\n            if (needsLockedBlue)\n            {\n                for (int idx = 0; idx < 5; idx++)\n                {\n                    if (configData.deckBlue[idx].state == ScannerTriad.ECardState.Visible)\n                    {\n                        screenGame.forcedBlueCard = screenGame.blueDeck[idx];\n                        break;\n                    }\n                }\n            }\n\n            for (int idx = 0; idx < 9; idx++)\n            {\n                screenGame.board[idx] = ConvertToTriadCard(configData.board[idx]);\n                screenGame.boardOwner[idx] =\n                    configData.board[idx].state == ScannerTriad.ECardState.PlacedBlue ? ETriadCardOwner.Blue :\n                    configData.board[idx].state == ScannerTriad.ECardState.PlacedRed ? ETriadCardOwner.Red :\n                    ETriadCardOwner.Unknown;\n            }\n\n            TriadGameScreenMemory screenMemory = new TriadGameScreenMemory { logScan = false };\n            if (!rootOb.entries.ContainsKey(\"moves\"))\n            {\n                screenMemory.OnNewScan(screenGame, testNpc);\n                bool hasMove = screenMemory.gameSolver.FindNextMove(screenMemory.gameState, out var dummyCardIdx, out var dummyBoardPos, out var bestChance);\n\n                if (!hasMove || (bestChance.expectedResult == ETriadGameState.BlueLost && bestChance.winChance <= 0.0f && bestChance.drawChance <= 0.0f))\n                {\n                    string exceptionMsg = string.Format(\"Test {0} failed! Can't find move!\", testName);\n                    throw new Exception(exceptionMsg);\n                }\n            }\n            else\n            {\n                debugMode = true;\n\n                TriadGameSimulation testSession = new TriadGameSimulation();\n                testSession.Initialize(screenGame.mods, null);\n\n                TriadGameSimulationState testGameState = new TriadGameSimulationState() { bDebugRules = debugMode };\n                testGameState.deckBlue = new TriadDeckInstanceManual(new TriadDeck(screenGame.blueDeck));\n                testGameState.deckRed = new TriadDeckInstanceManual(testNpc.Deck);\n\n                bool shouldForceBlueSelection = true;\n\n                JsonParser.ArrayValue moveArr = rootOb.entries[\"moves\"] as JsonParser.ArrayValue;\n                for (int idx = 0; idx < moveArr.entries.Count; idx++)\n                {\n                    var move = new VerifyMove();\n                    move.Load(moveArr.entries[idx] as JsonParser.ObjectValue);\n\n                    if (debugMode) { Logger.WriteLine(\"move[{0}]: [{1}] {2}: {3}\", idx, move.boardPos, move.owner, move.card); }\n\n                    if (move.owner == ETriadCardOwner.Blue)\n                    {\n                        CopyGameStateToScreen(testGameState, screenGame);\n                        if (shouldForceBlueSelection)\n                        {\n                            screenGame.forcedBlueCard = screenGame.blueDeck[move.cardIdx];\n                        }\n\n                        screenMemory.OnNewScan(screenGame, testNpc);\n                        screenMemory.gameSolver.FindNextMove(screenMemory.gameState, out int solverCardIdx, out int solverBoardPos, out SolverResult bestChance);\n\n                        if (debugMode)\n                        {\n                            var solverTriadCard = testGameState.deckBlue.GetCard(solverCardIdx);\n                            Logger.WriteLine(\"solver: {0} -> board[{1}], chance: {2}\", solverTriadCard.Name.GetCodeName(), solverBoardPos, bestChance.expectedResult);\n\n                            if (solverBoardPos != move.boardPos)\n                            {\n                                Logger.WriteLine(\"  >> MISMATCH!\");\n                            }\n                        }\n                    }\n\n                    testGameState.state = move.owner == ETriadCardOwner.Blue ? ETriadGameState.InProgressBlue : ETriadGameState.InProgressRed;\n                    testSession.PlaceCard(testGameState, move.card, move.owner, move.boardPos);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/gamelogic/tests/TriadGameTests.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\n\nnamespace FFTriadBuddy\n{\n#if DEBUG\n    public class TriadGameTests\n    {\n        private static Dictionary<string, TriadGameModifier> mapValidationRules;\n\n        private class VerifyMove\n        {\n            private ETriadCardOwner[] expectedState;\n            public TriadCard card;\n            public ETriadCardOwner owner;\n            public int boardPos;\n\n            public void Load(JsonParser.ObjectValue configOb)\n            {\n                string ownerStr = configOb[\"player\"] as JsonParser.StringValue;\n                owner = (ownerStr == \"blue\") ? ETriadCardOwner.Blue : (ownerStr == \"red\") ? ETriadCardOwner.Red : ETriadCardOwner.Unknown;\n\n                boardPos = configOb[\"pos\"] as JsonParser.IntValue;\n\n                if (configOb.entries.ContainsKey(\"board\"))\n                {\n                    string boardCode = configOb[\"board\"] as JsonParser.StringValue;\n                    boardCode = boardCode.Replace(\" \", \"\");\n\n                    expectedState = new ETriadCardOwner[9];\n                    for (int idx = 0; idx < expectedState.Length; idx++)\n                    {\n                        expectedState[idx] = (boardCode[idx] == 'R') ? ETriadCardOwner.Red : (boardCode[idx] == 'B') ? ETriadCardOwner.Blue : ETriadCardOwner.Unknown;\n                    }\n                }\n\n                var cardName = configOb[\"card\"] as JsonParser.StringValue;\n                if (cardName != null)\n                {\n                    card = TriadCardDB.Get().Find(cardName);\n                }\n                else\n                {\n                    var cardSides = configOb[\"card\"] as JsonParser.ArrayValue;\n\n                    int numU = cardSides[0] as JsonParser.IntValue;\n                    int numL = cardSides[1] as JsonParser.IntValue;\n                    int numD = cardSides[2] as JsonParser.IntValue;\n                    int numR = cardSides[3] as JsonParser.IntValue;\n\n                    card = TriadCardDB.Get().Find(numU, numL, numD, numR);\n                }\n            }\n\n            public bool VerifyState(TriadGameSimulationState gameState, bool debugMode)\n            {\n                if (expectedState != null)\n                {\n                    for (int idx = 0; idx < expectedState.Length; idx++)\n                    {\n                        if (gameState.board[idx] == null && expectedState[idx] == ETriadCardOwner.Unknown)\n                        {\n                            continue;\n                        }\n\n                        if (gameState.board[idx].owner != expectedState[idx])\n                        {\n                            if (debugMode)\n                            {\n                                string expectedCode = \"\";\n                                string currentCode = \"\";\n                                Func<ETriadCardOwner, char> GetOwnerCode = (owner) => (owner == ETriadCardOwner.Blue) ? 'B' : (owner == ETriadCardOwner.Red) ? 'R' : '.';\n\n                                for (int codeIdx = 0; codeIdx < 9; codeIdx++)\n                                {\n                                    if (codeIdx == 3 || codeIdx == 6) { expectedCode += ' '; currentCode += ' '; }\n\n                                    expectedCode += GetOwnerCode(gameState.board[codeIdx]?.owner ?? ETriadCardOwner.Unknown);\n                                    currentCode += GetOwnerCode(expectedState[codeIdx]);\n                                }\n\n                                Logger.WriteLine(\"Failed, mismatch at [{0}]! Expected:{1}, got{2}\", idx, expectedCode, currentCode);\n                            }\n                            return false;\n                        }\n                    }\n                }\n\n                return true;\n            }\n        }\n\n        public static void RunTest(string configPath, bool debugMode)\n        {\n            string testName = System.IO.Path.GetFileNameWithoutExtension(configPath);\n\n            string configText = System.IO.File.ReadAllText(configPath);\n            JsonParser.ObjectValue configOb = JsonParser.ParseJson(configText);\n            if (configOb[\"type\"] != \"Solver\")\n            {\n                return;\n            }\n\n            // intial state\n            ScannerTriad.VerifyConfig configData = new ScannerTriad.VerifyConfig();\n            configData.Load(configOb);\n\n            if (mapValidationRules == null)\n            {\n                mapValidationRules = new Dictionary<string, TriadGameModifier>();\n                foreach (TriadGameModifier mod in ImageHashDB.Get().modObjects)\n                {\n                    mapValidationRules.Add(mod.GetCodeName(), mod);\n                }\n            }\n\n            List<TriadGameModifier> configMods = new List<TriadGameModifier>();\n            foreach (string modName in configData.rules)\n            {\n                configMods.Add(mapValidationRules[modName]);\n            }\n\n            TriadGameSimulation testSession = new TriadGameSimulation();\n            testSession.Initialize(configMods);\n\n            TriadGameSimulationState testGameData = new TriadGameSimulationState() { bDebugRules = debugMode };\n\n            if (configData.board.Length > 0)\n            {\n                Func<ScannerTriad.VerifyCard, TriadCard> ConvertToTriadCard = configCard =>\n                {\n                    if (configCard.state == ScannerTriad.ECardState.None) { return null; }\n                    if (configCard.state == ScannerTriad.ECardState.Hidden) { return TriadCardDB.Get().hiddenCard; }\n\n                    TriadCard matchingCard = !string.IsNullOrEmpty(configCard.name) ?\n                        TriadCardDB.Get().Find(configCard.name) :\n                        TriadCardDB.Get().Find(configCard.sides[0], configCard.sides[1], configCard.sides[2], configCard.sides[3]);\n\n                    if (matchingCard == null)\n                    {\n                        string exceptionMsg = string.Format(\"Test {0} failed! Can't match validation card: '{1}' [{2},{3},{4},{5}]\", testName,\n                            configCard.name, configCard.sides[0], configCard.sides[1], configCard.sides[2], configCard.sides[3]);\n                        throw new Exception(exceptionMsg);\n                    }\n\n                    return matchingCard;\n                };\n\n                for (int idx = 0; idx < configData.board.Length; idx++)\n                {\n                    var configState = configData.board[idx].state;\n                    if (configState != ScannerTriad.ECardState.None)\n                    {\n                        testGameData.board[idx] = new TriadCardInstance(ConvertToTriadCard(configData.board[idx]),\n                            (configState == ScannerTriad.ECardState.PlacedBlue) ? ETriadCardOwner.Blue :\n                            (configState == ScannerTriad.ECardState.PlacedRed) ? ETriadCardOwner.Red :\n                            ETriadCardOwner.Unknown);\n                    }\n                }\n            }\n\n            var deckRed = new TriadDeck();\n            var deckBlue = new TriadDeck();\n\n            testGameData.deckBlue = new TriadDeckInstanceManual(deckBlue);\n            testGameData.deckRed = new TriadDeckInstanceManual(deckRed);\n\n            JsonParser.ArrayValue moveArr = configOb.entries[\"moves\"] as JsonParser.ArrayValue;\n            for (int idx = 0; idx < moveArr.entries.Count; idx++)\n            {\n                var move = new VerifyMove();\n                move.Load(moveArr.entries[idx] as JsonParser.ObjectValue);\n\n                var useDeck = (move.owner == ETriadCardOwner.Blue) ? deckBlue : deckRed;\n                useDeck.knownCards.Add(move.card);\n                if (idx == 0)\n                {\n                    testGameData.state = (move.owner == ETriadCardOwner.Blue) ? ETriadGameState.InProgressBlue : ETriadGameState.InProgressRed;\n                }\n\n                if (debugMode) { Logger.WriteLine(\"move[{0}]: [{1}] {2}: {3}\", idx, move.boardPos, move.owner, move.card); }\n\n                bool result = testSession.PlaceCard(testGameData, move.card, move.owner, move.boardPos);\n                if (!result)\n                {\n                    string exceptionMsg = string.Format(\"Test {0} failed! Can't place card!\", testName);\n                    throw new Exception(exceptionMsg);\n                }\n\n                result = move.VerifyState(testGameData, debugMode);\n                if (!result)\n                {\n                    string exceptionMsg = string.Format(\"Test {0} failed! Finished with bad state!\", testName);\n                    throw new Exception(exceptionMsg);\n                }\n            }\n        }\n\n        public static void RunSolverStressTest()\n        {\n            int numIterations = 1000 * 1000;\n            Logger.WriteLine(\"Solver speed testing start, numIterations:\" + numIterations);\n\n            Stopwatch timer = new Stopwatch();\n            timer.Start();\n\n            TriadDeck testDeck = new TriadDeck(new int[] { 10, 20, 30, 40, 50 });\n            TriadNpc testNpc = TriadNpcDB.Get().Find(\"Garima\");\n\n            var solver = new TriadGameSolver();\n            solver.InitializeSimulation(testNpc.Rules);\n\n            var agent = new TriadGameAgentRandom(solver, 0);\n            for (int Idx = 0; Idx < numIterations; Idx++)\n            {\n                var gameState = solver.StartSimulation(testDeck, testNpc.Deck, ETriadGameState.InProgressBlue);\n                solver.RunSimulation(gameState, agent, agent);\n            }\n\n            timer.Stop();\n            Logger.WriteLine(\"Solver speed testing finished, time taken:\" + timer.ElapsedMilliseconds + \"ms\");\n\n            if (Debugger.IsAttached)\n            {\n                Debugger.Break();\n            }\n        }\n\n        private static int[][] BuildDeckPermutations()\n        {\n            var permutationList = new int[120][];\n            int ListIdx = 0;\n            for (int IdxP0 = 0; IdxP0 < 5; IdxP0++)\n            {\n                for (int IdxP1 = 0; IdxP1 < 5; IdxP1++)\n                {\n                    if (IdxP1 == IdxP0) { continue; }\n                    for (int IdxP2 = 0; IdxP2 < 5; IdxP2++)\n                    {\n                        if (IdxP2 == IdxP0 || IdxP2 == IdxP1) { continue; }\n                        for (int IdxP3 = 0; IdxP3 < 5; IdxP3++)\n                        {\n                            if (IdxP3 == IdxP0 || IdxP3 == IdxP1 || IdxP3 == IdxP2) { continue; }\n                            for (int IdxP4 = 0; IdxP4 < 5; IdxP4++)\n                            {\n                                if (IdxP4 == IdxP0 || IdxP4 == IdxP1 || IdxP4 == IdxP2 || IdxP4 == IdxP3) { continue; }\n\n                                permutationList[ListIdx] = new int[5] { IdxP0, IdxP1, IdxP2, IdxP3, IdxP4 };\n                                ListIdx++;\n                            }\n                        }\n                    }\n                }\n            }\n\n            return permutationList;\n        }\n\n        private static int[][] deckPermutations;\n        private static int[] PickRandomPermutation(Random rand)\n        {\n            if (deckPermutations == null)\n            {\n                deckPermutations = BuildDeckPermutations();\n            }\n\n            return deckPermutations[rand.Next(deckPermutations.Length)];\n        }\n\n        class SolverAccTestInfo\n        {\n            public TriadGameAgent agentBlue;\n            public TriadGameAgent agentRed;\n            public int numWins = 0;\n            public int numControlled = 0;\n            public float elapsedSeconds = 0.0f;\n            public List<float> predictionSteps;\n        }\n\n        private static void PlayTestGame(SolverAccTestInfo testInfo, TriadGameSolver solver, TriadGameSimulationState gameState, Random sessionRand)\n        {\n            int[] blueDeckOrder = solver.HasSimulationRule(ETriadGameSpecialMod.BlueCardSelection) ? PickRandomPermutation(sessionRand) : null;\n\n            bool keepPlaying = true;\n            while (keepPlaying)\n            {\n                if (gameState.state == ETriadGameState.InProgressBlue)\n                {\n                    gameState.forcedCardIdx = (blueDeckOrder != null) ? blueDeckOrder[gameState.deckBlue.numPlaced] : -1;\n\n                    keepPlaying = testInfo.agentBlue.FindNextMove(solver, gameState, out int cardIdx, out int boardPos, out var blueResult);\n                    if (keepPlaying)\n                    {\n                        testInfo.predictionSteps[gameState.deckBlue.numPlaced] += blueResult.winChance;\n\n                        keepPlaying = solver.simulation.PlaceCard(gameState, cardIdx, gameState.deckBlue, ETriadCardOwner.Blue, boardPos);\n                    }\n                }\n                else if (gameState.state == ETriadGameState.InProgressRed)\n                {\n                    gameState.forcedCardIdx = -1;\n\n                    keepPlaying = testInfo.agentRed.FindNextMove(solver, gameState, out int cardIdx, out int boardPos, out var dummyResult);\n                    if (keepPlaying)\n                    {\n                        keepPlaying = solver.simulation.PlaceCard(gameState, cardIdx, gameState.deckRed, ETriadCardOwner.Red, boardPos);\n                    }\n                }\n                else\n                {\n                    keepPlaying = false;\n                }\n            }\n\n            int numBlue = (gameState.deckBlue.availableCardMask != 0) ? 1 : 0;\n            foreach (TriadCardInstance card in gameState.board)\n            {\n                numBlue += (card != null && card.owner == ETriadCardOwner.Blue) ? 1 : 0;\n            }\n\n            testInfo.numControlled += numBlue;\n            testInfo.numWins += (gameState.state == ETriadGameState.BlueWins) ? 1 : 0;\n        }\n\n        public static void RunSolverAccuracyTests()\n        {\n            int numIterations = 200;\n            var deckPermutations = BuildDeckPermutations();\n\n            Logger.WriteLine(\"Solver accuracy testing start, numIterations:\" + numIterations);\n\n            var deckRand = new Random(20);\n            var deckCards = new int[5] { 61, 248, 113, 191, 87 };\n            var cardDB = TriadCardDB.Get();\n            int idx = 0;\n            while (idx < deckCards.Length)\n            {\n                int cardIdx = deckRand.Next(cardDB.cards.Count);\n                if (cardDB.cards[cardIdx].IsValid())\n                {\n                    deckCards[idx] = cardIdx;\n                    idx++;\n                }\n            }\n\n            TriadDeck testDeck = new TriadDeck(deckCards);\n            //TriadNpc testNpc = TriadNpcDB.Get().Find(\"Garima\");\n            //TriadNpc testNpc = TriadNpcDB.Get().Find(\"Swift\");\n            TriadNpc testNpc = TriadNpcDB.Get().Find(\"Aurifort of the Three Clubs\");\n\n            var solver = new TriadGameSolver();\n            solver.InitializeSimulation(testNpc.Rules);\n\n            var playerAgents = new List<TriadGameAgent>();\n            playerAgents.Add(new TriadGameAgentDerpyCarlo());\n            playerAgents.Add(new TriadGameAgentCarloTheExplorer());\n            playerAgents.Add(new TriadGameAgentCarloScored());\n\n            // single iteration: in depth testing for single agent\n            if (numIterations == 1)\n            {\n                playerAgents.Clear();\n                playerAgents.Add(new TriadGameAgentCarloScored());\n\n                playerAgents[0].debugFlags = TriadGameAgent.DebugFlags.AgentInitialize | TriadGameAgent.DebugFlags.ShowMoveStart | TriadGameAgent.DebugFlags.ShowMoveDetails;\n            }\n\n            var testResults = new List<SolverAccTestInfo>();\n            foreach (var agent in playerAgents)\n            {\n                var testInfo = new SolverAccTestInfo() { agentBlue = agent, agentRed = new TriadGameAgentRandom() };\n                testInfo.agentBlue.Initialize(solver, 0);\n                testInfo.agentRed.Initialize(solver, 0);\n\n                testInfo.predictionSteps = new List<float>();\n                for (idx = 0; idx < 5; idx++)\n                {\n                    testInfo.predictionSteps.Add(0.0f);\n                }\n\n                testResults.Add(testInfo);\n            }\n\n            TriadGameAgentRandom.UseEqualDistribution = true;\n\n            var iterCounter = 0;\n            foreach (var testInfo in testResults)\n            {\n                var sessionRand = new Random(0);\n                Stopwatch timer = new Stopwatch();\n                timer.Start();\n\n                for (int Idx = 0; Idx < numIterations; Idx++)\n                {\n                    iterCounter++;\n                    if (iterCounter % 20 == 0)\n                    {\n                        Logger.WriteLine(\">> {0}/{1}\", iterCounter, numIterations * testResults.Count);\n                    }\n\n                    var initialState = solver.StartSimulation(testDeck, testNpc.Deck, ETriadGameState.InProgressRed);\n                    PlayTestGame(testInfo, solver, initialState, sessionRand);\n                }\n\n                timer.Stop();\n                testInfo.elapsedSeconds = timer.ElapsedMilliseconds / 1000.0f;\n            }\n\n            Logger.WriteLine(\"Solver accuracy testing finished\");\n            foreach (var testInfo in testResults)\n            {\n                string predictionDesc = \"\";\n                for (idx = 0; idx < testInfo.predictionSteps.Count - 1; idx++)\n                {\n                    if (predictionDesc.Length > 0) { predictionDesc += \", \"; }\n                    predictionDesc += $\"{(testInfo.predictionSteps[idx] / numIterations):P0}\";\n                }\n\n                Logger.WriteLine(\"[{0}] score:{1:P2}, control:{2:0.##}, time taken:{3}s, predictions:{4}\",\n                    testInfo.agentBlue.agentName,\n                    (float)testInfo.numWins / numIterations,\n                    (float)testInfo.numControlled / numIterations,\n                    testInfo.elapsedSeconds,\n                    predictionDesc);\n            }\n\n            if (Debugger.IsAttached)\n            {\n                Debugger.Break();\n            }\n        }\n\n        private static TriadNpc PickRandomNpc(Random rand)\n        {\n            var npcList = TriadNpcDB.Get().npcs;\n            TriadNpc npcOb = null;\n            while (npcOb == null)\n            {\n                npcOb = npcList[rand.Next(npcList.Count)];\n            }\n\n            return npcOb;\n        }\n\n        private static Dictionary<int, List<TriadCard>> mapCardRarities;\n        private static TriadCard PickRandomCard(Random rand, int rarity)\n        {\n            if (mapCardRarities == null)\n            {\n                mapCardRarities = new Dictionary<int, List<TriadCard>>();\n                for (int idx = 0; idx < 5; idx++)\n                {\n                    mapCardRarities.Add(idx, new List<TriadCard>());\n                }\n\n                var cardList = TriadCardDB.Get().cards;\n                foreach (var card in cardList)\n                {\n                    if (card != null && card.IsValid())\n                    {\n                        mapCardRarities[(int)card.Rarity].Add(card);\n                    }\n                }\n            }\n\n            var rarityList = mapCardRarities[rarity];\n            return rarityList[rand.Next(rarityList.Count)];\n        }\n\n        private static TriadDeck PickRandomDeck(Random rand)\n        {\n            int[] rarityCount = new int[5];\n            int rarityType = rand.Next(10);\n            if (rarityType < 2)\n            {\n                rarityCount[0] = 4;\n                rarityCount[1] = 1;\n            }\n            else if (rarityType < 5)\n            {\n                rarityCount[0] = 1;\n                rarityCount[1] = 2;\n                rarityCount[2] = 1;\n                rarityCount[3] = 1;\n                rarityCount[4] = 0;\n            }\n            else\n            {\n                rarityCount[2] = 3;\n                rarityCount[3] = 1;\n                rarityCount[4] = 1;\n            }\n\n            var listCards = new List<TriadCard>();\n            for (int idxRarity = 0; idxRarity < rarityCount.Length; idxRarity++)\n            {\n                int numAdded = 0;\n                while (numAdded < rarityCount[idxRarity])\n                {\n                    var cardOb = PickRandomCard(rand, idxRarity);\n                    if (!listCards.Contains(cardOb))\n                    {\n                        listCards.Add(cardOb);\n                        numAdded++;\n                    }\n                }\n            }\n\n            return new TriadDeck(listCards);\n        }\n\n        public static void GenerateAccuracyTrainingData()\n        {\n            int numGames = 100;\n            int numSamples = 200;\n            int seed = 0;\n\n            TriadGameAgentRandom.UseEqualDistribution = true;\n            var rand = new Random(seed);\n\n            var testLines = new List<string>();\n            testLines.Add(\"seed,deck0,deck1,deck2,deck3,deck4,npc,winChance\");\n\n            for (int idxSample = 0; idxSample < numSamples; idxSample++)\n            {\n                Logger.WriteLine($\">> {idxSample + 1}/{numSamples}\");\n\n                var sessionSeed = rand.Next();\n                var sessionRand = new Random(sessionSeed);\n\n                var testNpc = PickRandomNpc(sessionRand);\n                var testDeck = PickRandomDeck(sessionRand);\n\n                var solver = new TriadGameSolver();\n                solver.InitializeSimulation(testNpc.Rules);\n\n                var testInfo = new SolverAccTestInfo() { agentBlue = new TriadGameAgentCarloTheExplorer(), agentRed = new TriadGameAgentRandom() };\n                testInfo.agentBlue.Initialize(solver, 0);\n                testInfo.agentRed.Initialize(solver, 0);\n\n                testInfo.predictionSteps = new List<float>();\n                for (int idx = 0; idx < 5; idx++)\n                {\n                    testInfo.predictionSteps.Add(0.0f);\n                }\n\n                for (int idxGame = 0; idxGame < numGames; idxGame++)\n                {\n                    var initialState = solver.StartSimulation(testDeck, testNpc.Deck, ETriadGameState.InProgressRed);\n                    PlayTestGame(testInfo, solver, initialState, sessionRand);\n                }\n\n                var winChance = 1.0f * testInfo.numWins / numGames;\n                testLines.Add($\"{sessionSeed},{testDeck.knownCards[0].Id},{testDeck.knownCards[1].Id},{testDeck.knownCards[2].Id},{testDeck.knownCards[3].Id},{testDeck.knownCards[4].Id},{testNpc.Id},{winChance}\");\n            }\n\n            System.IO.File.WriteAllLines(\"predictionDump.csv\", testLines);\n        }\n    }\n#endif // DEBUG\n}\n"
  },
  {
    "path": "sources/googleapi/GoogleClientMissingIdentifiers.cs",
    "content": "using System;\n\n#warning \"API keys not available, cloud storage access will be disabled\"\n\nnamespace MgAl2O4.GoogleAPI\n{    \n    public class GoogleClientIdentifiers\n    {\n        public static GoogleOAuth2.ClientIdentifier Keys = null;\n    }\n}\n"
  },
  {
    "path": "sources/googleapi/GoogleDriveService.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Net;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace MgAl2O4.GoogleAPI\n{\n    // Google API package is doing awesome job, but.... it adds lots of dll dependecies\n    // I want to keep program as dll free as it gets, so it means: REWRITE ALL THE THINGS!\n    // (jk, I like doing stuff myself and learning how it works under the hood too much)\n\n    public class GoogleDriveService\n    {\n        private static readonly string FilesApi = \"https://www.googleapis.com/drive/v3/files\";\n        private static readonly string UploadApi = \"https://www.googleapis.com/upload/drive/v3/files\";\n\n        public enum EState\n        {\n            NoErrors,\n            NotInitialized,\n            NotAuthorized,\n            AuthInProgress,\n            ApiFailure,\n        }\n\n        private readonly GoogleOAuth2.ClientIdentifier clientId;\n        private GoogleOAuth2.Token authToken;\n        private EState currentState;\n        private string lastApiResponse;\n\n        private Dictionary<string, string> mapFileIds;\n\n        private class Reply\n        {\n            public bool bIsSuccessful;\n            public string contentBody;\n            public HttpWebResponse response;\n        }\n\n        public GoogleDriveService(GoogleOAuth2.ClientIdentifier clientIdentifier, GoogleOAuth2.Token savedToken)\n        {\n            clientId = clientIdentifier;\n            authToken = savedToken;\n            mapFileIds = new Dictionary<string, string>();\n            currentState = ((savedToken == null) || !savedToken.IsValidForRefresh()) ? EState.NotAuthorized : EState.NotInitialized;\n            lastApiResponse = null;\n        }\n\n        public GoogleOAuth2.Token GetAuthToken()\n        {\n            return authToken;\n        }\n\n        public EState GetState()\n        {\n            return currentState;\n        }\n\n        public string GetLastApiResponse()\n        {\n            return lastApiResponse;\n        }\n\n        public int GetFileCount()\n        {\n            return mapFileIds.Count;\n        }\n\n        public async Task InitFileList()\n        {\n            string requestUri = FilesApi +\n                \"?list\" +\n                \"&spaces=appDataFolder\" +\n                \"&q=\" + Uri.EscapeDataString(\"trashed=false\");\n\n            bool bHasValidResponse = false;\n\n            Reply reply = await HandleRequest(\"GET\", requestUri);\n            if (reply.bIsSuccessful)\n            {\n                JsonParser.ObjectValue jsonOb = JsonParser.ParseJson(reply.contentBody);\n                if (jsonOb != null)\n                {\n                    JsonParser.ArrayValue fileArr = (JsonParser.ArrayValue)jsonOb[\"files\"];\n                    foreach (JsonParser.Value entry in fileArr.entries)\n                    {\n                        JsonParser.ObjectValue entryOb = (JsonParser.ObjectValue)entry;\n\n                        string mapKey = entryOb[\"name\"];\n                        mapFileIds.Remove(mapKey);\n                        mapFileIds.Add(mapKey, entryOb[\"id\"]);\n                    }\n\n                    bHasValidResponse = true;\n                }\n                else\n                {\n                    // null response is still valid (no settings saved yet)\n                    bHasValidResponse = true;\n                }\n            }\n\n            UpdateCurrentState(bHasValidResponse);\n        }\n\n        public async Task<bool> UploadTextFile(string fileName, string fileContent)\n        {\n            bool bResult = false;\n            if (!mapFileIds.ContainsKey(fileName))\n            {\n                string uploadRequestUri = UploadApi + \"?uploadType=multipart\";\n                string uploadMeta = \"{\\\"name\\\":\\\"\" + fileName + \"\\\",parents:[\\\"appDataFolder\\\"]}\";\n\n                Reply reply = await HandleRequest(\"POST\", uploadRequestUri, uploadMeta, fileContent);\n                if (reply.bIsSuccessful)\n                {\n                    JsonParser.ObjectValue jsonOb = JsonParser.ParseJson(reply.contentBody);\n                    if (jsonOb != null)\n                    {\n                        string fileId = jsonOb[\"id\"];\n                        mapFileIds.Remove(fileName);\n                        mapFileIds.Add(fileName, fileId);\n                        bResult = true;\n                    }\n                }\n            }\n            else\n            {\n                string patchRequestUri = UploadApi +\n                    \"/\" + mapFileIds[fileName] +\n                    \"?uploadType=media\";\n\n                Reply reply = await HandleRequest(\"PATCH\", patchRequestUri, fileContent);\n                bResult = reply.bIsSuccessful;\n            }\n\n            UpdateCurrentState(bResult);\n            return bResult;\n        }\n\n        public async Task<string> DownloadTextFile(string fileName)\n        {\n            if (!mapFileIds.ContainsKey(fileName))\n            {\n                return null;\n            }\n\n            string getRequestUri = FilesApi +\n                \"/\" + mapFileIds[fileName] +\n                \"?alt=media\";\n\n            Reply reply = await HandleRequest(\"GET\", getRequestUri);\n            if (reply.bIsSuccessful)\n            {\n                UpdateCurrentState(true);\n                return reply.contentBody;\n            }\n\n            UpdateCurrentState(false);\n            return null;\n        }\n\n        private void UpdateCurrentState(bool bHasValidApiResponse)\n        {\n            if (currentState <= EState.NotInitialized && !bHasValidApiResponse)\n            {\n                currentState = EState.ApiFailure;\n            }\n        }\n\n        private async Task<Reply> HandleRequest(string method, string requestUri, params string[] requestBody)\n        {\n            Reply result = new Reply();\n            result.bIsSuccessful = false;\n\n            currentState = EState.AuthInProgress;\n\n            GoogleOAuth2.Token activeToken = await GoogleOAuth2.GetAuthorizationToken(clientId, authToken);\n            if (activeToken != null && activeToken.IsValidForAuth())\n            {\n                authToken = activeToken;\n                currentState = EState.NoErrors;\n\n                HttpWebRequest request = WebRequest.CreateHttp(requestUri);\n                request.Method = method;\n                request.Headers.Add(HttpRequestHeader.Authorization, \"Bearer \" + authToken.accessToken);\n                SetRequestContent(request, requestBody);\n\n                WebResponse rawResponse = null;\n                try\n                {\n                    rawResponse = await request.GetResponseAsync();\n                }\n                catch (WebException ex)\n                {\n                    lastApiResponse = ex.Message;\n                }\n                catch (Exception ex)\n                {\n                    lastApiResponse = \"Exception: \" + ex;\n                }\n\n                HttpWebResponse response = (HttpWebResponse)rawResponse;\n                result.response = response;\n\n                if (response != null)\n                {\n                    lastApiResponse = response.StatusDescription;\n                    if (response.StatusCode == HttpStatusCode.OK)\n                    {\n                        string responseBody = null;\n                        if (response.ContentLength > 0)\n                        {\n                            byte[] contentBytes = new byte[response.ContentLength];\n                            Stream contentStream = response.GetResponseStream();\n                            contentStream.Read(contentBytes, 0, contentBytes.Length);\n                            contentStream.Close();\n\n                            responseBody = Encoding.UTF8.GetString(contentBytes);\n                        }\n\n                        result.bIsSuccessful = true;\n                        result.contentBody = responseBody;\n                    }\n                }\n            }\n            else\n            {\n                currentState = EState.NotAuthorized;\n            }\n\n            return result;\n        }\n\n        private void SetRequestContent(HttpWebRequest request, string[] parts)\n        {\n            byte[] contentBytes = null;\n\n            if (parts.Length == 0)\n            {\n                request.ContentType = \"application/json; charset=UTF-8\";\n            }\n            else if (parts.Length == 1)\n            {\n                bool bJsonRequest = (parts[0].Length > 1) && (parts[0][0] == '{');\n                request.ContentType = (bJsonRequest ? \"application/json\" : \"text/plain\") + \"; charset=UTF-8\";\n\n                contentBytes = Encoding.UTF8.GetBytes(parts[0]);\n            }\n            else\n            {\n                string boundaryStr = \"SPLITMEHERE\";\n                request.ContentType = \"multipart/related; boundary=\" + boundaryStr;\n\n                MemoryStream memoryStream = new MemoryStream();\n                foreach (string str in parts)\n                {\n                    bool bJsonRequest = (str.Length > 1) && (str[0] == '{');\n                    string header = (memoryStream.Position == 0 ? \"\" : \"\\r\\n\") + \"--\" + boundaryStr + \"\\r\\nContent-Type: \" + (bJsonRequest ? \"application/json\" : \"text/plain\") + \"; charset=UTF-8\\r\\n\\r\\n\";\n\n                    byte[] headerBytes = Encoding.UTF8.GetBytes(header);\n                    memoryStream.Write(headerBytes, 0, headerBytes.Length);\n\n                    byte[] partBytes = Encoding.UTF8.GetBytes(str);\n                    memoryStream.Write(partBytes, 0, partBytes.Length);\n                }\n\n                string footer = \"\\r\\n--\" + boundaryStr + \"--\";\n                byte[] footerBytes = Encoding.UTF8.GetBytes(footer);\n                memoryStream.Write(footerBytes, 0, footerBytes.Length);\n\n                contentBytes = new byte[memoryStream.Length];\n                memoryStream.Position = 0;\n                memoryStream.Read(contentBytes, 0, contentBytes.Length);\n                memoryStream.Close();\n            }\n\n            request.ContentLength = (contentBytes != null) ? contentBytes.Length : 0;\n            if (contentBytes != null)\n            {\n                Stream contentStream = request.GetRequestStream();\n                contentStream.Write(contentBytes, 0, contentBytes.Length);\n                contentStream.Close();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/googleapi/GoogleOAuth2.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Net;\nusing System.Net.Http;\nusing System.Net.Http.Headers;\nusing System.Net.Sockets;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace MgAl2O4.GoogleAPI\n{\n    // Google API package is doing awesome job, but.... it adds lots of dll dependecies\n    // I want to keep program as dll free as it gets, so it means: REWRITE ALL THE THINGS!\n    // (jk, I like doing stuff myself and learning how it works under the hood too much)\n    //\n    // source of API and params: Google OAuth 2.0 Playground\n\n    public class GoogleOAuth2\n    {\n        public class Token\n        {\n            public DateTime expirationDate;\n            public string accessToken;\n            public string refreshToken;\n\n            public bool IsValidForAuth()\n            {\n                return !string.IsNullOrEmpty(accessToken) && (expirationDate != null) && (DateTime.Now.CompareTo(expirationDate) < 0);\n            }\n\n            public bool IsValidForRefresh()\n            {\n                return !string.IsNullOrEmpty(refreshToken);\n            }\n\n            public override string ToString()\n            {\n                return \"AccessToken:\" + accessToken + \", RefreshToken:\" + refreshToken + \", ExpirationDate:\" + expirationDate;\n            }\n        }\n\n        public class ClientIdentifier\n        {\n            public override string ToString()\n            {\n                return \"ID:\" + GetID() + \", Secret:\" + GetSecret();\n            }\n\n            public virtual string GetID() { return \"\"; }\n            public virtual string GetSecret() { return \"\"; }\n        }\n\n        private static readonly string RequestAccessApi = \"https://accounts.google.com/o/oauth2/v2/auth\";\n        private static readonly string TokenApi = \"https://www.googleapis.com/oauth2/v4/token\";\n        //private static readonly string AccessScopeOwnedFiles = \"https://www.googleapis.com/auth/drive.file\";\n        private static readonly string AccessScopeAppSettings = \"https://www.googleapis.com/auth/drive.appdata\";\n        private static HttpListener httpListener = new HttpListener();\n\n        public static void KillPendingAuthorization()\n        {\n            if (httpListener != null && httpListener.IsListening)\n            {\n                httpListener.Close();\n                httpListener = null;\n            }\n        }\n\n        public static async Task<Token> GetAuthorizationToken(ClientIdentifier clientIdentifier, Token savedTokenData)\n        {\n            if (clientIdentifier == null)\n            {\n                return null;\n            }\n\n            if (savedTokenData != null)\n            {\n                if (savedTokenData.IsValidForAuth())\n                {\n                    return savedTokenData;\n                }\n                else if (savedTokenData.IsValidForRefresh())\n                {\n                    Token refreshedToken = await RefreshToken(clientIdentifier, savedTokenData);\n                    return refreshedToken;\n                }\n            }\n\n            KillPendingAuthorization();\n\n            Token newToken = await RequestToken(clientIdentifier);\n            return newToken;\n        }\n\n        private static async Task<Token> RequestToken(ClientIdentifier clientIdentifier)\n        {\n            // prepare listen server for receiving token data\n            if (!HttpListener.IsSupported || clientIdentifier == null)\n            {\n                return null;\n            }\n\n            int listenPort = FindListenPort();\n            string authListenUri = \"http://localhost:\" + listenPort + \"/auth_response/\";\n\n            httpListener = new HttpListener();\n            httpListener.Prefixes.Add(authListenUri);\n            httpListener.Start();\n\n            // send authorization request: open in default browser\n            string authRequestUri = RequestAccessApi +\n                \"?redirect_uri=\" + Uri.EscapeDataString(authListenUri) +\n                \"&prompt=consent\" +\n                \"&response_type=code\" +\n                \"&client_id=\" + clientIdentifier.GetID() +\n                \"&scope=\" + Uri.EscapeDataString(AccessScopeAppSettings) +\n                \"&access_type=offline\";\n\n            Process.Start(authRequestUri);\n\n            // wait for reponse\n            string authorizationCode = null;\n            {\n                HttpListenerContext listenContext = null;\n                try\n                {\n                    listenContext = await Task.Factory.FromAsync(httpListener.BeginGetContext(null, null), httpListener.EndGetContext);\n                }\n                catch (Exception) { listenContext = null; }\n\n                if (listenContext != null)\n                {\n                    Uri requestUrl = listenContext.Request.Url;\n\n                    ILookup<string, string> queryLookup = requestUrl.Query.TrimStart('?')\n                        .Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries)\n                        .Select(k => k.Split('='))\n                        .Where(k => k.Length == 2)\n                        .ToLookup(a => a[0], a => Uri.UnescapeDataString(a[1]), StringComparer.OrdinalIgnoreCase);\n                    authorizationCode = queryLookup[\"code\"].FirstOrDefault();\n\n                    string responseString = \"<html><title>Google account verification</title><body>Received verification code. You may now close this window.</body></html>\";\n                    byte[] buffer = Encoding.UTF8.GetBytes(responseString);\n\n                    HttpListenerResponse listenResponse = listenContext.Response;\n                    listenResponse.ContentLength64 = buffer.Length;\n                    listenResponse.OutputStream.Write(buffer, 0, buffer.Length);\n                    listenResponse.OutputStream.Close();\n                }\n            }\n\n            // send token grant request: no user ui needed\n            Token resultToken = null;\n            if (!string.IsNullOrEmpty(authorizationCode))\n            {\n                HttpContent tokenGrantContent = new FormUrlEncodedContent(new[]\n                {\n                new KeyValuePair<string, string>(\"code\", authorizationCode),\n                new KeyValuePair<string, string>(\"redirect_uri\", authListenUri),\n                new KeyValuePair<string, string>(\"client_id\", clientIdentifier.GetID()),\n                new KeyValuePair<string, string>(\"client_secret\", clientIdentifier.GetSecret()),\n                new KeyValuePair<string, string>(\"scope\", AccessScopeAppSettings),\n                new KeyValuePair<string, string>(\"grant_type\", \"authorization_code\"),\n                });\n\n                tokenGrantContent.Headers.ContentType = new MediaTypeHeaderValue(\"application/x-www-form-urlencoded\");\n\n                try\n                {\n\n                    HttpClient client = new HttpClient();\n                    HttpResponseMessage tokenGrantResponse = await client.PostAsync(TokenApi, tokenGrantContent);\n                    if (tokenGrantResponse.IsSuccessStatusCode)\n                    {\n                        string replyJson = await tokenGrantResponse.Content.ReadAsStringAsync();\n                        resultToken = CreateToken(replyJson);\n                    }\n                }\n                catch (Exception) { }\n            }\n\n            if (httpListener != null && httpListener.IsListening)\n            {\n                httpListener.Close();\n                httpListener = null;\n            }\n\n            return resultToken;\n        }\n\n        private static async Task<Token> RefreshToken(ClientIdentifier clientIdentifier, Token tokenData)\n        {\n            Token resultToken = null;\n\n            HttpContent requestContent = new FormUrlEncodedContent(new[]\n            {\n                new KeyValuePair<string, string>(\"client_secret\", clientIdentifier.GetSecret()),\n                new KeyValuePair<string, string>(\"grant_type\", \"refresh_token\"),\n                new KeyValuePair<string, string>(\"refresh_token\", tokenData.refreshToken),\n                new KeyValuePair<string, string>(\"client_id\", clientIdentifier.GetID()),\n                });\n\n            requestContent.Headers.ContentType = new MediaTypeHeaderValue(\"application/x-www-form-urlencoded\");\n\n            try\n            {\n                HttpClient client = new HttpClient();\n                HttpResponseMessage response = await client.PostAsync(TokenApi, requestContent);\n                if (response.IsSuccessStatusCode)\n                {\n                    string replyJson = await response.Content.ReadAsStringAsync();\n                    resultToken = CreateToken(replyJson);\n                    resultToken.refreshToken = tokenData.refreshToken;\n                }\n            }\n            catch (Exception) { }\n\n            return resultToken;\n        }\n\n        private static Token CreateToken(string jsonStr)\n        {\n            JsonParser.ObjectValue jsonOb = JsonParser.ParseJson(jsonStr);\n            int validForSec = (JsonParser.IntValue)jsonOb[\"expires_in\"];\n\n            return new Token\n            {\n                accessToken = jsonOb[\"access_token\"],\n                refreshToken = jsonOb[\"refresh_token\", JsonParser.StringValue.Empty],\n                expirationDate = DateTime.Now.AddSeconds(validForSec)\n            };\n        }\n\n        private static int FindListenPort()\n        {\n            TcpListener tcpListener = new TcpListener(IPAddress.Loopback, 0);\n            tcpListener.Start();\n            int port = ((IPEndPoint)tcpListener.LocalEndpoint).Port;\n            tcpListener.Stop();\n            return port;\n        }\n    }\n}\n"
  },
  {
    "path": "sources/loc/strings.cs",
    "content": "﻿namespace FFTriadBuddy.loc\n{\n    public class strings\n    {\n        // keep default values in for editor\n\n        public static string AdjustForm_CancelButton = \"Cancel\";\n        public static string AdjustForm_CardDown = \"D:\";\n        public static string AdjustForm_CardLeft = \"L:\";\n        public static string AdjustForm_CardList = \"Adjusted:\";\n        public static string AdjustForm_CardRight = \"R:\";\n        public static string AdjustForm_CardStatus = \"Status:\";\n        public static string AdjustForm_CardUp = \"U:\";\n        public static string AdjustForm_Current = \"Current:\";\n        public static string AdjustForm_Distance = \"Distance:\";\n        public static string AdjustForm_Dynamic_CardState_None = \"None\";\n        public static string AdjustForm_Dynamic_CardState_Hidden = \"Hidden\";\n        public static string AdjustForm_Dynamic_CardState_Locked = \"Locked\";\n        public static string AdjustForm_Dynamic_CardState_Visible = \"Visible\";\n        public static string AdjustForm_Dynamic_CardState_PlacedRed = \"Placed: Red\";\n        public static string AdjustForm_Dynamic_CardState_PlacedBlue = \"Placed: Blue\";\n        public static string AdjustForm_Dynamic_Digit_Default = \"auto\";\n        public static string AdjustForm_Dynamic_Digit_Override = \"custom\";\n        public static string AdjustForm_Dynamic_Distance_Classifier = \"<= Classifier based\";\n        public static string AdjustForm_Dynamic_Distance_DefaultHint = \"<= Lower is more accurate\";\n        public static string AdjustForm_Dynamic_Distance_Exact = \"<= Exact match\";\n        public static string AdjustForm_Dynamic_Distance_NotAvail = \"N/A\";\n        public static string AdjustForm_Dynamic_UnknownOwner = \"unknown\";\n        public static string AdjustForm_HashList = \"Detect:\";\n        public static string AdjustForm_SaveButton = \"Save\";\n        public static string AdjustForm_Title = \"Adjust detection\";\n        public static string App_Title = \"FF Triad Buddy\";\n        public static string DeckCtrl_CtxMenu_LockForOptimization = \"Lock card for deck optimization\";\n        public static string DeckCtrl_CtxMenu_PickCard = \"Pick card:\";\n        public static string DeckCtrl_CtxMenu_UseOnlyOwned = \"Use only owned cards (click to change)\";\n        public static string FavDeckCtrl_Edit = \"Edit\";\n        public static string FavDeckForm_AddButton = \"Add\";\n        public static string FavDeckForm_Dynamic_AutoName = \"Fav #{0}\";\n        public static string FavDeckForm_Dynamic_RemoveMsg = \"Favorite deck will be removed, do you want to continue?\";\n        public static string FavDeckForm_Dynamic_UpdateButton = \"Update\";\n        public static string FavDeckForm_Info = \"Favorite deck, click card for options:\";\n        public static string FavDeckForm_Name = \"Short name:\";\n        public static string FavDeckForm_RemoveButton = \"Remove\";\n        public static string FavDeckForm_Title = \"Edit favorite deck\";\n        public static string Game_ProcessName_DX11 = \"ffxiv_dx11\";\n        public static string Game_ProcessName_DX9 = \"ffxiv\";\n        public static string Game_WindowNamePrefix = \"FINAL FANTASY\";\n        public static string LocalSaves_Export = \"Export settings\";\n        public static string LocalSaves_Import = \"Import settings\";\n        public static string LocalSaves_ShowBackupFolder = \"Show backups\";\n        public static string LocalSaves_Title = \"Local saves\";\n        public static string MainForm_Cards_IconsTitle = \"Icons\";\n        public static string MainForm_Cards_ListTitle = \"List\";\n        public static string MainForm_Cards_List_ColumnId = \"#\";\n        public static string MainForm_Cards_List_ColumnName = \"Name\";\n        public static string MainForm_Cards_List_ColumnOwned = \"Owned\";\n        public static string MainForm_Cards_List_ColumnPower = \"Power\";\n        public static string MainForm_Cards_List_ColumnRarity = \"Rarity\";\n        public static string MainForm_Cards_List_ColumnType = \"Type\";\n        public static string MainForm_Cards_NumOwned = \"Number of owned cards:\";\n        public static string MainForm_Cards_Title = \"Cards\";\n        public static string MainForm_CtxMenu_CardInfo_FindOnline = \"Find online\";\n        public static string MainForm_CtxMenu_CardInfo_NpcReward = \"Npc reward:\";\n        public static string MainForm_CtxMenu_FindCard = \"Find card:\";\n        public static string MainForm_CtxMenu_FindNpc = \"Find npc:\";\n        public static string MainForm_CtxMenu_Learn_Adjust = \"Adjust...\";\n        public static string MainForm_CtxMenu_Learn_Delete = \"Delete\";\n        public static string MainForm_CtxMenu_SelectNpc_Rewards = \"Rewards (click to add/remove)\";\n        public static string MainForm_CtxMenu_SelectNpc_Select = \"Select npc to play\";\n        public static string MainForm_Dynamic_CardOwnedColumn = \"Yes\";\n        public static string MainForm_Dynamic_DeckState_HasDuplicates = \"Found duplicate cards!\";\n        public static string MainForm_Dynamic_DeckState_MissingCards = \"Missing cards!\";\n        public static string MainForm_Dynamic_DeckState_TooMany4Star = \"More than two 4+ star!\";\n        public static string MainForm_Dynamic_DeckState_TooMany5Star = \"Only one 5 star allowed!\";\n        public static string MainForm_Dynamic_NpcCompletedColumn = \"Yes\";\n        public static string MainForm_Dynamic_RuleListEmpty = \"(none)\";\n        public static string MainForm_Dynamic_Screenhot_Status_NoErrors = \"Active\";\n        public static string MainForm_Dynamic_Screenshot_BoardRow_Bottom = \"bot\";\n        public static string MainForm_Dynamic_Screenshot_BoardRow_Middle = \"mid\";\n        public static string MainForm_Dynamic_Screenshot_BoardRow_Top = \"top\";\n        public static string MainForm_Dynamic_Screenshot_CardLocation_BlueDeck = \"Blue {0}\";\n        public static string MainForm_Dynamic_Screenshot_CardLocation_Board = \"Board {0} {1}\";\n        public static string MainForm_Dynamic_Screenshot_CardLocation_RedDeck = \"Red {0}\";\n        public static string MainForm_Dynamic_Screenshot_CardNotDetected = \"not detected!\";\n        public static string MainForm_Dynamic_Screenshot_HashType_Card = \"Card\";\n        public static string MainForm_Dynamic_Screenshot_HashType_Number = \"Number\";\n        public static string MainForm_Dynamic_Screenshot_HashType_Rule = \"Rule\";\n        public static string MainForm_Dynamic_Screenshot_MatchType_Auto = \"Auto\";\n        public static string MainForm_Dynamic_Screenshot_MatchType_Exact = \"Exact\";\n        public static string MainForm_Dynamic_Screenshot_MatchType_Similar = \"Similar\";\n        public static string MainForm_Dynamic_Screenshot_SelectDetectionMatch = \"(select match)\";\n        public static string MainForm_Dynamic_Screenshot_Status_Disabled = \"Disabled\";\n        public static string MainForm_Dynamic_Screenshot_Status_FailedCardMatching = \"Failed to recognize some of cards! Game state won't be accurate\";\n        public static string MainForm_Dynamic_Screenshot_Status_MissingGridOrCards = \"Can't find board! Try resetting UI position, turning off reshade, etc...\";\n        public static string MainForm_Dynamic_Screenshot_Status_UnknownHash = \"Can't recognize pattern! See details below\";\n        public static string MainForm_Dynamic_Setup_CloudSave_Loaded = \"Cloud save applied\";\n        public static string MainForm_Dynamic_Setup_CloudStatus_ApiFailure = \"API call failed\";\n        public static string MainForm_Dynamic_Setup_CloudStatus_AuthInProgress = \"authorizing...\";\n        public static string MainForm_Dynamic_Setup_CloudStatus_Disabled = \"disabled, local only\";\n        public static string MainForm_Dynamic_Setup_CloudStatus_NoDatabase = \"database failure\";\n        public static string MainForm_Dynamic_Setup_CloudStatus_NoErrors = \"active\";\n        public static string MainForm_Dynamic_Setup_CloudStatus_NotAuthorized = \"Auth required\";\n        public static string MainForm_Dynamic_Setup_CloudStatus_NotInitialized = \"scanning...\";\n        public static string MainForm_Dynamic_Setup_CloudStatus_Synced = \"synchronized\";\n        public static string MainForm_Dynamic_Setup_CloudStatus_Uploaded = \"stored\";\n        public static string MainForm_Dynamic_Setup_OptimizerProgressAborted = \"aborted at {0:P0}\";\n        public static string MainForm_Dynamic_Simulate_BlueStartButton = \"Blue starts\";\n        public static string MainForm_Dynamic_Simulate_ChanceIsDraw = \"(DRAW)\";\n        public static string MainForm_Dynamic_Simulate_ChangeBlueHint = \"Click to change card\";\n        public static string MainForm_Dynamic_Simulate_EndGame_BlueDraw = \"draw\";\n        public static string MainForm_Dynamic_Simulate_EndGame_BlueLost = \"lost\";\n        public static string MainForm_Dynamic_Simulate_EndGame_BlueWin = \"yay!\";\n        public static string MainForm_Dynamic_Simulate_LastCardHint = \"[Place last red card to trigger modifier]\";\n        public static string MainForm_Dynamic_Simulate_RedStartButton = \"Red start: drag & drop card on board\";\n        public static string MainForm_Dynamic_Simulate_SwapRuleButton = \"Select one card from each deck to swap\";\n        public static string MainForm_Info_HomePage = \"Project home page:\";\n        public static string MainForm_Info_BugReports = \"Bug reports:\";\n        public static string MainForm_Info_Localization = \"UI localization:\";\n        public static string MainForm_Info_Title = \"Info & Settings\";\n        public static string MainForm_Info_TranslatorLove = \"Thanks to everyone who contributed their translations! You're awesome!\";\n        public static string MainForm_Info_TranslatorNeeded = \"( volunteers still needed ;) )\";\n        public static string MainForm_Npcs_List_ColumnCompleted = \"Completed\";\n        public static string MainForm_Npcs_List_ColumnLocation = \"Location\";\n        public static string MainForm_Npcs_List_ColumnName = \"Name\";\n        public static string MainForm_Npcs_List_ColumnPower = \"Power\";\n        public static string MainForm_Npcs_List_ColumnReward = \"Reward\";\n        public static string MainForm_Npcs_List_ColumnRules = \"Rules\";\n        public static string MainForm_Npcs_NumKnown = \"Number of npcs with cards:\";\n        public static string MainForm_Npcs_Title = \"Npcs\";\n        public static string MainForm_Screenshot_CurrentState = \"Current state:\";\n        public static string MainForm_Screenshot_History_CardsColumnDetection = \"Last detection\";\n        public static string MainForm_Screenshot_History_CardsColumnSides = \"Numbers [U-L-D-R]\";\n        public static string MainForm_Screenshot_History_CardsColumnType = \"Type\";\n        public static string MainForm_Screenshot_History_HashColumnDetection = \"Last detection\";\n        public static string MainForm_Screenshot_History_HashColumnType = \"Type\";\n        public static string MainForm_Screenshot_InfoLines = \"Rules:\\n\" +\n            \"- game must be in Windowed or Borderless Windowed mode\\n\" +\n            \"- UI: set scale to 100%, use dark scheme\\n\" +\n            \"- npc must be selected\\n\" +\n            \"- capture during Blue turn, after all card animations finished(!!!)\\n\" +\n            \"\\nGamepad: press both trigger buttons to capture\\n\" +\n            \"\\nWorks with Mini Cactpot too :D\";\n        public static string MainForm_Screenshot_Learn_DetectList = \"Detect:\";\n        public static string MainForm_Screenshot_Learn_DiscardAllButton = \"Discard All\";\n        public static string MainForm_Screenshot_Learn_DiscardAllInfo = \"<< Resets scanner state\";\n        public static string MainForm_Screenshot_Learn_PendingPlural = \"(there are {0} more pending)\";\n        public static string MainForm_Screenshot_Learn_PendingSingular = \"(there is 1 more pending)\";\n        public static string MainForm_Screenshot_Learn_SaveButton = \"Save\";\n        public static string MainForm_Screenshot_Learn_SourceImage = \"Source image:\";\n        public static string MainForm_Screenshot_Learn_Type = \"Type:\";\n        public static string MainForm_Screenshot_ListHint = \"^ Right click on lists for options\";\n        public static string MainForm_Screenshot_RemovePatternsButton = \"Remove local patterns\";\n        public static string MainForm_Screenshot_RemovePatternsTitle = \"Use to clear all custom patterns and start from scratch:\";\n        public static string MainForm_Screenshot_Title = \"Play: Screenshot\";\n        public static string MainForm_Setup_Cloud_AuthButton = \"Login:\";\n        public static string MainForm_Setup_Cloud_Desc = \"Save in Google:\";\n        public static string MainForm_Setup_Deck_OptimizeAbortButton = \"Abort\";\n        public static string MainForm_Setup_Deck_OptimizeStartButton = \"Optimize deck for NPC\";\n        public static string MainForm_Setup_Deck_Title = \"Active deck, click card for options:\";\n        public static string MainForm_Setup_Fav_AddSlotButton = \"Add favorite deck slot\";\n        public static string MainForm_Setup_NPC = \"NPC:\";\n        public static string MainForm_Setup_NPC_DeckPower = \"Deck power:\";\n        public static string MainForm_Setup_NPC_Location = \"Location:\";\n        public static string MainForm_Setup_NPC_Rules = \"Rules:\";\n        public static string MainForm_Setup_NPC_WinChance = \"Win chance:\";\n        public static string MainForm_Setup_OptimizerStats_NumOwned = \"Num owned cards:\";\n        public static string MainForm_Setup_OptimizerStats_NumPossible = \"Num possible decks:\";\n        public static string MainForm_Setup_OptimizerStats_NumTested = \"Num tested decks:\";\n        public static string MainForm_Setup_OptimizerStats_Progress = \"Progress:\";\n        public static string MainForm_Setup_OptimizerStats_TimeLeft = \"Time left:\";\n        public static string MainForm_Setup_OptimizeStats_Title = \"Optimization stats:\";\n        public static string MainForm_Setup_RulesToggle = \"Toggle npc rules, disable for tournament\";\n        public static string MainForm_Setup_Rules_Region1 = \"Region rule 1:\";\n        public static string MainForm_Setup_Rules_Region2 = \"Region rule 2:\";\n        public static string MainForm_Setup_Rules_Tournament = \"Tournament:\";\n        public static string MainForm_Setup_Rules_TournamentRules = \"Rules:\";\n        public static string MainForm_Setup_Title = \"Setup\";\n        public static string MainForm_Simulate_Debug_ForceCached = \"Force using cached image\";\n        public static string MainForm_Simulate_Debug_Info = \"Take game screenshot: DEBUG mode\";\n        public static string MainForm_Simulate_Game_ApplyRuleButton = \"Apply rule\";\n        public static string MainForm_Simulate_Game_KnownCards = \"Known cards:\";\n        public static string MainForm_Simulate_Game_ListHint = \"[Drag & drop cards on board]\";\n        public static string MainForm_Simulate_Game_SkipRuleButton = \"Skip\";\n        public static string MainForm_Simulate_Game_SpecialRule = \"Special Rule >>\";\n        public static string MainForm_Simulate_Game_UnknownCards = \"Unknown cards remaining: {0}\";\n        public static string MainForm_Simulate_Open_Hint = \"Select visible cards:\";\n        public static string MainForm_Simulate_Random_Info = \"Adjust randomized deck :(\";\n        public static string MainForm_Simulate_ResetButton = \"Reset\";\n        public static string MainForm_Simulate_Roulette_Rule1 = \"Pick roulette rule #1\";\n        public static string MainForm_Simulate_Roulette_Rule2 = \"Pick roulette rule #2\";\n        public static string MainForm_Simulate_Roulette_Rule3 = \"Pick roulette rule #3\";\n        public static string MainForm_Simulate_Roulette_Rule4 = \"Pick roulette rule #4\";\n        public static string MainForm_Simulate_RuleList = \"Current rules:\";\n        public static string MainForm_Simulate_Title = \"Play: Simulate\";\n        public static string MainForm_Simulate_UndoRedMoveButton = \"Undo last Red move\";\n        public static string MainForm_Simulate_WinChance = \"Win chance:\";\n        public static string MainForm_UpdateNotify = \"New version downloaded, please restart program to finish update. Click here to hide.\";\n        public static string OverlayForm_Capture_AutoScan = \"Auto scan\";\n        public static string OverlayForm_Capture_Button = \"Capture\";\n        public static string OverlayForm_Capture_Details = \"Show details\";\n        public static string OverlayForm_Capture_Status = \"Status\";\n        public static string OverlayForm_CardInfo_Swapped = \"SWAPPED\";\n        public static string OverlayForm_DeckInfo_Mismatch = \"Blue deck is not matching Setup page! Solver needs to observe a few games to guess correct one.\";\n        public static string OverlayForm_Details_Npc = \"NPC: {0}\";\n        public static string OverlayForm_Details_RedDeck = \"Red deck details:\";\n        public static string OverlayForm_Details_RedInfo = \"< Potential red cards, not including visible ones below:\";\n        public static string OverlayForm_Details_RedPlacedAll = \"All placed: {0}\";\n        public static string OverlayForm_Details_RedPlacedVariable = \"Var placed: {0}\";\n        public static string OverlayForm_Details_Rules = \"Rules: {0}\";\n        public static string OverlayForm_Details_ScanId = \"Scan Id: {0}\";\n        public static string OverlayForm_Dynamic_NpcUnknown = \"unknown\";\n        public static string OverlayForm_Dynamic_RulesWaiting = \"npc changed, waiting for scan...\";\n        public static string OverlayForm_Dynamic_Status_ActiveTurn = \"Ready (active turn!)\";\n        public static string OverlayForm_Dynamic_Status_AutoScanMouseOverBoard = \"Move cursor away from scan zone!\";\n        public static string OverlayForm_Dynamic_Status_CactpotReady = \"Mini cactpot: Ready\";\n        public static string OverlayForm_Dynamic_Status_FailedCardMatching = \"Unknown cards! Check Play:Screenshot for details\";\n        public static string OverlayForm_Dynamic_Status_MissingCards = \"Can't find blue deck\";\n        public static string OverlayForm_Dynamic_Status_MissingGameProcess = \"Game is not running\";\n        public static string OverlayForm_Dynamic_Status_MissingGameWindow = \"Can't find game window\";\n        public static string OverlayForm_Dynamic_Status_MissingGrid = \"Can't find board\";\n        public static string OverlayForm_Dynamic_Status_NoInputImage = \"Can't retrieve image to analyze\";\n        public static string OverlayForm_Dynamic_Status_NoScannerMatch = \"Can't find minigame window\";\n        public static string OverlayForm_Dynamic_Status_Ready = \"Ready\";\n        public static string OverlayForm_Dynamic_Status_ScannerErrors = \"Failed to recognize minigame\";\n        public static string OverlayForm_Dynamic_Status_UnknownHash = \"Unknown pattern! Check Play:Screenshot for details\";\n        public static string OverlayForm_Dynamic_Status_WaitingForTurn = \"Waiting for blue turn\";\n        public static string Settings_AlwaysOnTop = \"Always on top\";\n        public static string Settings_AlwaysSmallIcons = \"Always use small icons\";\n        public static string Settings_DisableHardwareAcceleration = \"Disable hardware acceleration\";\n        public static string Settings_FontSize = \"Font size\";\n        public static string Settings_MarkerDurationCard = \"Marker duration: card\";\n        public static string Settings_MarkerDurationSwap = \"Marker duration: swap\";\n        public static string Settings_MarkerDurationCactpot = \"Marker duration: cactpot\";\n        public static string Settings_SkipOptionalSimulateRules = \"Skip optional rules in simulate\";\n        public static string Settings_Title = \"Settings\";\n    }\n}\n"
  },
  {
    "path": "sources/loc/strings.de.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\" id=\"root\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\"/>\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\"/>\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\"/>\n              <xsd:attribute name=\"type\" type=\"xsd:string\"/>\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\"/>\n              <xsd:attribute ref=\"xml:space\"/>\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\"/>\n              <xsd:attribute name=\"name\" type=\"xsd:string\"/>\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\"/>\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\"/>\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\"/>\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\"/>\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\"/>\n              <xsd:attribute ref=\"xml:space\"/>\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\"/>\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\"/>\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <data name=\"AdjustForm_CancelButton\" xml:space=\"preserve\">\n    <value>Abbrechen</value>\n  </data>\n  <data name=\"AdjustForm_CardDown\" xml:space=\"preserve\">\n    <value>D:</value>\n  </data>\n  <data name=\"AdjustForm_CardLeft\" xml:space=\"preserve\">\n    <value>L:</value>\n  </data>\n  <data name=\"AdjustForm_CardList\" xml:space=\"preserve\">\n    <value>Angepasst:</value>\n  </data>\n  <data name=\"AdjustForm_CardRight\" xml:space=\"preserve\">\n    <value>R:</value>\n  </data>\n  <data name=\"AdjustForm_CardStatus\" xml:space=\"preserve\">\n    <value>Status:</value>\n  </data>\n  <data name=\"AdjustForm_CardUp\" xml:space=\"preserve\">\n    <value>U:</value>\n  </data>\n  <data name=\"AdjustForm_Current\" xml:space=\"preserve\">\n    <value>Aktuell:</value>\n  </data>\n  <data name=\"AdjustForm_Distance\" xml:space=\"preserve\">\n    <value>Entfernung:</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_Hidden\" xml:space=\"preserve\">\n    <value>Ausgeblendet</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_Locked\" xml:space=\"preserve\">\n    <value>Gesperrt</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_None\" xml:space=\"preserve\">\n    <value>Nichts</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_PlacedBlue\" xml:space=\"preserve\">\n    <value>Platziert: Blau</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_PlacedRed\" xml:space=\"preserve\">\n    <value>Platziert: Rot</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_Visible\" xml:space=\"preserve\">\n    <value>Sichtbar</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Digit_Default\" xml:space=\"preserve\">\n    <value>automatisch</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Digit_Override\" xml:space=\"preserve\">\n    <value>Benutzerdefiniert</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_Classifier\" xml:space=\"preserve\">\n    <value>&lt;= Klassifikatorbasiert</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_DefaultHint\" xml:space=\"preserve\">\n    <value>&lt;= Niedriger ist genau</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_Exact\" xml:space=\"preserve\">\n    <value>&lt;= Exakte Übereinstimmung</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_NotAvail\" xml:space=\"preserve\">\n    <value>k.A.</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_UnknownOwner\" xml:space=\"preserve\">\n    <value>unbekannt</value>\n  </data>\n  <data name=\"AdjustForm_HashList\" xml:space=\"preserve\">\n    <value>Erkennen: </value>\n  </data>\n  <data name=\"AdjustForm_SaveButton\" xml:space=\"preserve\">\n    <value>Speichern</value>\n  </data>\n  <data name=\"AdjustForm_Title\" xml:space=\"preserve\">\n    <value>Erkennung anpassen</value>\n  </data>\n  <data name=\"App_Title\" xml:space=\"preserve\">\n    <value>FF Triad Buddy</value>\n  </data>\n  <data name=\"DeckCtrl_CtxMenu_LockForOptimization\" xml:space=\"preserve\">\n    <value>Karte für Deck-Optimierung sperren</value>\n  </data>\n  <data name=\"DeckCtrl_CtxMenu_PickCard\" xml:space=\"preserve\">\n    <value>Wähle Karte:</value>\n  </data>\n  <data name=\"DeckCtrl_CtxMenu_UseOnlyOwned\" xml:space=\"preserve\">\n    <value>Nur eigene Karten verwenden (zum Ändern klicken)</value>\n  </data>\n  <data name=\"FavDeckCtrl_Edit\" xml:space=\"preserve\">\n    <value>Bearbeiten</value>\n  </data>\n  <data name=\"FavDeckForm_AddButton\" xml:space=\"preserve\">\n    <value>Hinzufügen</value>\n  </data>\n  <data name=\"FavDeckForm_Dynamic_AutoName\" xml:space=\"preserve\">\n    <value>Fav #{0}</value>\n  </data>\n  <data name=\"FavDeckForm_Dynamic_RemoveMsg\" xml:space=\"preserve\">\n    <value>Favoriten Deck wird entfernt, möchtest du fortfahren?</value>\n  </data>\n  <data name=\"FavDeckForm_Dynamic_UpdateButton\" xml:space=\"preserve\">\n    <value>Akutalisieren</value>\n  </data>\n  <data name=\"FavDeckForm_Info\" xml:space=\"preserve\">\n    <value>Bevorzugtes Deck, für Optionen Karte anklicken:</value>\n  </data>\n  <data name=\"FavDeckForm_Name\" xml:space=\"preserve\">\n    <value>Kurzname:</value>\n  </data>\n  <data name=\"FavDeckForm_RemoveButton\" xml:space=\"preserve\">\n    <value>Entfernen</value>\n  </data>\n  <data name=\"FavDeckForm_Title\" xml:space=\"preserve\">\n    <value>Favoriten bearbeiten</value>\n  </data>\n  <data name=\"Game_ProcessName_DX11\" xml:space=\"preserve\">\n    <value>ffxiv_dx11</value>\n  </data>\n  <data name=\"Game_ProcessName_DX9\" xml:space=\"preserve\">\n    <value>ffxiv</value>\n  </data>\n  <data name=\"Game_WindowNamePrefix\" xml:space=\"preserve\">\n    <value>FINALE FANTASIE</value>\n  </data>\n  <data name=\"LocalSaves_Export\" xml:space=\"preserve\">\n    <value>Einstellungen exportieren</value>\n  </data>\n  <data name=\"LocalSaves_Import\" xml:space=\"preserve\">\n    <value>Einstellungen importieren</value>\n  </data>\n  <data name=\"LocalSaves_ShowBackupFolder\" xml:space=\"preserve\">\n    <value>Backups anzeigen</value>\n  </data>\n  <data name=\"LocalSaves_Title\" xml:space=\"preserve\">\n    <value>Lokale Speicherung</value>\n  </data>\n  <data name=\"MainForm_Cards_IconsTitle\" xml:space=\"preserve\">\n    <value>Symbole</value>\n  </data>\n  <data name=\"MainForm_Cards_ListTitle\" xml:space=\"preserve\">\n    <value>Liste</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnId\" xml:space=\"preserve\">\n    <value>#</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnName\" xml:space=\"preserve\">\n    <value>Name</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnOwned\" xml:space=\"preserve\">\n    <value>Im Besitz</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnPower\" xml:space=\"preserve\">\n    <value>Power</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnRarity\" xml:space=\"preserve\">\n    <value>Seltenheit</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnType\" xml:space=\"preserve\">\n    <value>Typ</value>\n  </data>\n  <data name=\"MainForm_Cards_NumOwned\" xml:space=\"preserve\">\n    <value>Anzahl der eigenen Karten:</value>\n  </data>\n  <data name=\"MainForm_Cards_Title\" xml:space=\"preserve\">\n    <value>Karten</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_CardInfo_FindOnline\" xml:space=\"preserve\">\n    <value>Online finden</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_CardInfo_NpcReward\" xml:space=\"preserve\">\n    <value>NPC Belohnung:</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_FindCard\" xml:space=\"preserve\">\n    <value>Karte finden:</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_FindNpc\" xml:space=\"preserve\">\n    <value>NPC finden:</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_Learn_Adjust\" xml:space=\"preserve\">\n    <value>Anpassen...</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_Learn_Delete\" xml:space=\"preserve\">\n    <value>Löschen</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_SelectNpc_Rewards\" xml:space=\"preserve\">\n    <value>Belohnungen (Zum Hinzufügen/Entfernen klicken)</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_SelectNpc_Select\" xml:space=\"preserve\">\n    <value>Wähle NPC zum Starten</value>\n  </data>\n  <data name=\"MainForm_Dynamic_CardOwnedColumn\" xml:space=\"preserve\">\n    <value>Ja</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_HasDuplicates\" xml:space=\"preserve\">\n    <value>Doppelte Karten gefunden!</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_MissingCards\" xml:space=\"preserve\">\n    <value>Fehlende Karten!</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_TooMany4Star\" xml:space=\"preserve\">\n    <value>Mehr als zwei 4+ Sterne!</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_TooMany5Star\" xml:space=\"preserve\">\n    <value>Nur ein 5 Sterne erlaubt!</value>\n  </data>\n  <data name=\"MainForm_Dynamic_NpcCompletedColumn\" xml:space=\"preserve\">\n    <value>Ja</value>\n  </data>\n  <data name=\"MainForm_Dynamic_RuleListEmpty\" xml:space=\"preserve\">\n    <value>(keine)</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenhot_Status_NoErrors\" xml:space=\"preserve\">\n    <value>Aktiv</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_BoardRow_Bottom\" xml:space=\"preserve\">\n    <value>bot</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_BoardRow_Middle\" xml:space=\"preserve\">\n    <value>mid</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_BoardRow_Top\" xml:space=\"preserve\">\n    <value>top</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardLocation_BlueDeck\" xml:space=\"preserve\">\n    <value>Blau {0}</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardLocation_Board\" xml:space=\"preserve\">\n    <value>Brett {0} {1}</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardLocation_RedDeck\" xml:space=\"preserve\">\n    <value>Rot {0}</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardNotDetected\" xml:space=\"preserve\">\n    <value>nicht erkannt!</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_HashType_Card\" xml:space=\"preserve\">\n    <value>Karte</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_HashType_Number\" xml:space=\"preserve\">\n    <value>Nummer</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_HashType_Rule\" xml:space=\"preserve\">\n    <value>Regel</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_MatchType_Auto\" xml:space=\"preserve\">\n    <value>Automatisch</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_MatchType_Exact\" xml:space=\"preserve\">\n    <value>Exakt</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_MatchType_Similar\" xml:space=\"preserve\">\n    <value>Ähnlich</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_SelectDetectionMatch\" xml:space=\"preserve\">\n    <value>(Match auswählen)</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_Disabled\" xml:space=\"preserve\">\n    <value>Deaktiviert</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_FailedCardMatching\" xml:space=\"preserve\">\n    <value>Einige Karten konnten nicht erkannt werden! Der Spielstatus ist nicht korrekt</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_MissingGridOrCards\" xml:space=\"preserve\">\n    <value>Kann das Board nicht finden! Versuche die UI-Position zurückzusetzen, deaktiviere das reshade, etc...</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_UnknownHash\" xml:space=\"preserve\">\n    <value>Muster kann nicht erkannt werden! Siehe Details unten</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudSave_Loaded\" xml:space=\"preserve\">\n    <value>Cloudspeicherung angewendet</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_ApiFailure\" xml:space=\"preserve\">\n    <value>API-Zugriff fehlgeschlagen</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_AuthInProgress\" xml:space=\"preserve\">\n    <value>autorisieren...</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_Disabled\" xml:space=\"preserve\">\n    <value>deaktiviert, nur lokal</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NoDatabase\" xml:space=\"preserve\">\n    <value>Datenbankfehler</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NoErrors\" xml:space=\"preserve\">\n    <value>aktiv</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NotAuthorized\" xml:space=\"preserve\">\n    <value>Auth erforderlich</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NotInitialized\" xml:space=\"preserve\">\n    <value>Scannen...</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_Synced\" xml:space=\"preserve\">\n    <value>Synchronisiert</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_Uploaded\" xml:space=\"preserve\">\n    <value>gespeichert</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_OptimizerProgressAborted\" xml:space=\"preserve\">\n    <value>abgebrochen um {0:P0}</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_BlueStartButton\" xml:space=\"preserve\">\n    <value>Blau beginnt</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_ChanceIsDraw\" xml:space=\"preserve\">\n    <value>(ZIEH)</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_ChangeBlueHint\" xml:space=\"preserve\">\n    <value>Klicken, um die Karte zu ändern</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_EndGame_BlueDraw\" xml:space=\"preserve\">\n    <value>unentschieden</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_EndGame_BlueLost\" xml:space=\"preserve\">\n    <value>verloren</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_EndGame_BlueWin\" xml:space=\"preserve\">\n    <value>Juhu!</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_LastCardHint\" xml:space=\"preserve\">\n    <value>[Legen Sie die letzte rote Karte, um den Modifikator auszulösen]</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_RedStartButton\" xml:space=\"preserve\">\n    <value>Rot startet: Karte per Drag &amp; Drop auf das Board</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_SwapRuleButton\" xml:space=\"preserve\">\n    <value>Wählen Sie eine Karte von jedem Deck zum Tausch</value>\n  </data>\n  <data name=\"MainForm_Info_BugReports\" xml:space=\"preserve\">\n    <value>Fehlermeldungen:</value>\n  </data>\n  <data name=\"MainForm_Info_HomePage\" xml:space=\"preserve\">\n    <value>Projekt Homepage:</value>\n  </data>\n  <data name=\"MainForm_Info_Localization\" xml:space=\"preserve\">\n    <value>UI Lokalisierung:</value>\n  </data>\n  <data name=\"MainForm_Info_Title\" xml:space=\"preserve\">\n    <value>Info &amp; Einstellungen</value>\n  </data>\n  <data name=\"MainForm_Info_TranslatorLove\" xml:space=\"preserve\">\n    <value>Vielen Dank an alle, die ihre Übersetzungen beigesteuert haben! Ihr seid großartig!</value>\n  </data>\n  <data name=\"MainForm_Info_TranslatorNeeded\" xml:space=\"preserve\">\n    <value>( Freiwillige werden noch gesucht ;) )</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnCompleted\" xml:space=\"preserve\">\n    <value>Abgeschlossen</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnLocation\" xml:space=\"preserve\">\n    <value>Standort</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnName\" xml:space=\"preserve\">\n    <value>Name</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnPower\" xml:space=\"preserve\">\n    <value>Power</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnReward\" xml:space=\"preserve\">\n    <value>Belohnung</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnRules\" xml:space=\"preserve\">\n    <value>Regeln</value>\n  </data>\n  <data name=\"MainForm_Npcs_NumKnown\" xml:space=\"preserve\">\n    <value>Anzahl der NPCs mit Karten:</value>\n  </data>\n  <data name=\"MainForm_Npcs_Title\" xml:space=\"preserve\">\n    <value>NPCs</value>\n  </data>\n  <data name=\"MainForm_Screenshot_CurrentState\" xml:space=\"preserve\">\n    <value>Aktueller Status</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_CardsColumnDetection\" xml:space=\"preserve\">\n    <value>Letzte Erfassung</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_CardsColumnSides\" xml:space=\"preserve\">\n    <value>Nummern [U-L-D-R]</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_CardsColumnType\" xml:space=\"preserve\">\n    <value>Typ</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_HashColumnDetection\" xml:space=\"preserve\">\n    <value>Letzte Erfassung</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_HashColumnType\" xml:space=\"preserve\">\n    <value>Typ</value>\n  </data>\n  <data name=\"MainForm_Screenshot_InfoLines\" xml:space=\"preserve\">\n    <value>Regeln:\n- Das Spiel muss im Fenstermodus oder im randlosen Fenstermodus sein.\n- UI: Skalierung auf 100% setzen, dunkles Schema verwenden\n- NPC muss ausgewählt sein\n- Aufnahme während des blauen Zuges, nachdem alle Kartenanimationen abgeschlossen sind (!!!)\n\nGamepad: Drücken Sie beide Trigger-Tasten zum Erfassen\n\nFunktioniert auch mit Mini Cactpot :D</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_DetectList\" xml:space=\"preserve\">\n    <value>Ermittelt:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_DiscardAllButton\" xml:space=\"preserve\">\n    <value>Alle Verwerfen</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_DiscardAllInfo\" xml:space=\"preserve\">\n    <value>&lt;&lt; Setzt den Status des Scanners zurück</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_PendingPlural\" xml:space=\"preserve\">\n    <value>(es sind {0} weitere ausstehend)</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_PendingSingular\" xml:space=\"preserve\">\n    <value>(weitere 1 steht noch aus)</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_SaveButton\" xml:space=\"preserve\">\n    <value>Speichern</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_SourceImage\" xml:space=\"preserve\">\n    <value>Quellbild:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_Type\" xml:space=\"preserve\">\n    <value>Typ:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_ListHint\" xml:space=\"preserve\">\n    <value>^ Rechtsklicken Sie auf Listen für Optionen</value>\n  </data>\n  <data name=\"MainForm_Screenshot_RemovePatternsButton\" xml:space=\"preserve\">\n    <value>Lokale Muster entfernen</value>\n  </data>\n  <data name=\"MainForm_Screenshot_RemovePatternsTitle\" xml:space=\"preserve\">\n    <value>Verwenden Sie diese Option, um alle benutzerdefinierten Muster zu löschen und von vorne zu beginnen:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Title\" xml:space=\"preserve\">\n    <value>Spielen: Screenshot</value>\n  </data>\n  <data name=\"MainForm_Setup_Cloud_AuthButton\" xml:space=\"preserve\">\n    <value>Login:</value>\n  </data>\n  <data name=\"MainForm_Setup_Cloud_Desc\" xml:space=\"preserve\">\n    <value>In Google speichern:</value>\n  </data>\n  <data name=\"MainForm_Setup_Deck_OptimizeAbortButton\" xml:space=\"preserve\">\n    <value>Abbrechen</value>\n  </data>\n  <data name=\"MainForm_Setup_Deck_OptimizeStartButton\" xml:space=\"preserve\">\n    <value>Deck für NPC optimieren</value>\n  </data>\n  <data name=\"MainForm_Setup_Deck_Title\" xml:space=\"preserve\">\n    <value>Aktives Deck, für Optionen Karte anklicken:</value>\n  </data>\n  <data name=\"MainForm_Setup_Fav_AddSlotButton\" xml:space=\"preserve\">\n    <value>Favoriten Deck-Slot hinzufügen</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC\" xml:space=\"preserve\">\n    <value>NPC:</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_DeckPower\" xml:space=\"preserve\">\n    <value>Deck Power:</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_Location\" xml:space=\"preserve\">\n    <value>Standort:</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_Rules\" xml:space=\"preserve\">\n    <value>Regeln:</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_WinChance\" xml:space=\"preserve\">\n    <value>Gewinnchance:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_NumOwned\" xml:space=\"preserve\">\n    <value>Anzahl der Karten im Besitz:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_NumPossible\" xml:space=\"preserve\">\n    <value>Anzahl möglicher Decks:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_NumTested\" xml:space=\"preserve\">\n    <value>Anzahl getesteter Decks:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_Progress\" xml:space=\"preserve\">\n    <value>Fortschritt:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_TimeLeft\" xml:space=\"preserve\">\n    <value>Verbleibende Zeit:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizeStats_Title\" xml:space=\"preserve\">\n    <value>Optimierungsstatus:</value>\n  </data>\n  <data name=\"MainForm_Setup_RulesToggle\" xml:space=\"preserve\">\n    <value>NPC-Regeln ein-/ausschalten für Turnier</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_Region1\" xml:space=\"preserve\">\n    <value>Regionalregel 1:</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_Region2\" xml:space=\"preserve\">\n    <value>Regionalregel 2:</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_Tournament\" xml:space=\"preserve\">\n    <value>Turnier:</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_TournamentRules\" xml:space=\"preserve\">\n    <value>Regeln:</value>\n  </data>\n  <data name=\"MainForm_Setup_Title\" xml:space=\"preserve\">\n    <value>Konfiguration</value>\n  </data>\n  <data name=\"MainForm_Simulate_Debug_ForceCached\" xml:space=\"preserve\">\n    <value>Erzwinge mit zwischengespeichertem Bild</value>\n  </data>\n  <data name=\"MainForm_Simulate_Debug_Info\" xml:space=\"preserve\">\n    <value>Machen Sie einen Screenshot vom Spiel: DEBUG-Modus</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_ApplyRuleButton\" xml:space=\"preserve\">\n    <value>Regel anwenden</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_KnownCards\" xml:space=\"preserve\">\n    <value>Bekannte Karten:</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_ListHint\" xml:space=\"preserve\">\n    <value>[Karten auf das Board ziehen und ablegen]</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_SkipRuleButton\" xml:space=\"preserve\">\n    <value>Überspringen</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_SpecialRule\" xml:space=\"preserve\">\n    <value>Sonderregel &gt;&gt;</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_UnknownCards\" xml:space=\"preserve\">\n    <value>Unbekannte Karten verbleibend: {0}</value>\n  </data>\n  <data name=\"MainForm_Simulate_Open_Hint\" xml:space=\"preserve\">\n    <value>Sichtbare Karten auswählen:</value>\n  </data>\n  <data name=\"MainForm_Simulate_Random_Info\" xml:space=\"preserve\">\n    <value>Zufälliges Deck anpassen :(</value>\n  </data>\n  <data name=\"MainForm_Simulate_ResetButton\" xml:space=\"preserve\">\n    <value>Zurücksetzen</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule1\" xml:space=\"preserve\">\n    <value>Wähle Roulette Regel #1 aus</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule2\" xml:space=\"preserve\">\n    <value>Wähle Roulette Regel #2 aus</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule3\" xml:space=\"preserve\">\n    <value>Wähle Roulette Regel #3 aus</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule4\" xml:space=\"preserve\">\n    <value>Wählen Sie Roulette-Regel #4</value>\n  </data>\n  <data name=\"MainForm_Simulate_RuleList\" xml:space=\"preserve\">\n    <value>Aktuelle Regeln:</value>\n  </data>\n  <data name=\"MainForm_Simulate_Title\" xml:space=\"preserve\">\n    <value>Spielen: Simulieren</value>\n  </data>\n  <data name=\"MainForm_Simulate_UndoRedMoveButton\" xml:space=\"preserve\">\n    <value>Letzten roten Zug rückgängig machen</value>\n  </data>\n  <data name=\"MainForm_Simulate_WinChance\" xml:space=\"preserve\">\n    <value>Gewinnchance:</value>\n  </data>\n  <data name=\"MainForm_UpdateNotify\" xml:space=\"preserve\">\n    <value>Neue Version heruntergeladen, bitte starten Sie das Programm neu, um das Update abzuschließen. Klicken Sie hier, um es auszublenden.</value>\n  </data>\n  <data name=\"OverlayForm_Capture_AutoScan\" xml:space=\"preserve\">\n    <value>Automatischer Scan</value>\n  </data>\n  <data name=\"OverlayForm_Capture_Button\" xml:space=\"preserve\">\n    <value>Erfassen</value>\n  </data>\n  <data name=\"OverlayForm_Capture_Details\" xml:space=\"preserve\">\n    <value>Details anzeigen</value>\n  </data>\n  <data name=\"OverlayForm_Capture_Status\" xml:space=\"preserve\">\n    <value>Status</value>\n  </data>\n  <data name=\"OverlayForm_CardInfo_Swapped\" xml:space=\"preserve\">\n    <value>GETAUSCHT</value>\n  </data>\n  <data name=\"OverlayForm_DeckInfo_Mismatch\" xml:space=\"preserve\">\n    <value>Das blaue Deck passt nicht zur Setup-Seite! Der Solver muss ein paar Spiele beobachten, um das richtige Spiel zu erraten.</value>\n  </data>\n  <data name=\"OverlayForm_Details_Npc\" xml:space=\"preserve\">\n    <value>NPC: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedDeck\" xml:space=\"preserve\">\n    <value>Details zum roten Deck</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedInfo\" xml:space=\"preserve\">\n    <value>&lt; Mögliche rote Karten, ohne die unten sichtbaren:</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedPlacedAll\" xml:space=\"preserve\">\n    <value>Alle platziert: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedPlacedVariable\" xml:space=\"preserve\">\n    <value>Var platziert: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_Rules\" xml:space=\"preserve\">\n    <value>Regeln: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_ScanId\" xml:space=\"preserve\">\n    <value>Scan-ID: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_NpcUnknown\" xml:space=\"preserve\">\n    <value>unbekannt</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_RulesWaiting\" xml:space=\"preserve\">\n    <value>NPC geändert, warten auf Scan...</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_ActiveTurn\" xml:space=\"preserve\">\n    <value>Bereit (aktiver Zug!)</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_AutoScanMouseOverBoard\" xml:space=\"preserve\">\n    <value>Bewegen Sie den Cursor aus dem Scanbereich heraus!</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_CactpotReady\" xml:space=\"preserve\">\n    <value>Mini Cactpot: Bereit</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_FailedCardMatching\" xml:space=\"preserve\">\n    <value>Unbekannte Karten! Überprüfe Play:Screenshot für Details</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingCards\" xml:space=\"preserve\">\n    <value>Blaues Deck kann nicht gefunden werden</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingGameProcess\" xml:space=\"preserve\">\n    <value>Spiel läuft nicht!</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingGameWindow\" xml:space=\"preserve\">\n    <value>Spielfenster nicht gefunden</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingGrid\" xml:space=\"preserve\">\n    <value>Board nicht gefunden</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_NoInputImage\" xml:space=\"preserve\">\n    <value>Bild kann nicht zur Analyse abgerufen werden</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_NoScannerMatch\" xml:space=\"preserve\">\n    <value>Minigame Fenster nicht gefunden</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_Ready\" xml:space=\"preserve\">\n    <value>Bereit</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_ScannerErrors\" xml:space=\"preserve\">\n    <value>Minispiel konnte nicht erkannt werden</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_UnknownHash\" xml:space=\"preserve\">\n    <value>Unbekanntes Muster! Siehe Play:Screenshot für Details</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_WaitingForTurn\" xml:space=\"preserve\">\n    <value>Warten auf blauen Zug</value>\n  </data>\n  <data name=\"Settings_AlwaysOnTop\" xml:space=\"preserve\">\n    <value>Immer im Vordergrund</value>\n  </data>\n  <data name=\"Settings_AlwaysSmallIcons\" xml:space=\"preserve\">\n    <value>Immer kleine Symbole verwenden</value>\n  </data>\n  <data name=\"Settings_DisableHardwareAcceleration\" xml:space=\"preserve\">\n    <value>Hardwarebeschleunigung deaktivieren</value>\n  </data>\n  <data name=\"Settings_FontSize\" xml:space=\"preserve\">\n    <value>Schriftgröße</value>\n  </data>\n  <data name=\"Settings_MarkerDurationCactpot\" xml:space=\"preserve\">\n    <value>Markierungsdauer: cactpot</value>\n  </data>\n  <data name=\"Settings_MarkerDurationCard\" xml:space=\"preserve\">\n    <value>Markierungsdauer: Karte</value>\n  </data>\n  <data name=\"Settings_MarkerDurationSwap\" xml:space=\"preserve\">\n    <value>Markierungsdauer: Swap</value>\n  </data>\n  <data name=\"Settings_SkipOptionalSimulateRules\" xml:space=\"preserve\">\n    <value>Optionale Regeln in Simulation überspringen</value>\n  </data>\n  <data name=\"Settings_Title\" xml:space=\"preserve\">\n    <value>Einstellungen</value>\n  </data>\n</root>"
  },
  {
    "path": "sources/loc/strings.es.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\" id=\"root\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\"/>\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\"/>\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\"/>\n              <xsd:attribute name=\"type\" type=\"xsd:string\"/>\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\"/>\n              <xsd:attribute ref=\"xml:space\"/>\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\"/>\n              <xsd:attribute name=\"name\" type=\"xsd:string\"/>\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\"/>\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\"/>\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\"/>\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\"/>\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\"/>\n              <xsd:attribute ref=\"xml:space\"/>\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\"/>\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\"/>\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <data name=\"AdjustForm_CancelButton\" xml:space=\"preserve\">\n    <value>Cancelar</value>\n  </data>\n  <data name=\"AdjustForm_CardDown\" xml:space=\"preserve\">\n    <value>D:</value>\n  </data>\n  <data name=\"AdjustForm_CardLeft\" xml:space=\"preserve\">\n    <value>L:</value>\n  </data>\n  <data name=\"AdjustForm_CardList\" xml:space=\"preserve\">\n    <value>Ajustado:</value>\n  </data>\n  <data name=\"AdjustForm_CardRight\" xml:space=\"preserve\">\n    <value>R:</value>\n  </data>\n  <data name=\"AdjustForm_CardStatus\" xml:space=\"preserve\">\n    <value>Estado:</value>\n  </data>\n  <data name=\"AdjustForm_CardUp\" xml:space=\"preserve\">\n    <value>U:</value>\n  </data>\n  <data name=\"AdjustForm_Current\" xml:space=\"preserve\">\n    <value>Actual:</value>\n  </data>\n  <data name=\"AdjustForm_Distance\" xml:space=\"preserve\">\n    <value>Distancia:</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_Hidden\" xml:space=\"preserve\">\n    <value>Oculto</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_Locked\" xml:space=\"preserve\">\n    <value>Bloqueado</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_None\" xml:space=\"preserve\">\n    <value>Ninguno</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_PlacedBlue\" xml:space=\"preserve\">\n    <value>Colocado: Azul</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_PlacedRed\" xml:space=\"preserve\">\n    <value>Colocado: Rojo</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_Visible\" xml:space=\"preserve\">\n    <value>Visible</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Digit_Default\" xml:space=\"preserve\">\n    <value>auto</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Digit_Override\" xml:space=\"preserve\">\n    <value>personalizado</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_Classifier\" xml:space=\"preserve\">\n    <value>&lt;= Basado en la clasificación</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_DefaultHint\" xml:space=\"preserve\">\n    <value>&lt;= El inferior es más preciso</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_Exact\" xml:space=\"preserve\">\n    <value>&lt;= Partida exacta</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_NotAvail\" xml:space=\"preserve\">\n    <value>N/A</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_UnknownOwner\" xml:space=\"preserve\">\n    <value>desconocido</value>\n  </data>\n  <data name=\"AdjustForm_HashList\" xml:space=\"preserve\">\n    <value>Detectar:</value>\n  </data>\n  <data name=\"AdjustForm_SaveButton\" xml:space=\"preserve\">\n    <value>Guardar</value>\n  </data>\n  <data name=\"AdjustForm_Title\" xml:space=\"preserve\">\n    <value>Ajuste de detección</value>\n  </data>\n  <data name=\"App_Title\" xml:space=\"preserve\">\n    <value>FF Triad Buddy</value>\n  </data>\n  <data name=\"DeckCtrl_CtxMenu_LockForOptimization\" xml:space=\"preserve\">\n    <value>Bloquear carta para optimizar el mazo</value>\n  </data>\n  <data name=\"DeckCtrl_CtxMenu_PickCard\" xml:space=\"preserve\">\n    <value>Elegir carta:</value>\n  </data>\n  <data name=\"DeckCtrl_CtxMenu_UseOnlyOwned\" xml:space=\"preserve\">\n    <value>Usar sólo cartas propias (clic para cambiar)</value>\n  </data>\n  <data name=\"FavDeckCtrl_Edit\" xml:space=\"preserve\">\n    <value>Editar</value>\n  </data>\n  <data name=\"FavDeckForm_AddButton\" xml:space=\"preserve\">\n    <value>Agregar</value>\n  </data>\n  <data name=\"FavDeckForm_Dynamic_AutoName\" xml:space=\"preserve\">\n    <value>Fav #{0}</value>\n  </data>\n  <data name=\"FavDeckForm_Dynamic_RemoveMsg\" xml:space=\"preserve\">\n    <value>Se eliminará el mazo favorito, ¿quieres continuar?</value>\n  </data>\n  <data name=\"FavDeckForm_Dynamic_UpdateButton\" xml:space=\"preserve\">\n    <value>Actualizar</value>\n  </data>\n  <data name=\"FavDeckForm_Info\" xml:space=\"preserve\">\n    <value>Mazo favorito, haz clic en una carta para opciones:</value>\n  </data>\n  <data name=\"FavDeckForm_Name\" xml:space=\"preserve\">\n    <value>Nombre corto:</value>\n  </data>\n  <data name=\"FavDeckForm_RemoveButton\" xml:space=\"preserve\">\n    <value>Eliminar</value>\n  </data>\n  <data name=\"FavDeckForm_Title\" xml:space=\"preserve\">\n    <value>Editar mazo favorito</value>\n  </data>\n  <data name=\"Game_ProcessName_DX11\" xml:space=\"preserve\">\n    <value>ffxiv_dx11</value>\n  </data>\n  <data name=\"Game_ProcessName_DX9\" xml:space=\"preserve\">\n    <value>ffxiv</value>\n  </data>\n  <data name=\"Game_WindowNamePrefix\" xml:space=\"preserve\">\n    <value>FINAL FANTASY</value>\n  </data>\n  <data name=\"LocalSaves_Export\" xml:space=\"preserve\">\n    <value>Exportar configuración</value>\n  </data>\n  <data name=\"LocalSaves_Import\" xml:space=\"preserve\">\n    <value>Importar configuración</value>\n  </data>\n  <data name=\"LocalSaves_ShowBackupFolder\" xml:space=\"preserve\">\n    <value>Mostrar backups</value>\n  </data>\n  <data name=\"LocalSaves_Title\" xml:space=\"preserve\">\n    <value>Guardados locales</value>\n  </data>\n  <data name=\"MainForm_Cards_IconsTitle\" xml:space=\"preserve\">\n    <value>Íconos</value>\n  </data>\n  <data name=\"MainForm_Cards_ListTitle\" xml:space=\"preserve\">\n    <value>Lista</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnId\" xml:space=\"preserve\">\n    <value>#</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnName\" xml:space=\"preserve\">\n    <value>Nombre</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnOwned\" xml:space=\"preserve\">\n    <value>En propiedad</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnPower\" xml:space=\"preserve\">\n    <value>Poder</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnRarity\" xml:space=\"preserve\">\n    <value>Rareza</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnType\" xml:space=\"preserve\">\n    <value>Tipo</value>\n  </data>\n  <data name=\"MainForm_Cards_NumOwned\" xml:space=\"preserve\">\n    <value>Número de cartas poseídas:</value>\n  </data>\n  <data name=\"MainForm_Cards_Title\" xml:space=\"preserve\">\n    <value>Cartas</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_CardInfo_FindOnline\" xml:space=\"preserve\">\n    <value>Buscar en línea</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_CardInfo_NpcReward\" xml:space=\"preserve\">\n    <value>Recompensa de PNJ:</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_FindCard\" xml:space=\"preserve\">\n    <value>Buscar carta:</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_FindNpc\" xml:space=\"preserve\">\n    <value>Buscar PNJ:</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_Learn_Adjust\" xml:space=\"preserve\">\n    <value>Ajustar...</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_Learn_Delete\" xml:space=\"preserve\">\n    <value>Eliminar</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_SelectNpc_Rewards\" xml:space=\"preserve\">\n    <value>Recompensas (haga clic para añadir/eliminar)</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_SelectNpc_Select\" xml:space=\"preserve\">\n    <value>Selecciona este PNJ para reproducir</value>\n  </data>\n  <data name=\"MainForm_Dynamic_CardOwnedColumn\" xml:space=\"preserve\">\n    <value>Si</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_HasDuplicates\" xml:space=\"preserve\">\n    <value>¡Se han encontrado cartas duplicadas!</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_MissingCards\" xml:space=\"preserve\">\n    <value>¡Faltan cartas!</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_TooMany4Star\" xml:space=\"preserve\">\n    <value>¡Más de dos 4+ estrellas!</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_TooMany5Star\" xml:space=\"preserve\">\n    <value>¡Sólo se permite una 5 estrellas!</value>\n  </data>\n  <data name=\"MainForm_Dynamic_NpcCompletedColumn\" xml:space=\"preserve\">\n    <value>Si</value>\n  </data>\n  <data name=\"MainForm_Dynamic_RuleListEmpty\" xml:space=\"preserve\">\n    <value>(ninguno)</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenhot_Status_NoErrors\" xml:space=\"preserve\">\n    <value>Activo</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_BoardRow_Bottom\" xml:space=\"preserve\">\n    <value>interior</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_BoardRow_Middle\" xml:space=\"preserve\">\n    <value>central</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_BoardRow_Top\" xml:space=\"preserve\">\n    <value>superior</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardLocation_BlueDeck\" xml:space=\"preserve\">\n    <value>Azul {0}</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardLocation_Board\" xml:space=\"preserve\">\n    <value>Tablero {0} {1}</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardLocation_RedDeck\" xml:space=\"preserve\">\n    <value>Rojo {0}</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardNotDetected\" xml:space=\"preserve\">\n    <value>¡no detectado!</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_HashType_Card\" xml:space=\"preserve\">\n    <value>Carta</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_HashType_Number\" xml:space=\"preserve\">\n    <value>Número</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_HashType_Rule\" xml:space=\"preserve\">\n    <value>Regla</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_MatchType_Auto\" xml:space=\"preserve\">\n    <value>Auto</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_MatchType_Exact\" xml:space=\"preserve\">\n    <value>Exacto</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_MatchType_Similar\" xml:space=\"preserve\">\n    <value>Similar</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_SelectDetectionMatch\" xml:space=\"preserve\">\n    <value>(seleccionar partida)</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_Disabled\" xml:space=\"preserve\">\n    <value>Desactivado</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_FailedCardMatching\" xml:space=\"preserve\">\n    <value>¡Error al reconocer algunas cartas! El estado del juego no será preciso</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_MissingGridOrCards\" xml:space=\"preserve\">\n    <value>¡No se puede encontrar el tablero! Intenta restablecer la posición de la interfaz de usuario, apagando el rehade, etc...</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_UnknownHash\" xml:space=\"preserve\">\n    <value>¡No se puede reconocer el patrón! Ver los detalles a continuación</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudSave_Loaded\" xml:space=\"preserve\">\n    <value>Guardado en la nube aplicado</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_ApiFailure\" xml:space=\"preserve\">\n    <value>Falló la llamada API</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_AuthInProgress\" xml:space=\"preserve\">\n    <value>autorizando...</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_Disabled\" xml:space=\"preserve\">\n    <value>desactivado, solo local</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NoDatabase\" xml:space=\"preserve\">\n    <value>error de base de datos</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NoErrors\" xml:space=\"preserve\">\n    <value>activo</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NotAuthorized\" xml:space=\"preserve\">\n    <value>Se requiere autenticación</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NotInitialized\" xml:space=\"preserve\">\n    <value>escaneando...</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_Synced\" xml:space=\"preserve\">\n    <value>sincronizado</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_Uploaded\" xml:space=\"preserve\">\n    <value>almacenado</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_OptimizerProgressAborted\" xml:space=\"preserve\">\n    <value>abortado en {0:P0}</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_BlueStartButton\" xml:space=\"preserve\">\n    <value>Empieza azul</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_ChanceIsDraw\" xml:space=\"preserve\">\n    <value>(EMPATE)</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_ChangeBlueHint\" xml:space=\"preserve\">\n    <value>Haga clic para cambiar la carta</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_EndGame_BlueDraw\" xml:space=\"preserve\">\n    <value>empate</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_EndGame_BlueLost\" xml:space=\"preserve\">\n    <value>derrota</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_EndGame_BlueWin\" xml:space=\"preserve\">\n    <value>¡Hurra!</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_LastCardHint\" xml:space=\"preserve\">\n    <value>[Coloca la última carta roja para activar el modificador]</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_RedStartButton\" xml:space=\"preserve\">\n    <value>Empieza rojo: arrastre y suelte la carta en el tablero</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_SwapRuleButton\" xml:space=\"preserve\">\n    <value>Selecciona una carta de cada mazo para intercambiar</value>\n  </data>\n  <data name=\"MainForm_Info_BugReports\" xml:space=\"preserve\">\n    <value>Informes de errores:</value>\n  </data>\n  <data name=\"MainForm_Info_HomePage\" xml:space=\"preserve\">\n    <value>Página principal del proyecto:</value>\n  </data>\n  <data name=\"MainForm_Info_Localization\" xml:space=\"preserve\">\n    <value>Localización de la IU:</value>\n  </data>\n  <data name=\"MainForm_Info_Title\" xml:space=\"preserve\">\n    <value>Info y Preferencias</value>\n  </data>\n  <data name=\"MainForm_Info_TranslatorLove\" xml:space=\"preserve\">\n    <value>¡Gracias a todos los que han contribuido con sus traducciones! ¡Sois increíbles!</value>\n  </data>\n  <data name=\"MainForm_Info_TranslatorNeeded\" xml:space=\"preserve\">\n    <value>(todavía se necesitan voluntarios ;) )</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnCompleted\" xml:space=\"preserve\">\n    <value>Completado</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnLocation\" xml:space=\"preserve\">\n    <value>Ubicación</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnName\" xml:space=\"preserve\">\n    <value>Nombre</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnPower\" xml:space=\"preserve\">\n    <value>Poder</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnReward\" xml:space=\"preserve\">\n    <value>Recompensa</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnRules\" xml:space=\"preserve\">\n    <value>Reglas</value>\n  </data>\n  <data name=\"MainForm_Npcs_NumKnown\" xml:space=\"preserve\">\n    <value>Número de npcs con cartas:</value>\n  </data>\n  <data name=\"MainForm_Npcs_Title\" xml:space=\"preserve\">\n    <value>Npcs</value>\n  </data>\n  <data name=\"MainForm_Screenshot_CurrentState\" xml:space=\"preserve\">\n    <value>Estado actual:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_CardsColumnDetection\" xml:space=\"preserve\">\n    <value>Última detección</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_CardsColumnSides\" xml:space=\"preserve\">\n    <value>Números [U-L-D-R]</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_CardsColumnType\" xml:space=\"preserve\">\n    <value>Tipo</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_HashColumnDetection\" xml:space=\"preserve\">\n    <value>Última detección</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_HashColumnType\" xml:space=\"preserve\">\n    <value>Tipo</value>\n  </data>\n  <data name=\"MainForm_Screenshot_InfoLines\" xml:space=\"preserve\">\n    <value>Reglas:\n- el juego debe estar en modo ventana o ventana sin bordes\n- UI: establecer escala a 100%, usa interfaz oscuro\n- Se debe de seleccionar al PNJ\n- Haz captura durante el turno de Azul, después de que hayan terminado todas las animaciones de las cartas (! !)\n\nMando: presione ambos gatillos para hacer captura\n\nFunciona también con Mini Cactpot :D</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_DetectList\" xml:space=\"preserve\">\n    <value>Detectar:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_DiscardAllButton\" xml:space=\"preserve\">\n    <value>Descartar todo</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_DiscardAllInfo\" xml:space=\"preserve\">\n    <value>&lt;&lt; Reinicia el estado del escáner</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_PendingPlural\" xml:space=\"preserve\">\n    <value>(hay {0} más pendientes)</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_PendingSingular\" xml:space=\"preserve\">\n    <value>(hay 1 más pendiente)</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_SaveButton\" xml:space=\"preserve\">\n    <value>Guardar</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_SourceImage\" xml:space=\"preserve\">\n    <value>Fuente de la imagen:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_Type\" xml:space=\"preserve\">\n    <value>Tipo:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_ListHint\" xml:space=\"preserve\">\n    <value>^ Clic derecho en las listas para opciones</value>\n  </data>\n  <data name=\"MainForm_Screenshot_RemovePatternsButton\" xml:space=\"preserve\">\n    <value>Eliminar patrones locales</value>\n  </data>\n  <data name=\"MainForm_Screenshot_RemovePatternsTitle\" xml:space=\"preserve\">\n    <value>Utilizar para borrar todos los patrones personalizados y empezar desde el principio:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Title\" xml:space=\"preserve\">\n    <value>Reproducción: Captura de pantalla</value>\n  </data>\n  <data name=\"MainForm_Setup_Cloud_AuthButton\" xml:space=\"preserve\">\n    <value>Conectar:</value>\n  </data>\n  <data name=\"MainForm_Setup_Cloud_Desc\" xml:space=\"preserve\">\n    <value>Guardar en Google:</value>\n  </data>\n  <data name=\"MainForm_Setup_Deck_OptimizeAbortButton\" xml:space=\"preserve\">\n    <value>Abortar</value>\n  </data>\n  <data name=\"MainForm_Setup_Deck_OptimizeStartButton\" xml:space=\"preserve\">\n    <value>Optimizar mazo para PNJ</value>\n  </data>\n  <data name=\"MainForm_Setup_Deck_Title\" xml:space=\"preserve\">\n    <value>Mazo activo, clic en una carta para opciones:</value>\n  </data>\n  <data name=\"MainForm_Setup_Fav_AddSlotButton\" xml:space=\"preserve\">\n    <value>Añadir ranura de mazo favorito</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC\" xml:space=\"preserve\">\n    <value>PNJ:</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_DeckPower\" xml:space=\"preserve\">\n    <value>Poder del mazo:</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_Location\" xml:space=\"preserve\">\n    <value>Ubicación:</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_Rules\" xml:space=\"preserve\">\n    <value>Reglas:</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_WinChance\" xml:space=\"preserve\">\n    <value>Probabilidad de ganar:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_NumOwned\" xml:space=\"preserve\">\n    <value>Número de cartas poseídas:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_NumPossible\" xml:space=\"preserve\">\n    <value>Número de mazos posibles:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_NumTested\" xml:space=\"preserve\">\n    <value>Número de mazos probados:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_Progress\" xml:space=\"preserve\">\n    <value>Progreso:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_TimeLeft\" xml:space=\"preserve\">\n    <value>Tiempo restante:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizeStats_Title\" xml:space=\"preserve\">\n    <value>Estadísticas de optimización:</value>\n  </data>\n  <data name=\"MainForm_Setup_RulesToggle\" xml:space=\"preserve\">\n    <value>Cambiar reglas de PNJ, desactivar para torneos</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_Region1\" xml:space=\"preserve\">\n    <value>Regla 1 de Región:</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_Region2\" xml:space=\"preserve\">\n    <value>Regla 2 de Región:</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_Tournament\" xml:space=\"preserve\">\n    <value>Torneo:</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_TournamentRules\" xml:space=\"preserve\">\n    <value>Reglas:</value>\n  </data>\n  <data name=\"MainForm_Setup_Title\" xml:space=\"preserve\">\n    <value>Configurar</value>\n  </data>\n  <data name=\"MainForm_Simulate_Debug_ForceCached\" xml:space=\"preserve\">\n    <value>Forzar el uso de imagen en caché</value>\n  </data>\n  <data name=\"MainForm_Simulate_Debug_Info\" xml:space=\"preserve\">\n    <value>Capturar pantalla de juego: modo DEBUG</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_ApplyRuleButton\" xml:space=\"preserve\">\n    <value>Aplicar regla</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_KnownCards\" xml:space=\"preserve\">\n    <value>Cartas conocidas:</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_ListHint\" xml:space=\"preserve\">\n    <value>[Arrastra y suelta cartas en el tablero]</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_SkipRuleButton\" xml:space=\"preserve\">\n    <value>Omitir</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_SpecialRule\" xml:space=\"preserve\">\n    <value>Regla Especial &gt;&gt;</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_UnknownCards\" xml:space=\"preserve\">\n    <value>Cartas desconocidas restantes: {0}</value>\n  </data>\n  <data name=\"MainForm_Simulate_Open_Hint\" xml:space=\"preserve\">\n    <value>Selecciona las cartas visibles:</value>\n  </data>\n  <data name=\"MainForm_Simulate_Random_Info\" xml:space=\"preserve\">\n    <value>Ajustar mazo randomizado :(</value>\n  </data>\n  <data name=\"MainForm_Simulate_ResetButton\" xml:space=\"preserve\">\n    <value>Restablecer</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule1\" xml:space=\"preserve\">\n    <value>Escoge la regla #1 de ruleta</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule2\" xml:space=\"preserve\">\n    <value>Escoge la regla #2 de ruleta</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule3\" xml:space=\"preserve\">\n    <value>Escoge la regla #3 de ruleta</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule4\" xml:space=\"preserve\">\n    <value>Escoge la regla #4 de ruleta</value>\n  </data>\n  <data name=\"MainForm_Simulate_RuleList\" xml:space=\"preserve\">\n    <value>Reglas actuales:</value>\n  </data>\n  <data name=\"MainForm_Simulate_Title\" xml:space=\"preserve\">\n    <value>Reproducción: Simular</value>\n  </data>\n  <data name=\"MainForm_Simulate_UndoRedMoveButton\" xml:space=\"preserve\">\n    <value>Deshacer el último movimiento de Rojo</value>\n  </data>\n  <data name=\"MainForm_Simulate_WinChance\" xml:space=\"preserve\">\n    <value>Probabilidad de ganar:</value>\n  </data>\n  <data name=\"MainForm_UpdateNotify\" xml:space=\"preserve\">\n    <value>Nueva versión descargada, por favor reinicie el programa para finalizar la actualización. Haga clic aquí para ocultar.</value>\n  </data>\n  <data name=\"OverlayForm_Capture_AutoScan\" xml:space=\"preserve\">\n    <value>Escaneo automático</value>\n  </data>\n  <data name=\"OverlayForm_Capture_Button\" xml:space=\"preserve\">\n    <value>Capturar</value>\n  </data>\n  <data name=\"OverlayForm_Capture_Details\" xml:space=\"preserve\">\n    <value>Mostrar detalles</value>\n  </data>\n  <data name=\"OverlayForm_Capture_Status\" xml:space=\"preserve\">\n    <value>Estado</value>\n  </data>\n  <data name=\"OverlayForm_CardInfo_Swapped\" xml:space=\"preserve\">\n    <value>Intercambiado</value>\n  </data>\n  <data name=\"OverlayForm_DeckInfo_Mismatch\" xml:space=\"preserve\">\n    <value>¡El mazo azul no coincide con la página de configuración! El solucionador necesita observar algunas partidas para generar uno correcto.</value>\n  </data>\n  <data name=\"OverlayForm_Details_Npc\" xml:space=\"preserve\">\n    <value>PNJ: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedDeck\" xml:space=\"preserve\">\n    <value>Detalles del mazo de Rojo:</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedInfo\" xml:space=\"preserve\">\n    <value>&lt; Cartas rojas posibles, sin incluir las visibles debajo:</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedPlacedAll\" xml:space=\"preserve\">\n    <value>Todas colocadas: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedPlacedVariable\" xml:space=\"preserve\">\n    <value>Var colocado: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_Rules\" xml:space=\"preserve\">\n    <value>Reglas: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_ScanId\" xml:space=\"preserve\">\n    <value>Id del escaneo: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_NpcUnknown\" xml:space=\"preserve\">\n    <value>desconocido</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_RulesWaiting\" xml:space=\"preserve\">\n    <value>PNJ cambiado, esperando a escanear...</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_ActiveTurn\" xml:space=\"preserve\">\n    <value>Listo (Activar turno!)</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_AutoScanMouseOverBoard\" xml:space=\"preserve\">\n    <value>¡Mueve el cursor lejos de la zona de escaneo!</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_CactpotReady\" xml:space=\"preserve\">\n    <value>Mini cactpot: Listo</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_FailedCardMatching\" xml:space=\"preserve\">\n    <value>¡Cartas desconocidas! Revisa Reproducción: Captura de pantalla para más detalles</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingCards\" xml:space=\"preserve\">\n    <value>No se puede encontrar el mazo azul</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingGameProcess\" xml:space=\"preserve\">\n    <value>El juego no se está ejecutando</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingGameWindow\" xml:space=\"preserve\">\n    <value>No se puede encontrar la ventana del juego</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingGrid\" xml:space=\"preserve\">\n    <value>No se puede encontrar el tablero</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_NoInputImage\" xml:space=\"preserve\">\n    <value>No se puede recuperar la imagen para analizar</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_NoScannerMatch\" xml:space=\"preserve\">\n    <value>No se puede encontrar la ventana del minijuego</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_Ready\" xml:space=\"preserve\">\n    <value>Listo</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_ScannerErrors\" xml:space=\"preserve\">\n    <value>Error al reconocer el minijuego</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_UnknownHash\" xml:space=\"preserve\">\n    <value>¡Patrón desconocido! Revisa Reproducción: Captura de pantalla para más detalles</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_WaitingForTurn\" xml:space=\"preserve\">\n    <value>Esperando al turno de Azul</value>\n  </data>\n  <data name=\"Settings_AlwaysOnTop\" xml:space=\"preserve\">\n    <value>Siempre visible</value>\n  </data>\n  <data name=\"Settings_AlwaysSmallIcons\" xml:space=\"preserve\">\n    <value>Usar siempre iconos pequeños</value>\n  </data>\n  <data name=\"Settings_DisableHardwareAcceleration\" xml:space=\"preserve\">\n    <value>Desactivar aceleración por hardware</value>\n  </data>\n  <data name=\"Settings_FontSize\" xml:space=\"preserve\">\n    <value>Tamaño de la fuente</value>\n  </data>\n  <data name=\"Settings_MarkerDurationCactpot\" xml:space=\"preserve\">\n    <value>Duración del marcador: cactpot</value>\n  </data>\n  <data name=\"Settings_MarkerDurationCard\" xml:space=\"preserve\">\n    <value>Duración del marcador: carta</value>\n  </data>\n  <data name=\"Settings_MarkerDurationSwap\" xml:space=\"preserve\">\n    <value>Duración del marcador: intercambio</value>\n  </data>\n  <data name=\"Settings_SkipOptionalSimulateRules\" xml:space=\"preserve\">\n    <value>Omitir reglas opcionales en simulado</value>\n  </data>\n  <data name=\"Settings_Title\" xml:space=\"preserve\">\n    <value>Configuración</value>\n  </data>\n</root>"
  },
  {
    "path": "sources/loc/strings.fr.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\" id=\"root\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\"/>\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\"/>\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\"/>\n              <xsd:attribute name=\"type\" type=\"xsd:string\"/>\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\"/>\n              <xsd:attribute ref=\"xml:space\"/>\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\"/>\n              <xsd:attribute name=\"name\" type=\"xsd:string\"/>\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\"/>\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\"/>\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\"/>\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\"/>\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\"/>\n              <xsd:attribute ref=\"xml:space\"/>\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\"/>\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\"/>\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <data name=\"AdjustForm_CancelButton\" xml:space=\"preserve\">\n    <value>Annuler</value>\n  </data>\n  <data name=\"AdjustForm_CardDown\" xml:space=\"preserve\">\n    <value>D:</value>\n  </data>\n  <data name=\"AdjustForm_CardLeft\" xml:space=\"preserve\">\n    <value>L:</value>\n  </data>\n  <data name=\"AdjustForm_CardList\" xml:space=\"preserve\">\n    <value>Ajusté :</value>\n  </data>\n  <data name=\"AdjustForm_CardRight\" xml:space=\"preserve\">\n    <value>D:</value>\n  </data>\n  <data name=\"AdjustForm_CardStatus\" xml:space=\"preserve\">\n    <value>État :</value>\n  </data>\n  <data name=\"AdjustForm_CardUp\" xml:space=\"preserve\">\n    <value>H:</value>\n  </data>\n  <data name=\"AdjustForm_Current\" xml:space=\"preserve\">\n    <value>Actuel :</value>\n  </data>\n  <data name=\"AdjustForm_Distance\" xml:space=\"preserve\">\n    <value>Distance :</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_Hidden\" xml:space=\"preserve\">\n    <value>Caché</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_Locked\" xml:space=\"preserve\">\n    <value>Verrouillé</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_None\" xml:space=\"preserve\">\n    <value>Aucun</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_PlacedBlue\" xml:space=\"preserve\">\n    <value>Placé : Bleu</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_PlacedRed\" xml:space=\"preserve\">\n    <value>Placé : Rouge</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_Visible\" xml:space=\"preserve\">\n    <value>Visible</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Digit_Default\" xml:space=\"preserve\">\n    <value>automatique</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Digit_Override\" xml:space=\"preserve\">\n    <value>Personnaliser</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_Classifier\" xml:space=\"preserve\">\n    <value>&lt;= Classificateur basé</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_DefaultHint\" xml:space=\"preserve\">\n    <value>&lt;= Le plus bas est plus précis</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_Exact\" xml:space=\"preserve\">\n    <value>&lt;= Correspondance exacte</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_NotAvail\" xml:space=\"preserve\">\n    <value>N/A</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_UnknownOwner\" xml:space=\"preserve\">\n    <value>inconnu</value>\n  </data>\n  <data name=\"AdjustForm_HashList\" xml:space=\"preserve\">\n    <value>Détecter :</value>\n  </data>\n  <data name=\"AdjustForm_SaveButton\" xml:space=\"preserve\">\n    <value>Sauvegarder</value>\n  </data>\n  <data name=\"AdjustForm_Title\" xml:space=\"preserve\">\n    <value>Ajuster la détection</value>\n  </data>\n  <data name=\"App_Title\" xml:space=\"preserve\">\n    <value>FF Triad Buddy</value>\n  </data>\n  <data name=\"DeckCtrl_CtxMenu_LockForOptimization\" xml:space=\"preserve\">\n    <value>Verrouiller les cartes pour l'optimisation du deck</value>\n  </data>\n  <data name=\"DeckCtrl_CtxMenu_PickCard\" xml:space=\"preserve\">\n    <value>Choisir une carte:</value>\n  </data>\n  <data name=\"DeckCtrl_CtxMenu_UseOnlyOwned\" xml:space=\"preserve\">\n    <value>Utiliser uniquement les cartes détenues (cliquez pour modifier)</value>\n  </data>\n  <data name=\"FavDeckCtrl_Edit\" xml:space=\"preserve\">\n    <value>Modifier</value>\n  </data>\n  <data name=\"FavDeckForm_AddButton\" xml:space=\"preserve\">\n    <value>Ajouter</value>\n  </data>\n  <data name=\"FavDeckForm_Dynamic_AutoName\" xml:space=\"preserve\">\n    <value>Fav #{0}</value>\n  </data>\n  <data name=\"FavDeckForm_Dynamic_RemoveMsg\" xml:space=\"preserve\">\n    <value>Le deck favori sera supprimé, voulez-vous continuer ?</value>\n  </data>\n  <data name=\"FavDeckForm_Dynamic_UpdateButton\" xml:space=\"preserve\">\n    <value>Mettre à jour</value>\n  </data>\n  <data name=\"FavDeckForm_Info\" xml:space=\"preserve\">\n    <value>Deck favori, cliquez sur une carte pour les options :</value>\n  </data>\n  <data name=\"FavDeckForm_Name\" xml:space=\"preserve\">\n    <value>Nom abrégé:</value>\n  </data>\n  <data name=\"FavDeckForm_RemoveButton\" xml:space=\"preserve\">\n    <value>Retirer</value>\n  </data>\n  <data name=\"FavDeckForm_Title\" xml:space=\"preserve\">\n    <value>Modifier le deck favori</value>\n  </data>\n  <data name=\"Game_ProcessName_DX11\" xml:space=\"preserve\">\n    <value>ffxiv_dx11</value>\n  </data>\n  <data name=\"Game_ProcessName_DX9\" xml:space=\"preserve\">\n    <value>ffxiv</value>\n  </data>\n  <data name=\"Game_WindowNamePrefix\" xml:space=\"preserve\">\n    <value>FINAL FANTASY</value>\n  </data>\n  <data name=\"LocalSaves_Export\" xml:space=\"preserve\">\n    <value>Exporter réglages</value>\n  </data>\n  <data name=\"LocalSaves_Import\" xml:space=\"preserve\">\n    <value>Importer réglages</value>\n  </data>\n  <data name=\"LocalSaves_ShowBackupFolder\" xml:space=\"preserve\">\n    <value>Afficher les sauvegardes</value>\n  </data>\n  <data name=\"LocalSaves_Title\" xml:space=\"preserve\">\n    <value>Sauvegardes locales</value>\n  </data>\n  <data name=\"MainForm_Cards_IconsTitle\" xml:space=\"preserve\">\n    <value>Icônes</value>\n  </data>\n  <data name=\"MainForm_Cards_ListTitle\" xml:space=\"preserve\">\n    <value>Liste</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnId\" xml:space=\"preserve\">\n    <value>#</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnName\" xml:space=\"preserve\">\n    <value>Nom</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnOwned\" xml:space=\"preserve\">\n    <value>Possédé</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnPower\" xml:space=\"preserve\">\n    <value>Puissance</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnRarity\" xml:space=\"preserve\">\n    <value>Rareté</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnType\" xml:space=\"preserve\">\n    <value>Type</value>\n  </data>\n  <data name=\"MainForm_Cards_NumOwned\" xml:space=\"preserve\">\n    <value>Nombre de cartes possédées:</value>\n  </data>\n  <data name=\"MainForm_Cards_Title\" xml:space=\"preserve\">\n    <value>Cartes</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_CardInfo_FindOnline\" xml:space=\"preserve\">\n    <value>Trouver en ligne</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_CardInfo_NpcReward\" xml:space=\"preserve\">\n    <value>Récompense du PNJ:</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_FindCard\" xml:space=\"preserve\">\n    <value>Trouver une carte:</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_FindNpc\" xml:space=\"preserve\">\n    <value>Trouver un PNJ:</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_Learn_Adjust\" xml:space=\"preserve\">\n    <value>Ajuster...</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_Learn_Delete\" xml:space=\"preserve\">\n    <value>Supprimer</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_SelectNpc_Rewards\" xml:space=\"preserve\">\n    <value>Récompenses (cliquez pour ajouter/retirer)</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_SelectNpc_Select\" xml:space=\"preserve\">\n    <value>Sélectionner un PNJ pour jouer</value>\n  </data>\n  <data name=\"MainForm_Dynamic_CardOwnedColumn\" xml:space=\"preserve\">\n    <value>Oui</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_HasDuplicates\" xml:space=\"preserve\">\n    <value>Cartes dupliquées trouvées !</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_MissingCards\" xml:space=\"preserve\">\n    <value>Cartes manquantes !</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_TooMany4Star\" xml:space=\"preserve\">\n    <value>Plus de deux 4 étoiles !</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_TooMany5Star\" xml:space=\"preserve\">\n    <value>Une seule 5 étoiles est autorisée !</value>\n  </data>\n  <data name=\"MainForm_Dynamic_NpcCompletedColumn\" xml:space=\"preserve\">\n    <value>Oui</value>\n  </data>\n  <data name=\"MainForm_Dynamic_RuleListEmpty\" xml:space=\"preserve\">\n    <value>(aucun)</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenhot_Status_NoErrors\" xml:space=\"preserve\">\n    <value>Actif</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_BoardRow_Bottom\" xml:space=\"preserve\">\n    <value>bas</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_BoardRow_Middle\" xml:space=\"preserve\">\n    <value>mil</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_BoardRow_Top\" xml:space=\"preserve\">\n    <value>haut</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardLocation_BlueDeck\" xml:space=\"preserve\">\n    <value>Bleu {0}</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardLocation_Board\" xml:space=\"preserve\">\n    <value>Plateau {0} {1}</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardLocation_RedDeck\" xml:space=\"preserve\">\n    <value>Rouge {0}</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardNotDetected\" xml:space=\"preserve\">\n    <value>non détectée!</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_HashType_Card\" xml:space=\"preserve\">\n    <value>Carte</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_HashType_Number\" xml:space=\"preserve\">\n    <value>Numéro</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_HashType_Rule\" xml:space=\"preserve\">\n    <value>Règle</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_MatchType_Auto\" xml:space=\"preserve\">\n    <value>Auto</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_MatchType_Exact\" xml:space=\"preserve\">\n    <value>Exact</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_MatchType_Similar\" xml:space=\"preserve\">\n    <value>Similaire</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_SelectDetectionMatch\" xml:space=\"preserve\">\n    <value>(choisir la correspondance)</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_Disabled\" xml:space=\"preserve\">\n    <value>Désactivé</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_FailedCardMatching\" xml:space=\"preserve\">\n    <value>Impossible de reconnaître certaines cartes! L'état du jeu ne sera pas précis</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_MissingGridOrCards\" xml:space=\"preserve\">\n    <value>Plateau introuvable ! Essayez de réinitialiser la position de l'IU, désactiver reshade, etc...</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_UnknownHash\" xml:space=\"preserve\">\n    <value>Impossible de reconnaître le modèle! Voir les détails ci-dessous</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudSave_Loaded\" xml:space=\"preserve\">\n    <value>Sauvegarde Cloud appliquée</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_ApiFailure\" xml:space=\"preserve\">\n    <value>Échec de l'appel API</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_AuthInProgress\" xml:space=\"preserve\">\n    <value>autorisation...</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_Disabled\" xml:space=\"preserve\">\n    <value>désactivé, local uniquement</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NoDatabase\" xml:space=\"preserve\">\n    <value>échec de la base de données</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NoErrors\" xml:space=\"preserve\">\n    <value>actif</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NotAuthorized\" xml:space=\"preserve\">\n    <value>Authentification requise</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NotInitialized\" xml:space=\"preserve\">\n    <value>analyse...</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_Synced\" xml:space=\"preserve\">\n    <value>synchronisé</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_Uploaded\" xml:space=\"preserve\">\n    <value>stocké</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_OptimizerProgressAborted\" xml:space=\"preserve\">\n    <value>abandonné à {0:P0}</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_BlueStartButton\" xml:space=\"preserve\">\n    <value>Bleu débute</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_ChanceIsDraw\" xml:space=\"preserve\">\n    <value>(TIRAGE)</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_ChangeBlueHint\" xml:space=\"preserve\">\n    <value>Cliquez pour changer de carte</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_EndGame_BlueDraw\" xml:space=\"preserve\">\n    <value>tirage</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_EndGame_BlueLost\" xml:space=\"preserve\">\n    <value>perdu</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_EndGame_BlueWin\" xml:space=\"preserve\">\n    <value>youpi !</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_LastCardHint\" xml:space=\"preserve\">\n    <value>[Placer la dernière carte rouge pour déclencher le modificateur]</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_RedStartButton\" xml:space=\"preserve\">\n    <value>Début rouge: glisser-déposer la carte sur le plateau</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_SwapRuleButton\" xml:space=\"preserve\">\n    <value>Sélectionnez une carte de chaque deck à échanger</value>\n  </data>\n  <data name=\"MainForm_Info_BugReports\" xml:space=\"preserve\">\n    <value>Rapports de bugs:</value>\n  </data>\n  <data name=\"MainForm_Info_HomePage\" xml:space=\"preserve\">\n    <value>Page d'accueil du projet:</value>\n  </data>\n  <data name=\"MainForm_Info_Localization\" xml:space=\"preserve\">\n    <value>Traduction de l'IU:</value>\n  </data>\n  <data name=\"MainForm_Info_Title\" xml:space=\"preserve\">\n    <value>Infos &amp; Réglages</value>\n  </data>\n  <data name=\"MainForm_Info_TranslatorLove\" xml:space=\"preserve\">\n    <value>Merci à tous ceux qui ont contribué à leurs traductions ! Vous êtes géniaux!</value>\n  </data>\n  <data name=\"MainForm_Info_TranslatorNeeded\" xml:space=\"preserve\">\n    <value>( bénévoles encore nécessaires ;) )</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnCompleted\" xml:space=\"preserve\">\n    <value>Complété</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnLocation\" xml:space=\"preserve\">\n    <value>Lieu</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnName\" xml:space=\"preserve\">\n    <value>Nom</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnPower\" xml:space=\"preserve\">\n    <value>Puissance</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnReward\" xml:space=\"preserve\">\n    <value>Récompense</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnRules\" xml:space=\"preserve\">\n    <value>Règles</value>\n  </data>\n  <data name=\"MainForm_Npcs_NumKnown\" xml:space=\"preserve\">\n    <value>Nombre de PNJs avec cartes:</value>\n  </data>\n  <data name=\"MainForm_Npcs_Title\" xml:space=\"preserve\">\n    <value>PNJs</value>\n  </data>\n  <data name=\"MainForm_Screenshot_CurrentState\" xml:space=\"preserve\">\n    <value>État actuel:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_CardsColumnDetection\" xml:space=\"preserve\">\n    <value>Dernière détection</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_CardsColumnSides\" xml:space=\"preserve\">\n    <value>Numéros [U-L-D-R]</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_CardsColumnType\" xml:space=\"preserve\">\n    <value>Type</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_HashColumnDetection\" xml:space=\"preserve\">\n    <value>Dernière détection</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_HashColumnType\" xml:space=\"preserve\">\n    <value>Type</value>\n  </data>\n  <data name=\"MainForm_Screenshot_InfoLines\" xml:space=\"preserve\">\n    <value>Règles :\n- le jeu doit être en mode Fenêtré ou Fenêtre sans bordure\n- IU : définir l'échelle à 100%, utiliser le schéma sombre\n- PNJ doit être sélectionné\n- capturer pendant le tour bleu, après que toutes les animations de carte soient terminées (!!!)\n\nGamepad : appuyez sur les deux boutons de déclenchement pour capturer\n\nFonctionne également avec le Mini Cactpot :D</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_DetectList\" xml:space=\"preserve\">\n    <value>Détecter:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_DiscardAllButton\" xml:space=\"preserve\">\n    <value>Tout abandonner</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_DiscardAllInfo\" xml:space=\"preserve\">\n    <value>&lt;&lt; État du scanner de réinitialisation</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_PendingPlural\" xml:space=\"preserve\">\n    <value>(il y a {0} autres en attente)</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_PendingSingular\" xml:space=\"preserve\">\n    <value>(il y a 1 autre en attente)</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_SaveButton\" xml:space=\"preserve\">\n    <value>Sauvegarder</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_SourceImage\" xml:space=\"preserve\">\n    <value>Image source:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_Type\" xml:space=\"preserve\">\n    <value>Type:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_ListHint\" xml:space=\"preserve\">\n    <value>^ Clic droit sur les listes pour les options</value>\n  </data>\n  <data name=\"MainForm_Screenshot_RemovePatternsButton\" xml:space=\"preserve\">\n    <value>Supprimer les modèles locaux</value>\n  </data>\n  <data name=\"MainForm_Screenshot_RemovePatternsTitle\" xml:space=\"preserve\">\n    <value>Utiliser pour effacer tous les modèles personnalisés et partir de zéro:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Title\" xml:space=\"preserve\">\n    <value>Lecture: Capture d'écran</value>\n  </data>\n  <data name=\"MainForm_Setup_Cloud_AuthButton\" xml:space=\"preserve\">\n    <value>Connexion:</value>\n  </data>\n  <data name=\"MainForm_Setup_Cloud_Desc\" xml:space=\"preserve\">\n    <value>Enregistrer sur Google:</value>\n  </data>\n  <data name=\"MainForm_Setup_Deck_OptimizeAbortButton\" xml:space=\"preserve\">\n    <value>Abandonner</value>\n  </data>\n  <data name=\"MainForm_Setup_Deck_OptimizeStartButton\" xml:space=\"preserve\">\n    <value>Optimiser le deck pour le PNJ</value>\n  </data>\n  <data name=\"MainForm_Setup_Deck_Title\" xml:space=\"preserve\">\n    <value>Deck actif, cliquez sur carte pour les options:</value>\n  </data>\n  <data name=\"MainForm_Setup_Fav_AddSlotButton\" xml:space=\"preserve\">\n    <value>Ajouter un emplacement de deck favori</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC\" xml:space=\"preserve\">\n    <value>PNJ:</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_DeckPower\" xml:space=\"preserve\">\n    <value>Puissance du deck:</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_Location\" xml:space=\"preserve\">\n    <value>Lieu:</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_Rules\" xml:space=\"preserve\">\n    <value>Règles:</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_WinChance\" xml:space=\"preserve\">\n    <value>Chance de victoire:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_NumOwned\" xml:space=\"preserve\">\n    <value>Nombre de cartes détenues:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_NumPossible\" xml:space=\"preserve\">\n    <value>Nombre de decks possibles:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_NumTested\" xml:space=\"preserve\">\n    <value>Nombre de decks testés:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_Progress\" xml:space=\"preserve\">\n    <value>Progrès:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_TimeLeft\" xml:space=\"preserve\">\n    <value>Temps restant:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizeStats_Title\" xml:space=\"preserve\">\n    <value>Statistiques d'optimisation:</value>\n  </data>\n  <data name=\"MainForm_Setup_RulesToggle\" xml:space=\"preserve\">\n    <value>Modifier les règles PNJ, désactiver pour le tournoi</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_Region1\" xml:space=\"preserve\">\n    <value>Règle de région 1:</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_Region2\" xml:space=\"preserve\">\n    <value>Règle de région 2:</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_Tournament\" xml:space=\"preserve\">\n    <value>Tournoi:</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_TournamentRules\" xml:space=\"preserve\">\n    <value>Règles:</value>\n  </data>\n  <data name=\"MainForm_Setup_Title\" xml:space=\"preserve\">\n    <value>Configuration</value>\n  </data>\n  <data name=\"MainForm_Simulate_Debug_ForceCached\" xml:space=\"preserve\">\n    <value>Forcer l'utilisation de l'image en cache</value>\n  </data>\n  <data name=\"MainForm_Simulate_Debug_Info\" xml:space=\"preserve\">\n    <value>Capture d'écran du jeu: mode DEBUG</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_ApplyRuleButton\" xml:space=\"preserve\">\n    <value>Appliquer une règle</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_KnownCards\" xml:space=\"preserve\">\n    <value>Cartes connues:</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_ListHint\" xml:space=\"preserve\">\n    <value>[Glisser et déposer des cartes sur le plateau]</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_SkipRuleButton\" xml:space=\"preserve\">\n    <value>Passer</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_SpecialRule\" xml:space=\"preserve\">\n    <value>Règle Spéciale &gt;&gt;</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_UnknownCards\" xml:space=\"preserve\">\n    <value>Cartes inconnues restantes: {0}</value>\n  </data>\n  <data name=\"MainForm_Simulate_Open_Hint\" xml:space=\"preserve\">\n    <value>Sélectionnez les cartes visibles:</value>\n  </data>\n  <data name=\"MainForm_Simulate_Random_Info\" xml:space=\"preserve\">\n    <value>Ajuster le deck randomisé :(</value>\n  </data>\n  <data name=\"MainForm_Simulate_ResetButton\" xml:space=\"preserve\">\n    <value>Réinitialiser</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule1\" xml:space=\"preserve\">\n    <value>Choisir la règle de la roulette #1</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule2\" xml:space=\"preserve\">\n    <value>Choisir la règle de la roulette #2</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule3\" xml:space=\"preserve\">\n    <value>Choisir la règle de la roulette #3</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule4\" xml:space=\"preserve\">\n    <value>Choisir la règle de la roulette #4</value>\n  </data>\n  <data name=\"MainForm_Simulate_RuleList\" xml:space=\"preserve\">\n    <value>Règles actuelles:</value>\n  </data>\n  <data name=\"MainForm_Simulate_Title\" xml:space=\"preserve\">\n    <value>Lire: Simuler</value>\n  </data>\n  <data name=\"MainForm_Simulate_UndoRedMoveButton\" xml:space=\"preserve\">\n    <value>Annuler le dernier coup Rouge</value>\n  </data>\n  <data name=\"MainForm_Simulate_WinChance\" xml:space=\"preserve\">\n    <value>Chance de victoire:</value>\n  </data>\n  <data name=\"MainForm_UpdateNotify\" xml:space=\"preserve\">\n    <value>Nouvelle version téléchargée, veuillez redémarrer le programme pour terminer la mise à jour. Cliquez ici pour cacher.</value>\n  </data>\n  <data name=\"OverlayForm_Capture_AutoScan\" xml:space=\"preserve\">\n    <value>Balayage auto</value>\n  </data>\n  <data name=\"OverlayForm_Capture_Button\" xml:space=\"preserve\">\n    <value>Capturer</value>\n  </data>\n  <data name=\"OverlayForm_Capture_Details\" xml:space=\"preserve\">\n    <value>Afficher les détails</value>\n  </data>\n  <data name=\"OverlayForm_Capture_Status\" xml:space=\"preserve\">\n    <value>État</value>\n  </data>\n  <data name=\"OverlayForm_CardInfo_Swapped\" xml:space=\"preserve\">\n    <value>ÉCHANGÉ</value>\n  </data>\n  <data name=\"OverlayForm_DeckInfo_Mismatch\" xml:space=\"preserve\">\n    <value>Le paquet bleu ne correspond pas à la page de configuration! Le solveur doit observer quelques jeux pour deviner la bonne.</value>\n  </data>\n  <data name=\"OverlayForm_Details_Npc\" xml:space=\"preserve\">\n    <value>PNJ : {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedDeck\" xml:space=\"preserve\">\n    <value>Détails du deck Rouge :</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedInfo\" xml:space=\"preserve\">\n    <value>&lt; Cartes rouges potentielles, sans inclure celles visibles ci-dessous:</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedPlacedAll\" xml:space=\"preserve\">\n    <value>Tous placés: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedPlacedVariable\" xml:space=\"preserve\">\n    <value>Variable placée: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_Rules\" xml:space=\"preserve\">\n    <value>Règles: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_ScanId\" xml:space=\"preserve\">\n    <value>ID du scan: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_NpcUnknown\" xml:space=\"preserve\">\n    <value>inconnu</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_RulesWaiting\" xml:space=\"preserve\">\n    <value>PNJ a changé, en attente d'analyse...</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_ActiveTurn\" xml:space=\"preserve\">\n    <value>Prêt (tour actif!)</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_AutoScanMouseOverBoard\" xml:space=\"preserve\">\n    <value>Éloignez le curseur de la zone de scan!</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_CactpotReady\" xml:space=\"preserve\">\n    <value>Mini cactpot: Prêt</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_FailedCardMatching\" xml:space=\"preserve\">\n    <value>Cartes inconnues! Vérifiez la capture d'écran pour plus de détails</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingCards\" xml:space=\"preserve\">\n    <value>Deck bleu introuvable</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingGameProcess\" xml:space=\"preserve\">\n    <value>Le jeu n'est pas en cours d'exécution</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingGameWindow\" xml:space=\"preserve\">\n    <value>Impossible de trouver la fenêtre de jeu</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingGrid\" xml:space=\"preserve\">\n    <value>Plateau introuvable</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_NoInputImage\" xml:space=\"preserve\">\n    <value>Impossible de récupérer l'image à analyser</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_NoScannerMatch\" xml:space=\"preserve\">\n    <value>Impossible de trouver la fenêtre du mini-jeu</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_Ready\" xml:space=\"preserve\">\n    <value>Prêt</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_ScannerErrors\" xml:space=\"preserve\">\n    <value>Impossible de reconnaître le mini-jeu</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_UnknownHash\" xml:space=\"preserve\">\n    <value>Modèle inconnu! Vérifiez la lecture:Capture d'écran pour plus de détails</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_WaitingForTurn\" xml:space=\"preserve\">\n    <value>En attente du tour bleu</value>\n  </data>\n  <data name=\"Settings_AlwaysOnTop\" xml:space=\"preserve\">\n    <value>Toujours au premier plan</value>\n  </data>\n  <data name=\"Settings_AlwaysSmallIcons\" xml:space=\"preserve\">\n    <value>Toujours utiliser de petites icônes</value>\n  </data>\n  <data name=\"Settings_DisableHardwareAcceleration\" xml:space=\"preserve\">\n    <value>Désactiver l'accélération matérielle</value>\n  </data>\n  <data name=\"Settings_FontSize\" xml:space=\"preserve\">\n    <value>Taille de police</value>\n  </data>\n  <data name=\"Settings_MarkerDurationCactpot\" xml:space=\"preserve\">\n    <value>Durée du marqueur: cactpot</value>\n  </data>\n  <data name=\"Settings_MarkerDurationCard\" xml:space=\"preserve\">\n    <value>Durée du marqueur: carte</value>\n  </data>\n  <data name=\"Settings_MarkerDurationSwap\" xml:space=\"preserve\">\n    <value>Durée du marqueur: échange</value>\n  </data>\n  <data name=\"Settings_SkipOptionalSimulateRules\" xml:space=\"preserve\">\n    <value>Ignorer les règles optionnelles dans la simulation</value>\n  </data>\n  <data name=\"Settings_Title\" xml:space=\"preserve\">\n    <value>Réglages</value>\n  </data>\n</root>"
  },
  {
    "path": "sources/loc/strings.ja.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\" id=\"root\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\"/>\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\"/>\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\"/>\n              <xsd:attribute name=\"type\" type=\"xsd:string\"/>\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\"/>\n              <xsd:attribute ref=\"xml:space\"/>\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\"/>\n              <xsd:attribute name=\"name\" type=\"xsd:string\"/>\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\"/>\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\"/>\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\"/>\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\"/>\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\"/>\n              <xsd:attribute ref=\"xml:space\"/>\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\"/>\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\"/>\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <data name=\"AdjustForm_CancelButton\" xml:space=\"preserve\">\n    <value>キャンセル</value>\n  </data>\n  <data name=\"AdjustForm_CardDown\" xml:space=\"preserve\">\n    <value>下:</value>\n  </data>\n  <data name=\"AdjustForm_CardLeft\" xml:space=\"preserve\">\n    <value>左:</value>\n  </data>\n  <data name=\"AdjustForm_CardList\" xml:space=\"preserve\">\n    <value>調整後:</value>\n  </data>\n  <data name=\"AdjustForm_CardRight\" xml:space=\"preserve\">\n    <value>右:</value>\n  </data>\n  <data name=\"AdjustForm_CardStatus\" xml:space=\"preserve\">\n    <value>ステータス:</value>\n  </data>\n  <data name=\"AdjustForm_CardUp\" xml:space=\"preserve\">\n    <value>上:</value>\n  </data>\n  <data name=\"AdjustForm_Current\" xml:space=\"preserve\">\n    <value>現在:</value>\n  </data>\n  <data name=\"AdjustForm_Distance\" xml:space=\"preserve\">\n    <value>距離:</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_Hidden\" xml:space=\"preserve\">\n    <value>隠し</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_Locked\" xml:space=\"preserve\">\n    <value>ロック中</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_None\" xml:space=\"preserve\">\n    <value>なし</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_PlacedBlue\" xml:space=\"preserve\">\n    <value>配置済み: 青</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_PlacedRed\" xml:space=\"preserve\">\n    <value>配置済み: 赤</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_Visible\" xml:space=\"preserve\">\n    <value>可視</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Digit_Default\" xml:space=\"preserve\">\n    <value>自動</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Digit_Override\" xml:space=\"preserve\">\n    <value>カスタム</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_Classifier\" xml:space=\"preserve\">\n    <value>&lt;= 分類器による</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_DefaultHint\" xml:space=\"preserve\">\n    <value>&lt;= 低い方が正確です</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_Exact\" xml:space=\"preserve\">\n    <value>&lt;= 完全一致</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_NotAvail\" xml:space=\"preserve\">\n    <value>該当なし</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_UnknownOwner\" xml:space=\"preserve\">\n    <value>不明</value>\n  </data>\n  <data name=\"AdjustForm_HashList\" xml:space=\"preserve\">\n    <value>検出:</value>\n  </data>\n  <data name=\"AdjustForm_SaveButton\" xml:space=\"preserve\">\n    <value>保存する</value>\n  </data>\n  <data name=\"AdjustForm_Title\" xml:space=\"preserve\">\n    <value>検出の調整</value>\n  </data>\n  <data name=\"App_Title\" xml:space=\"preserve\">\n    <value>FF トリプルトライアド バディ</value>\n  </data>\n  <data name=\"DeckCtrl_CtxMenu_LockForOptimization\" xml:space=\"preserve\">\n    <value>デッキの最適化にカードをロックする</value>\n  </data>\n  <data name=\"DeckCtrl_CtxMenu_PickCard\" xml:space=\"preserve\">\n    <value>カードを選択:</value>\n  </data>\n  <data name=\"DeckCtrl_CtxMenu_UseOnlyOwned\" xml:space=\"preserve\">\n    <value>所有しているカードのみを使用(クリックで変更)</value>\n  </data>\n  <data name=\"FavDeckCtrl_Edit\" xml:space=\"preserve\">\n    <value>編集</value>\n  </data>\n  <data name=\"FavDeckForm_AddButton\" xml:space=\"preserve\">\n    <value>追加</value>\n  </data>\n  <data name=\"FavDeckForm_Dynamic_AutoName\" xml:space=\"preserve\">\n    <value>お気に入り #{0}</value>\n  </data>\n  <data name=\"FavDeckForm_Dynamic_RemoveMsg\" xml:space=\"preserve\">\n    <value>お気に入りのデッキは削除されます。続行しますか？</value>\n  </data>\n  <data name=\"FavDeckForm_Dynamic_UpdateButton\" xml:space=\"preserve\">\n    <value>変更</value>\n  </data>\n  <data name=\"FavDeckForm_Info\" xml:space=\"preserve\">\n    <value>お気に入りのデッキ。カードをクリックしてオプションが開けます:</value>\n  </data>\n  <data name=\"FavDeckForm_Name\" xml:space=\"preserve\">\n    <value>略名:</value>\n  </data>\n  <data name=\"FavDeckForm_RemoveButton\" xml:space=\"preserve\">\n    <value>削除</value>\n  </data>\n  <data name=\"FavDeckForm_Title\" xml:space=\"preserve\">\n    <value>お気に入りのデッキを編集</value>\n  </data>\n  <data name=\"Game_ProcessName_DX11\" xml:space=\"preserve\">\n    <value>ffxiv_dx11</value>\n  </data>\n  <data name=\"Game_ProcessName_DX9\" xml:space=\"preserve\">\n    <value>ffxiv</value>\n  </data>\n  <data name=\"Game_WindowNamePrefix\" xml:space=\"preserve\">\n    <value>FINAL FANTASY</value>\n  </data>\n  <data name=\"LocalSaves_Export\" xml:space=\"preserve\">\n    <value>設定をエクスポート</value>\n  </data>\n  <data name=\"LocalSaves_Import\" xml:space=\"preserve\">\n    <value>設定をインポート</value>\n  </data>\n  <data name=\"LocalSaves_ShowBackupFolder\" xml:space=\"preserve\">\n    <value>バックアップを表示</value>\n  </data>\n  <data name=\"LocalSaves_Title\" xml:space=\"preserve\">\n    <value>ローカルセーブ</value>\n  </data>\n  <data name=\"MainForm_Cards_IconsTitle\" xml:space=\"preserve\">\n    <value>アイコン</value>\n  </data>\n  <data name=\"MainForm_Cards_ListTitle\" xml:space=\"preserve\">\n    <value>一覧表示</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnId\" xml:space=\"preserve\">\n    <value>#</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnName\" xml:space=\"preserve\">\n    <value>名前</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnOwned\" xml:space=\"preserve\">\n    <value>所有済み</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnPower\" xml:space=\"preserve\">\n    <value>数字</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnRarity\" xml:space=\"preserve\">\n    <value>レアリティ</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnType\" xml:space=\"preserve\">\n    <value>タイプ</value>\n  </data>\n  <data name=\"MainForm_Cards_NumOwned\" xml:space=\"preserve\">\n    <value>所有カードの枚数:</value>\n  </data>\n  <data name=\"MainForm_Cards_Title\" xml:space=\"preserve\">\n    <value>カード</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_CardInfo_FindOnline\" xml:space=\"preserve\">\n    <value>オンラインで検索</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_CardInfo_NpcReward\" xml:space=\"preserve\">\n    <value>NPC報酬:</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_FindCard\" xml:space=\"preserve\">\n    <value>カードを検索:</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_FindNpc\" xml:space=\"preserve\">\n    <value>NPCを検索:</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_Learn_Adjust\" xml:space=\"preserve\">\n    <value>調整…</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_Learn_Delete\" xml:space=\"preserve\">\n    <value>削除</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_SelectNpc_Rewards\" xml:space=\"preserve\">\n    <value>報酬（クリックで追加/削除）</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_SelectNpc_Select\" xml:space=\"preserve\">\n    <value>対戦NPCを選択</value>\n  </data>\n  <data name=\"MainForm_Dynamic_CardOwnedColumn\" xml:space=\"preserve\">\n    <value>はい</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_HasDuplicates\" xml:space=\"preserve\">\n    <value>重複したカードが見つかりました！</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_MissingCards\" xml:space=\"preserve\">\n    <value>カードがありません！</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_TooMany4Star\" xml:space=\"preserve\">\n    <value>★4以上のカードが最大2枚編成可能!</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_TooMany5Star\" xml:space=\"preserve\">\n    <value>★5のカードが1枚のみ編成可能!</value>\n  </data>\n  <data name=\"MainForm_Dynamic_NpcCompletedColumn\" xml:space=\"preserve\">\n    <value>はい</value>\n  </data>\n  <data name=\"MainForm_Dynamic_RuleListEmpty\" xml:space=\"preserve\">\n    <value>(なし)</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenhot_Status_NoErrors\" xml:space=\"preserve\">\n    <value>有効</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_BoardRow_Bottom\" xml:space=\"preserve\">\n    <value>下</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_BoardRow_Middle\" xml:space=\"preserve\">\n    <value>中</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_BoardRow_Top\" xml:space=\"preserve\">\n    <value>上</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardLocation_BlueDeck\" xml:space=\"preserve\">\n    <value>自分 {0}</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardLocation_Board\" xml:space=\"preserve\">\n    <value>ボード {0} {1}</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardLocation_RedDeck\" xml:space=\"preserve\">\n    <value>相手 {0}</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardNotDetected\" xml:space=\"preserve\">\n    <value>検出されませんでした!</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_HashType_Card\" xml:space=\"preserve\">\n    <value>カード</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_HashType_Number\" xml:space=\"preserve\">\n    <value>数字</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_HashType_Rule\" xml:space=\"preserve\">\n    <value>ルール</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_MatchType_Auto\" xml:space=\"preserve\">\n    <value>自動</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_MatchType_Exact\" xml:space=\"preserve\">\n    <value>完全一致</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_MatchType_Similar\" xml:space=\"preserve\">\n    <value>類似</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_SelectDetectionMatch\" xml:space=\"preserve\">\n    <value>(一致を選択)</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_Disabled\" xml:space=\"preserve\">\n    <value>無効</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_FailedCardMatching\" xml:space=\"preserve\">\n    <value>一部のカードを検出できませんでした！対戦状態は正しくない場合があります。</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_MissingGridOrCards\" xml:space=\"preserve\">\n    <value>ボードを検出できません！UI位置のリセットや、Reshadeを無効にするなどを試してください...</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_UnknownHash\" xml:space=\"preserve\">\n    <value>パターンを検出できません！詳細は以下を参照してください。</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudSave_Loaded\" xml:space=\"preserve\">\n    <value>クラウドデータが適用されました</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_ApiFailure\" xml:space=\"preserve\">\n    <value>API アクセスに失敗しました</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_AuthInProgress\" xml:space=\"preserve\">\n    <value>認証中...</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_Disabled\" xml:space=\"preserve\">\n    <value>無効、ローカルのみ</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NoDatabase\" xml:space=\"preserve\">\n    <value>データベースにエラー</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NoErrors\" xml:space=\"preserve\">\n    <value>有効</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NotAuthorized\" xml:space=\"preserve\">\n    <value>認証が必要です</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NotInitialized\" xml:space=\"preserve\">\n    <value>検出中…</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_Synced\" xml:space=\"preserve\">\n    <value>同期しました</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_Uploaded\" xml:space=\"preserve\">\n    <value>保存済み</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_OptimizerProgressAborted\" xml:space=\"preserve\">\n    <value>{0:P0} で中止されました</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_BlueStartButton\" xml:space=\"preserve\">\n    <value>自分が先攻</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_ChanceIsDraw\" xml:space=\"preserve\">\n    <value>(引き分け)</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_ChangeBlueHint\" xml:space=\"preserve\">\n    <value>クリックしてカードを変更</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_EndGame_BlueDraw\" xml:space=\"preserve\">\n    <value>引き分け</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_EndGame_BlueLost\" xml:space=\"preserve\">\n    <value>敗北</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_EndGame_BlueWin\" xml:space=\"preserve\">\n    <value>やった！</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_LastCardHint\" xml:space=\"preserve\">\n    <value>[相手のカードを配置してルールを適用する]</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_RedStartButton\" xml:space=\"preserve\">\n    <value>相手先攻: カードをボードへドラッグ&amp;ドロップ</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_SwapRuleButton\" xml:space=\"preserve\">\n    <value>各デッキからカードを1枚選択して交換する</value>\n  </data>\n  <data name=\"MainForm_Info_BugReports\" xml:space=\"preserve\">\n    <value>バグ報告:</value>\n  </data>\n  <data name=\"MainForm_Info_HomePage\" xml:space=\"preserve\">\n    <value>ホームページ:</value>\n  </data>\n  <data name=\"MainForm_Info_Localization\" xml:space=\"preserve\">\n    <value>UI のローカライズ:</value>\n  </data>\n  <data name=\"MainForm_Info_Title\" xml:space=\"preserve\">\n    <value>情報と設定</value>\n  </data>\n  <data name=\"MainForm_Info_TranslatorLove\" xml:space=\"preserve\">\n    <value>翻訳に貢献してくれた皆さん、ありがとうございます！皆さん素晴らしいです！</value>\n  </data>\n  <data name=\"MainForm_Info_TranslatorNeeded\" xml:space=\"preserve\">\n    <value>(ボランティア募集中 ;) )</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnCompleted\" xml:space=\"preserve\">\n    <value>完了</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnLocation\" xml:space=\"preserve\">\n    <value>位置</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnName\" xml:space=\"preserve\">\n    <value>名前</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnPower\" xml:space=\"preserve\">\n    <value>数字</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnReward\" xml:space=\"preserve\">\n    <value>報酬</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnRules\" xml:space=\"preserve\">\n    <value>ルール</value>\n  </data>\n  <data name=\"MainForm_Npcs_NumKnown\" xml:space=\"preserve\">\n    <value>カードを獲得できるNPC数:</value>\n  </data>\n  <data name=\"MainForm_Npcs_Title\" xml:space=\"preserve\">\n    <value>NPC</value>\n  </data>\n  <data name=\"MainForm_Screenshot_CurrentState\" xml:space=\"preserve\">\n    <value>現在の状態:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_CardsColumnDetection\" xml:space=\"preserve\">\n    <value>最新の検出</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_CardsColumnSides\" xml:space=\"preserve\">\n    <value>数字 [上-左-下-右]</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_CardsColumnType\" xml:space=\"preserve\">\n    <value>タイプ</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_HashColumnDetection\" xml:space=\"preserve\">\n    <value>最新の検出</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_HashColumnType\" xml:space=\"preserve\">\n    <value>タイプ</value>\n  </data>\n  <data name=\"MainForm_Screenshot_InfoLines\" xml:space=\"preserve\">\n    <value>手順:\n- ゲーム画面をウィンドウモードや仮想フルスクリーンモードへ切替\n- UIサイズを100%を設定、カラーテーマをダークにする。\n- カードのアニメーションが終わった後、自分の手番で画面をキャプチャ(!!!)\n\nコントローラーを使っている方: LT/RTを同時押し。\n\nミニ・くじテンダーにも使える。</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_DetectList\" xml:space=\"preserve\">\n    <value>検出:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_DiscardAllButton\" xml:space=\"preserve\">\n    <value>すべてを破棄する</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_DiscardAllInfo\" xml:space=\"preserve\">\n    <value>&lt;&lt; スキャナをリセット</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_PendingPlural\" xml:space=\"preserve\">\n    <value>({0} 件が保留中です)</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_PendingSingular\" xml:space=\"preserve\">\n    <value>(1 件が保留中です)</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_SaveButton\" xml:space=\"preserve\">\n    <value>保存する</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_SourceImage\" xml:space=\"preserve\">\n    <value>ソース画像:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_Type\" xml:space=\"preserve\">\n    <value>タイプ:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_ListHint\" xml:space=\"preserve\">\n    <value>右クリックで詳細オプションを開く</value>\n  </data>\n  <data name=\"MainForm_Screenshot_RemovePatternsButton\" xml:space=\"preserve\">\n    <value>保存したパターンを削除</value>\n  </data>\n  <data name=\"MainForm_Screenshot_RemovePatternsTitle\" xml:space=\"preserve\">\n    <value>カスタムパターンを削除して検出機能をリセット</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Title\" xml:space=\"preserve\">\n    <value>対戦: 検出機能</value>\n  </data>\n  <data name=\"MainForm_Setup_Cloud_AuthButton\" xml:space=\"preserve\">\n    <value>ログイン:</value>\n  </data>\n  <data name=\"MainForm_Setup_Cloud_Desc\" xml:space=\"preserve\">\n    <value>Google Driveに保存</value>\n  </data>\n  <data name=\"MainForm_Setup_Deck_OptimizeAbortButton\" xml:space=\"preserve\">\n    <value>中止</value>\n  </data>\n  <data name=\"MainForm_Setup_Deck_OptimizeStartButton\" xml:space=\"preserve\">\n    <value>デッキをNPCによって最適化</value>\n  </data>\n  <data name=\"MainForm_Setup_Deck_Title\" xml:space=\"preserve\">\n    <value>現在のデッキ、カードをクリックしてオプションが開けます:</value>\n  </data>\n  <data name=\"MainForm_Setup_Fav_AddSlotButton\" xml:space=\"preserve\">\n    <value>デッキをお気に入りに追加</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC\" xml:space=\"preserve\">\n    <value>NPC:</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_DeckPower\" xml:space=\"preserve\">\n    <value>デッキの数字:</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_Location\" xml:space=\"preserve\">\n    <value>位置:</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_Rules\" xml:space=\"preserve\">\n    <value>ルール:</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_WinChance\" xml:space=\"preserve\">\n    <value>勝率:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_NumOwned\" xml:space=\"preserve\">\n    <value>所有カード数:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_NumPossible\" xml:space=\"preserve\">\n    <value>可能なデッキ数:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_NumTested\" xml:space=\"preserve\">\n    <value>テスト済みデッキ数:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_Progress\" xml:space=\"preserve\">\n    <value>進捗状況：</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_TimeLeft\" xml:space=\"preserve\">\n    <value>残り時間:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizeStats_Title\" xml:space=\"preserve\">\n    <value>最適化統計:</value>\n  </data>\n  <data name=\"MainForm_Setup_RulesToggle\" xml:space=\"preserve\">\n    <value>NPCルールを切り替え、トーナメントで無効にする</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_Region1\" xml:space=\"preserve\">\n    <value>流行ルール1:</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_Region2\" xml:space=\"preserve\">\n    <value>流行ルール2:</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_Tournament\" xml:space=\"preserve\">\n    <value>トーナメント:</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_TournamentRules\" xml:space=\"preserve\">\n    <value>ルール:</value>\n  </data>\n  <data name=\"MainForm_Setup_Title\" xml:space=\"preserve\">\n    <value>設定</value>\n  </data>\n  <data name=\"MainForm_Simulate_Debug_ForceCached\" xml:space=\"preserve\">\n    <value>強制的にキャッシュされた画像を使用する</value>\n  </data>\n  <data name=\"MainForm_Simulate_Debug_Info\" xml:space=\"preserve\">\n    <value>スクリーンショットを撮る: デバッグ用</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_ApplyRuleButton\" xml:space=\"preserve\">\n    <value>ルールを適用</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_KnownCards\" xml:space=\"preserve\">\n    <value>既知のカード:</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_ListHint\" xml:space=\"preserve\">\n    <value>[カードをボードへドラッグ&amp;ドロップ]</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_SkipRuleButton\" xml:space=\"preserve\">\n    <value>スキップ</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_SpecialRule\" xml:space=\"preserve\">\n    <value>特殊ルール &gt;&gt;</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_UnknownCards\" xml:space=\"preserve\">\n    <value>不明なカードの残り: {0}</value>\n  </data>\n  <data name=\"MainForm_Simulate_Open_Hint\" xml:space=\"preserve\">\n    <value>表示中のカードを選択:</value>\n  </data>\n  <data name=\"MainForm_Simulate_Random_Info\" xml:space=\"preserve\">\n    <value>ランダムなデッキを調整する :(</value>\n  </data>\n  <data name=\"MainForm_Simulate_ResetButton\" xml:space=\"preserve\">\n    <value>リセット</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule1\" xml:space=\"preserve\">\n    <value>ルーレットルール#1を選択</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule2\" xml:space=\"preserve\">\n    <value>ルーレットルール#2を選択</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule3\" xml:space=\"preserve\">\n    <value>ルーレットルール#3を選択</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule4\" xml:space=\"preserve\">\n    <value>ルーレットルール#4を選択</value>\n  </data>\n  <data name=\"MainForm_Simulate_RuleList\" xml:space=\"preserve\">\n    <value>現在のルール:</value>\n  </data>\n  <data name=\"MainForm_Simulate_Title\" xml:space=\"preserve\">\n    <value>対戦: シミュレーション</value>\n  </data>\n  <data name=\"MainForm_Simulate_UndoRedMoveButton\" xml:space=\"preserve\">\n    <value>最後の相手カード操作を元に戻す</value>\n  </data>\n  <data name=\"MainForm_Simulate_WinChance\" xml:space=\"preserve\">\n    <value>勝率:</value>\n  </data>\n  <data name=\"MainForm_UpdateNotify\" xml:space=\"preserve\">\n    <value>新しいバージョンがダウンロードされました。アップデートを完了するためにプログラムを再起動してください。非表示にするにはここをクリックしてください。</value>\n  </data>\n  <data name=\"OverlayForm_Capture_AutoScan\" xml:space=\"preserve\">\n    <value>自動スキャン</value>\n  </data>\n  <data name=\"OverlayForm_Capture_Button\" xml:space=\"preserve\">\n    <value>キャプチャ</value>\n  </data>\n  <data name=\"OverlayForm_Capture_Details\" xml:space=\"preserve\">\n    <value>詳細表示</value>\n  </data>\n  <data name=\"OverlayForm_Capture_Status\" xml:space=\"preserve\">\n    <value>ステータス</value>\n  </data>\n  <data name=\"OverlayForm_CardInfo_Swapped\" xml:space=\"preserve\">\n    <value>交換済み</value>\n  </data>\n  <data name=\"OverlayForm_DeckInfo_Mismatch\" xml:space=\"preserve\">\n    <value>自分のデッキはセットアップページと一致しません！ソルバーは正しいカードを検出するためにいくつかの対戦を観察する必要があります。</value>\n  </data>\n  <data name=\"OverlayForm_Details_Npc\" xml:space=\"preserve\">\n    <value>NPC: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedDeck\" xml:space=\"preserve\">\n    <value>デッキ詳細を表示</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedInfo\" xml:space=\"preserve\">\n    <value>&lt; 可能な相手のカードです。以下に表示されるカードは含みません：</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedPlacedAll\" xml:space=\"preserve\">\n    <value>必ず持ち: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedPlacedVariable\" xml:space=\"preserve\">\n    <value>持ち可能: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_Rules\" xml:space=\"preserve\">\n    <value>ルール: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_ScanId\" xml:space=\"preserve\">\n    <value>スキャンID: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_NpcUnknown\" xml:space=\"preserve\">\n    <value>不明</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_RulesWaiting\" xml:space=\"preserve\">\n    <value>NPCが変更されたので、再スキャンを待ってください…</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_ActiveTurn\" xml:space=\"preserve\">\n    <value>準備完了 (俺のターン!)</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_AutoScanMouseOverBoard\" xml:space=\"preserve\">\n    <value>スキャンゾーンからカーソルを遠ざける！</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_CactpotReady\" xml:space=\"preserve\">\n    <value>ミニサボテン: 準備完了</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_FailedCardMatching\" xml:space=\"preserve\">\n    <value>不明なカードです！詳細は[対戦: スクリーンショット]で確認してください</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingCards\" xml:space=\"preserve\">\n    <value>自分のデッキが見つかりません</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingGameProcess\" xml:space=\"preserve\">\n    <value>ゲームが起動していません</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingGameWindow\" xml:space=\"preserve\">\n    <value>ゲームウィンドウは見つかりません</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingGrid\" xml:space=\"preserve\">\n    <value>ボードが見つかりません</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_NoInputImage\" xml:space=\"preserve\">\n    <value>画像を取得できません。</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_NoScannerMatch\" xml:space=\"preserve\">\n    <value>ミニゲームウィンドウが見つかりません</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_Ready\" xml:space=\"preserve\">\n    <value>準備完了</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_ScannerErrors\" xml:space=\"preserve\">\n    <value>ミニゲームウィンドウを検出できませんでした。</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_UnknownHash\" xml:space=\"preserve\">\n    <value>不明なパターンです！詳細は[対戦: スクリーンショット]で確認してください</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_WaitingForTurn\" xml:space=\"preserve\">\n    <value>自分の手番を待っています</value>\n  </data>\n  <data name=\"Settings_AlwaysOnTop\" xml:space=\"preserve\">\n    <value>常に最前面に表示する</value>\n  </data>\n  <data name=\"Settings_AlwaysSmallIcons\" xml:space=\"preserve\">\n    <value>常に小さなアイコンを使用</value>\n  </data>\n  <data name=\"Settings_DisableHardwareAcceleration\" xml:space=\"preserve\">\n    <value>ハードウェアアクセラレーションを無効にする</value>\n  </data>\n  <data name=\"Settings_FontSize\" xml:space=\"preserve\">\n    <value>フォント サイズ</value>\n  </data>\n  <data name=\"Settings_MarkerDurationCactpot\" xml:space=\"preserve\">\n    <value>マーカーの継続時間: ミニ・くじ</value>\n  </data>\n  <data name=\"Settings_MarkerDurationCard\" xml:space=\"preserve\">\n    <value>マーカーの継続時間: カード</value>\n  </data>\n  <data name=\"Settings_MarkerDurationSwap\" xml:space=\"preserve\">\n    <value>マーカーの継続時間: スワップ</value>\n  </data>\n  <data name=\"Settings_SkipOptionalSimulateRules\" xml:space=\"preserve\">\n    <value>シミュレーションでオプションのルールをスキップ</value>\n  </data>\n  <data name=\"Settings_Title\" xml:space=\"preserve\">\n    <value>設定</value>\n  </data>\n</root>"
  },
  {
    "path": "sources/loc/strings.ko.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\" id=\"root\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\"/>\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\"/>\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\"/>\n              <xsd:attribute name=\"type\" type=\"xsd:string\"/>\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\"/>\n              <xsd:attribute ref=\"xml:space\"/>\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\"/>\n              <xsd:attribute name=\"name\" type=\"xsd:string\"/>\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\"/>\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\"/>\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\"/>\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\"/>\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\"/>\n              <xsd:attribute ref=\"xml:space\"/>\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\"/>\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\"/>\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <data name=\"AdjustForm_CancelButton\" xml:space=\"preserve\">\n    <value>취소</value>\n  </data>\n  <data name=\"AdjustForm_CardDown\" xml:space=\"preserve\">\n    <value>하:</value>\n  </data>\n  <data name=\"AdjustForm_CardLeft\" xml:space=\"preserve\">\n    <value>좌:</value>\n  </data>\n  <data name=\"AdjustForm_CardList\" xml:space=\"preserve\">\n    <value>조정됨:</value>\n  </data>\n  <data name=\"AdjustForm_CardRight\" xml:space=\"preserve\">\n    <value>우:</value>\n  </data>\n  <data name=\"AdjustForm_CardStatus\" xml:space=\"preserve\">\n    <value>상태:</value>\n  </data>\n  <data name=\"AdjustForm_CardUp\" xml:space=\"preserve\">\n    <value>상:</value>\n  </data>\n  <data name=\"AdjustForm_Current\" xml:space=\"preserve\">\n    <value>현재:</value>\n  </data>\n  <data name=\"AdjustForm_Distance\" xml:space=\"preserve\">\n    <value>거리:</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_Hidden\" xml:space=\"preserve\">\n    <value>숨김</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_Locked\" xml:space=\"preserve\">\n    <value>잠김</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_None\" xml:space=\"preserve\">\n    <value>없음</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_PlacedBlue\" xml:space=\"preserve\">\n    <value>파란색 차례</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_PlacedRed\" xml:space=\"preserve\">\n    <value>빨간색 차례</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_Visible\" xml:space=\"preserve\">\n    <value>보이기</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Digit_Default\" xml:space=\"preserve\">\n    <value>자동</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Digit_Override\" xml:space=\"preserve\">\n    <value>사용자 정의</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_Classifier\" xml:space=\"preserve\">\n    <value>&lt;= 분류 기반</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_DefaultHint\" xml:space=\"preserve\">\n    <value>&lt;= 낮은 쪽이 더 정확</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_Exact\" xml:space=\"preserve\">\n    <value>&lt;= 정확히 일치</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_NotAvail\" xml:space=\"preserve\">\n    <value>해당 없음</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_UnknownOwner\" xml:space=\"preserve\">\n    <value>알 수 없음</value>\n  </data>\n  <data name=\"AdjustForm_HashList\" xml:space=\"preserve\">\n    <value>감지:</value>\n  </data>\n  <data name=\"AdjustForm_SaveButton\" xml:space=\"preserve\">\n    <value>저장</value>\n  </data>\n  <data name=\"AdjustForm_Title\" xml:space=\"preserve\">\n    <value>조정 감지함</value>\n  </data>\n  <data name=\"App_Title\" xml:space=\"preserve\">\n    <value>FF 트라이어드 버디</value>\n  </data>\n  <data name=\"DeckCtrl_CtxMenu_LockForOptimization\" xml:space=\"preserve\">\n    <value>덱 최적화를 위한 카드 잠금</value>\n  </data>\n  <data name=\"DeckCtrl_CtxMenu_PickCard\" xml:space=\"preserve\">\n    <value>카드 선택:</value>\n  </data>\n  <data name=\"DeckCtrl_CtxMenu_UseOnlyOwned\" xml:space=\"preserve\">\n    <value>갖고 있는 카드만 사용 (눌러서 변경)</value>\n  </data>\n  <data name=\"FavDeckCtrl_Edit\" xml:space=\"preserve\">\n    <value>편집</value>\n  </data>\n  <data name=\"FavDeckForm_AddButton\" xml:space=\"preserve\">\n    <value>추가</value>\n  </data>\n  <data name=\"FavDeckForm_Dynamic_AutoName\" xml:space=\"preserve\">\n    <value>즐겨찾기 #{0}</value>\n  </data>\n  <data name=\"FavDeckForm_Dynamic_RemoveMsg\" xml:space=\"preserve\">\n    <value>즐겨찾기한 덱을 제거합니다, 확실한가요?</value>\n  </data>\n  <data name=\"FavDeckForm_Dynamic_UpdateButton\" xml:space=\"preserve\">\n    <value>업데이트</value>\n  </data>\n  <data name=\"FavDeckForm_Info\" xml:space=\"preserve\">\n    <value>즐겨찾기 덱, 카드를 눌러서 설정:</value>\n  </data>\n  <data name=\"FavDeckForm_Name\" xml:space=\"preserve\">\n    <value>짧은 이름:</value>\n  </data>\n  <data name=\"FavDeckForm_RemoveButton\" xml:space=\"preserve\">\n    <value>제거</value>\n  </data>\n  <data name=\"FavDeckForm_Title\" xml:space=\"preserve\">\n    <value>즐겨찾기 덱 편집</value>\n  </data>\n  <data name=\"Game_ProcessName_DX11\" xml:space=\"preserve\">\n    <value>ffxiv_dx11</value>\n  </data>\n  <data name=\"Game_ProcessName_DX9\" xml:space=\"preserve\">\n    <value>ffxiv</value>\n  </data>\n  <data name=\"Game_WindowNamePrefix\" xml:space=\"preserve\">\n    <value>파이널 판타지</value>\n  </data>\n  <data name=\"LocalSaves_Export\" xml:space=\"preserve\">\n    <value>설정 내보내기</value>\n  </data>\n  <data name=\"LocalSaves_Import\" xml:space=\"preserve\">\n    <value>설정 가져오기</value>\n  </data>\n  <data name=\"LocalSaves_ShowBackupFolder\" xml:space=\"preserve\">\n    <value>백업 목록 보기</value>\n  </data>\n  <data name=\"LocalSaves_Title\" xml:space=\"preserve\">\n    <value>이 컴퓨터에 저장</value>\n  </data>\n  <data name=\"MainForm_Cards_IconsTitle\" xml:space=\"preserve\">\n    <value>아이콘</value>\n  </data>\n  <data name=\"MainForm_Cards_ListTitle\" xml:space=\"preserve\">\n    <value>목록</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnId\" xml:space=\"preserve\">\n    <value>#</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnName\" xml:space=\"preserve\">\n    <value>이름</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnOwned\" xml:space=\"preserve\">\n    <value>갖고있음</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnPower\" xml:space=\"preserve\">\n    <value>성능</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnRarity\" xml:space=\"preserve\">\n    <value>레어도</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnType\" xml:space=\"preserve\">\n    <value>구분</value>\n  </data>\n  <data name=\"MainForm_Cards_NumOwned\" xml:space=\"preserve\">\n    <value>갖고 있는 카드 개수:</value>\n  </data>\n  <data name=\"MainForm_Cards_Title\" xml:space=\"preserve\">\n    <value>카드</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_CardInfo_FindOnline\" xml:space=\"preserve\">\n    <value>온라인에서 찾기</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_CardInfo_NpcReward\" xml:space=\"preserve\">\n    <value>NPC 보상:</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_FindCard\" xml:space=\"preserve\">\n    <value>카드 찾기:</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_FindNpc\" xml:space=\"preserve\">\n    <value>NPC 찾기:</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_Learn_Adjust\" xml:space=\"preserve\">\n    <value>조정...</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_Learn_Delete\" xml:space=\"preserve\">\n    <value>지우기</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_SelectNpc_Rewards\" xml:space=\"preserve\">\n    <value>보상 (눌러서 추가/삭제)</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_SelectNpc_Select\" xml:space=\"preserve\">\n    <value>Npc를 고르세요</value>\n  </data>\n  <data name=\"MainForm_Dynamic_CardOwnedColumn\" xml:space=\"preserve\">\n    <value>네</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_HasDuplicates\" xml:space=\"preserve\">\n    <value>중복 카드 발견!</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_MissingCards\" xml:space=\"preserve\">\n    <value>보유하지 않은 카드</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_TooMany4Star\" xml:space=\"preserve\">\n    <value>4성카드가 2개보다 많습니다.</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_TooMany5Star\" xml:space=\"preserve\">\n    <value>5성카드 한 개만 덱에 넣을 수 있습니다.</value>\n  </data>\n  <data name=\"MainForm_Dynamic_NpcCompletedColumn\" xml:space=\"preserve\">\n    <value>네</value>\n  </data>\n  <data name=\"MainForm_Dynamic_RuleListEmpty\" xml:space=\"preserve\">\n    <value>(없음)</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenhot_Status_NoErrors\" xml:space=\"preserve\">\n    <value>활성화</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_BoardRow_Bottom\" xml:space=\"preserve\">\n    <value>밑</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_BoardRow_Middle\" xml:space=\"preserve\">\n    <value>중앙</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_BoardRow_Top\" xml:space=\"preserve\">\n    <value>위</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardLocation_BlueDeck\" xml:space=\"preserve\">\n    <value>파랑 {0}</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardLocation_Board\" xml:space=\"preserve\">\n    <value>보드 {0} {1}</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardLocation_RedDeck\" xml:space=\"preserve\">\n    <value>빨강 {0}</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardNotDetected\" xml:space=\"preserve\">\n    <value>검출되지 않았습니다.</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_HashType_Card\" xml:space=\"preserve\">\n    <value>카드</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_HashType_Number\" xml:space=\"preserve\">\n    <value>숫자</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_HashType_Rule\" xml:space=\"preserve\">\n    <value>규칙</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_MatchType_Auto\" xml:space=\"preserve\">\n    <value>자동</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_MatchType_Exact\" xml:space=\"preserve\">\n    <value>정확한</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_MatchType_Similar\" xml:space=\"preserve\">\n    <value>유사</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_SelectDetectionMatch\" xml:space=\"preserve\">\n    <value>(일치를 선택)</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_Disabled\" xml:space=\"preserve\">\n    <value>무효</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_FailedCardMatching\" xml:space=\"preserve\">\n    <value>일부 카드를 검출하지 못했습니다! 대전 상태가 올바르지 않을 경우가 있습니다.</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_MissingGridOrCards\" xml:space=\"preserve\">\n    <value>보드를 검출할 수 없습니다. UI 위치의 리셋 또는 Reshade를 무효로 하는 등 시도해주세요.</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_UnknownHash\" xml:space=\"preserve\">\n    <value>패턴을 검출할 수 없습니다. 상세 사항은 아래를 참조해주세요.</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudSave_Loaded\" xml:space=\"preserve\">\n    <value>클라우드 데이터가 적용되었습니다</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_ApiFailure\" xml:space=\"preserve\">\n    <value>API 액세스 실패</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_AuthInProgress\" xml:space=\"preserve\">\n    <value>인증중...</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_Disabled\" xml:space=\"preserve\">\n    <value>무효, 로컬만 가능</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NoDatabase\" xml:space=\"preserve\">\n    <value>데이터베이스 에러</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NoErrors\" xml:space=\"preserve\">\n    <value>활성화</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NotAuthorized\" xml:space=\"preserve\">\n    <value>인증이 필요합니다.</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NotInitialized\" xml:space=\"preserve\">\n    <value>검색 중...</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_Synced\" xml:space=\"preserve\">\n    <value>동기화 성공!</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_Uploaded\" xml:space=\"preserve\">\n    <value>보존됨</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_OptimizerProgressAborted\" xml:space=\"preserve\">\n    <value>{0:P0}에 종료</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_BlueStartButton\" xml:space=\"preserve\">\n    <value>파란색 우선시작</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_ChanceIsDraw\" xml:space=\"preserve\">\n    <value>(무승부)</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_ChangeBlueHint\" xml:space=\"preserve\">\n    <value>바꿀 카드를 선택</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_EndGame_BlueDraw\" xml:space=\"preserve\">\n    <value>동점</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_EndGame_BlueLost\" xml:space=\"preserve\">\n    <value>패배</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_EndGame_BlueWin\" xml:space=\"preserve\">\n    <value>이겼다!</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_LastCardHint\" xml:space=\"preserve\">\n    <value>[수정 트리거를 위한 마지막 빨간 카드 놓기]</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_RedStartButton\" xml:space=\"preserve\">\n    <value>빨강 시작: 카드를 보드에 끌어다 놓으세요</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_SwapRuleButton\" xml:space=\"preserve\">\n    <value>서로 바꿀 카드를 각각의 덱에서 고르세요</value>\n  </data>\n  <data name=\"MainForm_Info_BugReports\" xml:space=\"preserve\">\n    <value>버그 보고:</value>\n  </data>\n  <data name=\"MainForm_Info_HomePage\" xml:space=\"preserve\">\n    <value>프로젝트 웹사이트:</value>\n  </data>\n  <data name=\"MainForm_Info_Localization\" xml:space=\"preserve\">\n    <value>UI 지역화:</value>\n  </data>\n  <data name=\"MainForm_Info_Title\" xml:space=\"preserve\">\n    <value>정보 &amp; 설정</value>\n  </data>\n  <data name=\"MainForm_Info_TranslatorLove\" xml:space=\"preserve\">\n    <value>모든 번역 제공자분들에게 감사드립니다! 다들 멋져요!</value>\n  </data>\n  <data name=\"MainForm_Info_TranslatorNeeded\" xml:space=\"preserve\">\n    <value>(봉사자가 아직 필요해요 😵)</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnCompleted\" xml:space=\"preserve\">\n    <value>완료함</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnLocation\" xml:space=\"preserve\">\n    <value>위치</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnName\" xml:space=\"preserve\">\n    <value>이름</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnPower\" xml:space=\"preserve\">\n    <value>파워</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnReward\" xml:space=\"preserve\">\n    <value>보상</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnRules\" xml:space=\"preserve\">\n    <value>규칙</value>\n  </data>\n  <data name=\"MainForm_Npcs_NumKnown\" xml:space=\"preserve\">\n    <value>카드를 갖고 있는 NPC 수:</value>\n  </data>\n  <data name=\"MainForm_Npcs_Title\" xml:space=\"preserve\">\n    <value>NPC들</value>\n  </data>\n  <data name=\"MainForm_Screenshot_CurrentState\" xml:space=\"preserve\">\n    <value>현재 상태:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_CardsColumnDetection\" xml:space=\"preserve\">\n    <value>마지막 감지</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_CardsColumnSides\" xml:space=\"preserve\">\n    <value>숫자 [U-L-D-R]</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_CardsColumnType\" xml:space=\"preserve\">\n    <value>형식</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_HashColumnDetection\" xml:space=\"preserve\">\n    <value>마지막 감지</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_HashColumnType\" xml:space=\"preserve\">\n    <value>형식</value>\n  </data>\n  <data name=\"MainForm_Screenshot_InfoLines\" xml:space=\"preserve\">\n    <value>규칙:\n- 게임을 창 또는 전체 창 모드로 실행해야 합니다.\n- UI: 배율은 100%로, 어두운 구성표로 사용합니다.\n- NPC를 선택해야 합니다.\n- 모든 카드 애니메이션이 완료된 후, 파랑 턴에 갈무리 합니다 (!!!)\n\n게임 패드: 트리거 버튼을 동시에 눌러 갈무리 할 수 있습니다\n\n미니 복권에서도 쓸 수 있습니다! 🥰</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_DetectList\" xml:space=\"preserve\">\n    <value>감지:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_DiscardAllButton\" xml:space=\"preserve\">\n    <value>모두 버리기</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_DiscardAllInfo\" xml:space=\"preserve\">\n    <value>&lt;&lt; 스캔 상태 초기화</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_PendingPlural\" xml:space=\"preserve\">\n    <value>({0} 이상 대기중)</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_PendingSingular\" xml:space=\"preserve\">\n    <value>(1 이상 대기중)</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_SaveButton\" xml:space=\"preserve\">\n    <value>저장</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_SourceImage\" xml:space=\"preserve\">\n    <value>소스 이미지:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_Type\" xml:space=\"preserve\">\n    <value>형식:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_ListHint\" xml:space=\"preserve\">\n    <value>옵션 목록에서 마우스 우클릭</value>\n  </data>\n  <data name=\"MainForm_Screenshot_RemovePatternsButton\" xml:space=\"preserve\">\n    <value>로컬 패턴 해제</value>\n  </data>\n  <data name=\"MainForm_Screenshot_RemovePatternsTitle\" xml:space=\"preserve\">\n    <value>모든 커스텀 패턴을 지우고 검출기능을 리셋합니다:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Title\" xml:space=\"preserve\">\n    <value>플레이: 스크린샷</value>\n  </data>\n  <data name=\"MainForm_Setup_Cloud_AuthButton\" xml:space=\"preserve\">\n    <value>로그인:</value>\n  </data>\n  <data name=\"MainForm_Setup_Cloud_Desc\" xml:space=\"preserve\">\n    <value>구글에 저장:</value>\n  </data>\n  <data name=\"MainForm_Setup_Deck_OptimizeAbortButton\" xml:space=\"preserve\">\n    <value>중단</value>\n  </data>\n  <data name=\"MainForm_Setup_Deck_OptimizeStartButton\" xml:space=\"preserve\">\n    <value>NPC용 덱 최적화</value>\n  </data>\n  <data name=\"MainForm_Setup_Deck_Title\" xml:space=\"preserve\">\n    <value>활성화 덱, 옵션을 보려면 카드를 클릭하세요</value>\n  </data>\n  <data name=\"MainForm_Setup_Fav_AddSlotButton\" xml:space=\"preserve\">\n    <value>즐겨찾기에 덱 추가</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC\" xml:space=\"preserve\">\n    <value>NPC:</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_DeckPower\" xml:space=\"preserve\">\n    <value>덱 파워</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_Location\" xml:space=\"preserve\">\n    <value>위치</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_Rules\" xml:space=\"preserve\">\n    <value>규칙</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_WinChance\" xml:space=\"preserve\">\n    <value>승률:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_NumOwned\" xml:space=\"preserve\">\n    <value>갖고 있는 카드 수:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_NumPossible\" xml:space=\"preserve\">\n    <value>가능한 덱 수:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_NumTested\" xml:space=\"preserve\">\n    <value>테스트한 덱 수:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_Progress\" xml:space=\"preserve\">\n    <value>진행 상황</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_TimeLeft\" xml:space=\"preserve\">\n    <value>남은 시간:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizeStats_Title\" xml:space=\"preserve\">\n    <value>통계:</value>\n  </data>\n  <data name=\"MainForm_Setup_RulesToggle\" xml:space=\"preserve\">\n    <value>Npc 규칙 표시, 토너먼트에 사용안함</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_Region1\" xml:space=\"preserve\">\n    <value>유행 규칙 1:</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_Region2\" xml:space=\"preserve\">\n    <value>유행 규칙 2:</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_Tournament\" xml:space=\"preserve\">\n    <value>토너먼트:</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_TournamentRules\" xml:space=\"preserve\">\n    <value>규칙:</value>\n  </data>\n  <data name=\"MainForm_Setup_Title\" xml:space=\"preserve\">\n    <value>설정</value>\n  </data>\n  <data name=\"MainForm_Simulate_Debug_ForceCached\" xml:space=\"preserve\">\n    <value>강제로 캐시 이미지 사용</value>\n  </data>\n  <data name=\"MainForm_Simulate_Debug_Info\" xml:space=\"preserve\">\n    <value>스크린 샷 찍기: 디버그 모드</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_ApplyRuleButton\" xml:space=\"preserve\">\n    <value>규칙 적용</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_KnownCards\" xml:space=\"preserve\">\n    <value>알고 있는 카드:</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_ListHint\" xml:space=\"preserve\">\n    <value>[카드를 보드에 끌어다 놓기]</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_SkipRuleButton\" xml:space=\"preserve\">\n    <value>건너뛰기</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_SpecialRule\" xml:space=\"preserve\">\n    <value>특별한 룰 &gt;&gt;</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_UnknownCards\" xml:space=\"preserve\">\n    <value>남은 알 수 없는 카드: {0}</value>\n  </data>\n  <data name=\"MainForm_Simulate_Open_Hint\" xml:space=\"preserve\">\n    <value>보이는 카드 선택:</value>\n  </data>\n  <data name=\"MainForm_Simulate_Random_Info\" xml:space=\"preserve\">\n    <value>임의 덱 조정 :(</value>\n  </data>\n  <data name=\"MainForm_Simulate_ResetButton\" xml:space=\"preserve\">\n    <value>초기화</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule1\" xml:space=\"preserve\">\n    <value>루렛 규칙 선택 #1</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule2\" xml:space=\"preserve\">\n    <value>루렛 규칙 선택 #2</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule3\" xml:space=\"preserve\">\n    <value>루렛 규칙 선택 #3</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule4\" xml:space=\"preserve\">\n    <value>루렛 규칙 선택 #4</value>\n  </data>\n  <data name=\"MainForm_Simulate_RuleList\" xml:space=\"preserve\">\n    <value>현재 규칙:</value>\n  </data>\n  <data name=\"MainForm_Simulate_Title\" xml:space=\"preserve\">\n    <value>플레이: 시뮬레이션</value>\n  </data>\n  <data name=\"MainForm_Simulate_UndoRedMoveButton\" xml:space=\"preserve\">\n    <value>마지막 빨강 이동 물리기</value>\n  </data>\n  <data name=\"MainForm_Simulate_WinChance\" xml:space=\"preserve\">\n    <value>승리 찬스:</value>\n  </data>\n  <data name=\"MainForm_UpdateNotify\" xml:space=\"preserve\">\n    <value>새 버전으로 업데이트하려면 다시 시작하세요. 여기를 누르면 보이지 않습니다</value>\n  </data>\n  <data name=\"OverlayForm_Capture_AutoScan\" xml:space=\"preserve\">\n    <value>자동 스캔</value>\n  </data>\n  <data name=\"OverlayForm_Capture_Button\" xml:space=\"preserve\">\n    <value>갈무리</value>\n  </data>\n  <data name=\"OverlayForm_Capture_Details\" xml:space=\"preserve\">\n    <value>자세히 보기</value>\n  </data>\n  <data name=\"OverlayForm_Capture_Status\" xml:space=\"preserve\">\n    <value>상태</value>\n  </data>\n  <data name=\"OverlayForm_CardInfo_Swapped\" xml:space=\"preserve\">\n    <value>서로바뀜</value>\n  </data>\n  <data name=\"OverlayForm_DeckInfo_Mismatch\" xml:space=\"preserve\">\n    <value>파란 덱이 설정 페이지와 다릅니다! 추측해보려면 몇몇 게임을 관찰할 필요가 있습니다.</value>\n  </data>\n  <data name=\"OverlayForm_Details_Npc\" xml:space=\"preserve\">\n    <value>NPC: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedDeck\" xml:space=\"preserve\">\n    <value>빨강 덱 자세히 보기:</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedInfo\" xml:space=\"preserve\">\n    <value>&lt; 가능한 상대방의 카드입니다. 아래에 표시되는 카드는 포함되지 않습니다.</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedPlacedAll\" xml:space=\"preserve\">\n    <value>모두 배치됨: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedPlacedVariable\" xml:space=\"preserve\">\n    <value>휴대 가능: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_Rules\" xml:space=\"preserve\">\n    <value>규칙: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_ScanId\" xml:space=\"preserve\">\n    <value>스캔 ID: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_NpcUnknown\" xml:space=\"preserve\">\n    <value>알 수 없음</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_RulesWaiting\" xml:space=\"preserve\">\n    <value>NPC 변경됨, 스캔을 기다립니다...</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_ActiveTurn\" xml:space=\"preserve\">\n    <value>준비완료 (현재 턴!)</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_AutoScanMouseOverBoard\" xml:space=\"preserve\">\n    <value>스캔 구역에 마우스가 있습니다. 비켜주세요.</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_CactpotReady\" xml:space=\"preserve\">\n    <value>미니 복권: 준비됨</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_FailedCardMatching\" xml:space=\"preserve\">\n    <value>알 수 없는 카드! 플레이 확인: 스크린샷으로 세부사항</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingCards\" xml:space=\"preserve\">\n    <value>파랑 덱을 찾을 수 없습니다</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingGameProcess\" xml:space=\"preserve\">\n    <value>게임을 찾을 수 없습니다</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingGameWindow\" xml:space=\"preserve\">\n    <value>게임 윈도우를 찾을 수 없습니다</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingGrid\" xml:space=\"preserve\">\n    <value>보드를 찾을 수 없습니다</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_NoInputImage\" xml:space=\"preserve\">\n    <value>분석할 이미지를 불러올 수 없습니다</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_NoScannerMatch\" xml:space=\"preserve\">\n    <value>미니게임 윈도우를 찾을 수 없습니다</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_Ready\" xml:space=\"preserve\">\n    <value>준비 완료</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_ScannerErrors\" xml:space=\"preserve\">\n    <value>미니게임을 인식할 수 없습니다</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_UnknownHash\" xml:space=\"preserve\">\n    <value>알 수 없는 패턴! 플레이 확인: 스크린샷으로 세부사항</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_WaitingForTurn\" xml:space=\"preserve\">\n    <value>파랑 턴을 기다립니다</value>\n  </data>\n  <data name=\"Settings_AlwaysOnTop\" xml:space=\"preserve\">\n    <value>언제나 맨 위</value>\n  </data>\n  <data name=\"Settings_AlwaysSmallIcons\" xml:space=\"preserve\">\n    <value>항상 작은 아이콘 사용</value>\n  </data>\n  <data name=\"Settings_DisableHardwareAcceleration\" xml:space=\"preserve\">\n    <value>하드웨어 가속 비활성화</value>\n  </data>\n  <data name=\"Settings_FontSize\" xml:space=\"preserve\">\n    <value>글꼴 크기</value>\n  </data>\n  <data name=\"Settings_MarkerDurationCactpot\" xml:space=\"preserve\">\n    <value>마커 길이: 복권</value>\n  </data>\n  <data name=\"Settings_MarkerDurationCard\" xml:space=\"preserve\">\n    <value>마커 길이: 카드</value>\n  </data>\n  <data name=\"Settings_MarkerDurationSwap\" xml:space=\"preserve\">\n    <value>마거 길이: 서로바꿈</value>\n  </data>\n  <data name=\"Settings_SkipOptionalSimulateRules\" xml:space=\"preserve\">\n    <value>시뮬레이션에서 옵션 규칙 건너뛰기</value>\n  </data>\n  <data name=\"Settings_Title\" xml:space=\"preserve\">\n    <value>설정</value>\n  </data>\n</root>"
  },
  {
    "path": "sources/loc/strings.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <data name=\"AdjustForm_CancelButton\" xml:space=\"preserve\">\n    <value>Cancel</value>\n  </data>\n  <data name=\"AdjustForm_CardDown\" xml:space=\"preserve\">\n    <value>D:</value>\n  </data>\n  <data name=\"AdjustForm_CardLeft\" xml:space=\"preserve\">\n    <value>L:</value>\n  </data>\n  <data name=\"AdjustForm_CardList\" xml:space=\"preserve\">\n    <value>Adjusted:</value>\n  </data>\n  <data name=\"AdjustForm_CardRight\" xml:space=\"preserve\">\n    <value>R:</value>\n  </data>\n  <data name=\"AdjustForm_CardStatus\" xml:space=\"preserve\">\n    <value>Status:</value>\n  </data>\n  <data name=\"AdjustForm_CardUp\" xml:space=\"preserve\">\n    <value>U:</value>\n  </data>\n  <data name=\"AdjustForm_Current\" xml:space=\"preserve\">\n    <value>Current:</value>\n  </data>\n  <data name=\"AdjustForm_Distance\" xml:space=\"preserve\">\n    <value>Distance:</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_Hidden\" xml:space=\"preserve\">\n    <value>Hidden</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_Locked\" xml:space=\"preserve\">\n    <value>Locked</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_None\" xml:space=\"preserve\">\n    <value>None</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_PlacedBlue\" xml:space=\"preserve\">\n    <value>Placed: Blue</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_PlacedRed\" xml:space=\"preserve\">\n    <value>Placed: Red</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_Visible\" xml:space=\"preserve\">\n    <value>Visible</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Digit_Default\" xml:space=\"preserve\">\n    <value>auto</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Digit_Override\" xml:space=\"preserve\">\n    <value>custom</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_Classifier\" xml:space=\"preserve\">\n    <value>&lt;= Classifier based</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_DefaultHint\" xml:space=\"preserve\">\n    <value>&lt;= Lower is more accurate</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_Exact\" xml:space=\"preserve\">\n    <value>&lt;= Exact match</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_NotAvail\" xml:space=\"preserve\">\n    <value>N/A</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_UnknownOwner\" xml:space=\"preserve\">\n    <value>unknown</value>\n  </data>\n  <data name=\"AdjustForm_HashList\" xml:space=\"preserve\">\n    <value>Detect:</value>\n  </data>\n  <data name=\"AdjustForm_SaveButton\" xml:space=\"preserve\">\n    <value>Save</value>\n  </data>\n  <data name=\"AdjustForm_Title\" xml:space=\"preserve\">\n    <value>Adjust detection</value>\n  </data>\n  <data name=\"App_Title\" xml:space=\"preserve\">\n    <value>FF Triad Buddy</value>\n  </data>\n  <data name=\"DeckCtrl_CtxMenu_LockForOptimization\" xml:space=\"preserve\">\n    <value>Lock card for deck optimization</value>\n  </data>\n  <data name=\"DeckCtrl_CtxMenu_PickCard\" xml:space=\"preserve\">\n    <value>Pick card:</value>\n  </data>\n  <data name=\"DeckCtrl_CtxMenu_UseOnlyOwned\" xml:space=\"preserve\">\n    <value>Use only owned cards (click to change)</value>\n  </data>\n  <data name=\"FavDeckCtrl_Edit\" xml:space=\"preserve\">\n    <value>Edit</value>\n  </data>\n  <data name=\"FavDeckForm_AddButton\" xml:space=\"preserve\">\n    <value>Add</value>\n  </data>\n  <data name=\"FavDeckForm_Dynamic_AutoName\" xml:space=\"preserve\">\n    <value>Fav #{0}</value>\n  </data>\n  <data name=\"FavDeckForm_Dynamic_RemoveMsg\" xml:space=\"preserve\">\n    <value>Favorite deck will be removed, do you want to continue?</value>\n  </data>\n  <data name=\"FavDeckForm_Dynamic_UpdateButton\" xml:space=\"preserve\">\n    <value>Update</value>\n  </data>\n  <data name=\"FavDeckForm_Info\" xml:space=\"preserve\">\n    <value>Favorite deck, click card for options:</value>\n  </data>\n  <data name=\"FavDeckForm_Name\" xml:space=\"preserve\">\n    <value>Short name:</value>\n  </data>\n  <data name=\"FavDeckForm_RemoveButton\" xml:space=\"preserve\">\n    <value>Remove</value>\n  </data>\n  <data name=\"FavDeckForm_Title\" xml:space=\"preserve\">\n    <value>Edit favorite deck</value>\n  </data>\n  <data name=\"Game_ProcessName_DX11\" xml:space=\"preserve\">\n    <value>ffxiv_dx11</value>\n  </data>\n  <data name=\"Game_ProcessName_DX9\" xml:space=\"preserve\">\n    <value>ffxiv</value>\n  </data>\n  <data name=\"Game_WindowNamePrefix\" xml:space=\"preserve\">\n    <value>FINAL FANTASY</value>\n  </data>\n  <data name=\"LocalSaves_Export\" xml:space=\"preserve\">\n    <value>Export settings</value>\n  </data>\n  <data name=\"LocalSaves_Import\" xml:space=\"preserve\">\n    <value>Import settings</value>\n  </data>\n  <data name=\"LocalSaves_ShowBackupFolder\" xml:space=\"preserve\">\n    <value>Show backups</value>\n  </data>\n  <data name=\"LocalSaves_Title\" xml:space=\"preserve\">\n    <value>Local saves</value>\n  </data>\n  <data name=\"MainForm_Cards_IconsTitle\" xml:space=\"preserve\">\n    <value>Icons</value>\n  </data>\n  <data name=\"MainForm_Cards_ListTitle\" xml:space=\"preserve\">\n    <value>List</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnId\" xml:space=\"preserve\">\n    <value>#</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnName\" xml:space=\"preserve\">\n    <value>Name</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnOwned\" xml:space=\"preserve\">\n    <value>Owned</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnPower\" xml:space=\"preserve\">\n    <value>Power</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnRarity\" xml:space=\"preserve\">\n    <value>Rarity</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnType\" xml:space=\"preserve\">\n    <value>Type</value>\n  </data>\n  <data name=\"MainForm_Cards_NumOwned\" xml:space=\"preserve\">\n    <value>Number of owned cards:</value>\n  </data>\n  <data name=\"MainForm_Cards_Title\" xml:space=\"preserve\">\n    <value>Cards</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_CardInfo_FindOnline\" xml:space=\"preserve\">\n    <value>Find online</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_CardInfo_NpcReward\" xml:space=\"preserve\">\n    <value>Npc reward:</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_FindCard\" xml:space=\"preserve\">\n    <value>Find card:</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_FindNpc\" xml:space=\"preserve\">\n    <value>Find npc:</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_Learn_Adjust\" xml:space=\"preserve\">\n    <value>Adjust...</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_Learn_Delete\" xml:space=\"preserve\">\n    <value>Delete</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_SelectNpc_Rewards\" xml:space=\"preserve\">\n    <value>Rewards (click to add/remove)</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_SelectNpc_Select\" xml:space=\"preserve\">\n    <value>Select npc to play</value>\n  </data>\n  <data name=\"MainForm_Dynamic_CardOwnedColumn\" xml:space=\"preserve\">\n    <value>Yes</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_HasDuplicates\" xml:space=\"preserve\">\n    <value>Found duplicate cards!</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_MissingCards\" xml:space=\"preserve\">\n    <value>Missing cards!</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_TooMany4Star\" xml:space=\"preserve\">\n    <value>More than two 4+ star!</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_TooMany5Star\" xml:space=\"preserve\">\n    <value>Only one 5 star allowed!</value>\n  </data>\n  <data name=\"MainForm_Dynamic_NpcCompletedColumn\" xml:space=\"preserve\">\n    <value>Yes</value>\n  </data>\n  <data name=\"MainForm_Dynamic_RuleListEmpty\" xml:space=\"preserve\">\n    <value>(none)</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenhot_Status_NoErrors\" xml:space=\"preserve\">\n    <value>Active</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_BoardRow_Bottom\" xml:space=\"preserve\">\n    <value>bot</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_BoardRow_Middle\" xml:space=\"preserve\">\n    <value>mid</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_BoardRow_Top\" xml:space=\"preserve\">\n    <value>top</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardLocation_BlueDeck\" xml:space=\"preserve\">\n    <value>Blue {0}</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardLocation_Board\" xml:space=\"preserve\">\n    <value>Board {0} {1}</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardLocation_RedDeck\" xml:space=\"preserve\">\n    <value>Red {0}</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardNotDetected\" xml:space=\"preserve\">\n    <value>not detected!</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_HashType_Card\" xml:space=\"preserve\">\n    <value>Card</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_HashType_Number\" xml:space=\"preserve\">\n    <value>Number</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_HashType_Rule\" xml:space=\"preserve\">\n    <value>Rule</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_MatchType_Auto\" xml:space=\"preserve\">\n    <value>Auto</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_MatchType_Exact\" xml:space=\"preserve\">\n    <value>Exact</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_MatchType_Similar\" xml:space=\"preserve\">\n    <value>Similar</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_SelectDetectionMatch\" xml:space=\"preserve\">\n    <value>(select match)</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_Disabled\" xml:space=\"preserve\">\n    <value>Disabled</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_FailedCardMatching\" xml:space=\"preserve\">\n    <value>Failed to recognize some of cards! Game state won't be accurate</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_MissingGridOrCards\" xml:space=\"preserve\">\n    <value>Can't find board! Try resetting UI position, turning off reshade, etc...</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_UnknownHash\" xml:space=\"preserve\">\n    <value>Can't recognize pattern! See details below</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudSave_Loaded\" xml:space=\"preserve\">\n    <value>Cloud save applied</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_ApiFailure\" xml:space=\"preserve\">\n    <value>API call failed</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_AuthInProgress\" xml:space=\"preserve\">\n    <value>authorizing...</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_Disabled\" xml:space=\"preserve\">\n    <value>disabled, local only</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NoDatabase\" xml:space=\"preserve\">\n    <value>database failure</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NoErrors\" xml:space=\"preserve\">\n    <value>active</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NotAuthorized\" xml:space=\"preserve\">\n    <value>Auth required</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NotInitialized\" xml:space=\"preserve\">\n    <value>scanning...</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_Synced\" xml:space=\"preserve\">\n    <value>synchronized</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_Uploaded\" xml:space=\"preserve\">\n    <value>stored</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_OptimizerProgressAborted\" xml:space=\"preserve\">\n    <value>aborted at {0:P0}</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_BlueStartButton\" xml:space=\"preserve\">\n    <value>Blue starts</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_ChanceIsDraw\" xml:space=\"preserve\">\n    <value>(DRAW)</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_ChangeBlueHint\" xml:space=\"preserve\">\n    <value>Click to change card</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_EndGame_BlueDraw\" xml:space=\"preserve\">\n    <value>draw</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_EndGame_BlueLost\" xml:space=\"preserve\">\n    <value>lost</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_EndGame_BlueWin\" xml:space=\"preserve\">\n    <value>yay!</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_LastCardHint\" xml:space=\"preserve\">\n    <value>[Place last red card to trigger modifier]</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_RedStartButton\" xml:space=\"preserve\">\n    <value>Red start: drag &amp; drop card on board</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_SwapRuleButton\" xml:space=\"preserve\">\n    <value>Select one card from each deck to swap</value>\n  </data>\n  <data name=\"MainForm_Info_BugReports\" xml:space=\"preserve\">\n    <value>Bug reports:</value>\n  </data>\n  <data name=\"MainForm_Info_HomePage\" xml:space=\"preserve\">\n    <value>Project home page:</value>\n  </data>\n  <data name=\"MainForm_Info_Localization\" xml:space=\"preserve\">\n    <value>UI localization:</value>\n  </data>\n  <data name=\"MainForm_Info_Title\" xml:space=\"preserve\">\n    <value>Info &amp; Settings</value>\n  </data>\n  <data name=\"MainForm_Info_TranslatorLove\" xml:space=\"preserve\">\n    <value>Thanks to everyone who contributed their translations! You're awesome!</value>\n  </data>\n  <data name=\"MainForm_Info_TranslatorNeeded\" xml:space=\"preserve\">\n    <value>( volunteers still needed ;) )</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnCompleted\" xml:space=\"preserve\">\n    <value>Completed</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnLocation\" xml:space=\"preserve\">\n    <value>Location</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnName\" xml:space=\"preserve\">\n    <value>Name</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnPower\" xml:space=\"preserve\">\n    <value>Power</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnReward\" xml:space=\"preserve\">\n    <value>Reward</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnRules\" xml:space=\"preserve\">\n    <value>Rules</value>\n  </data>\n  <data name=\"MainForm_Npcs_NumKnown\" xml:space=\"preserve\">\n    <value>Number of npcs with cards:</value>\n  </data>\n  <data name=\"MainForm_Npcs_Title\" xml:space=\"preserve\">\n    <value>Npcs</value>\n  </data>\n  <data name=\"MainForm_Screenshot_CurrentState\" xml:space=\"preserve\">\n    <value>Current state:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_CardsColumnDetection\" xml:space=\"preserve\">\n    <value>Last detection</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_CardsColumnSides\" xml:space=\"preserve\">\n    <value>Numbers [U-L-D-R]</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_CardsColumnType\" xml:space=\"preserve\">\n    <value>Type</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_HashColumnDetection\" xml:space=\"preserve\">\n    <value>Last detection</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_HashColumnType\" xml:space=\"preserve\">\n    <value>Type</value>\n  </data>\n  <data name=\"MainForm_Screenshot_InfoLines\" xml:space=\"preserve\">\n    <value>Rules:\n- game must be in Windowed or Borderless Windowed mode\n- UI: set scale to 100%, use dark scheme\n- npc must be selected\n- capture during Blue turn, after all card animations finished (!!!)\n\nGamepad: press both trigger buttons to capture\n\nWorks with Mini Cactpot too :D</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_DetectList\" xml:space=\"preserve\">\n    <value>Detect:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_DiscardAllButton\" xml:space=\"preserve\">\n    <value>Discard All</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_DiscardAllInfo\" xml:space=\"preserve\">\n    <value>&lt;&lt; Resets scanner state</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_PendingPlural\" xml:space=\"preserve\">\n    <value>(there are {0} more pending)</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_PendingSingular\" xml:space=\"preserve\">\n    <value>(there is 1 more pending)</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_SaveButton\" xml:space=\"preserve\">\n    <value>Save</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_SourceImage\" xml:space=\"preserve\">\n    <value>Source image:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_Type\" xml:space=\"preserve\">\n    <value>Type:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_ListHint\" xml:space=\"preserve\">\n    <value>^ Right click on lists for options</value>\n  </data>\n  <data name=\"MainForm_Screenshot_RemovePatternsButton\" xml:space=\"preserve\">\n    <value>Remove local patterns</value>\n  </data>\n  <data name=\"MainForm_Screenshot_RemovePatternsTitle\" xml:space=\"preserve\">\n    <value>Use to clear all custom patterns and start from scratch:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Title\" xml:space=\"preserve\">\n    <value>Play: Screenshot</value>\n  </data>\n  <data name=\"MainForm_Setup_Cloud_AuthButton\" xml:space=\"preserve\">\n    <value>Login:</value>\n  </data>\n  <data name=\"MainForm_Setup_Cloud_Desc\" xml:space=\"preserve\">\n    <value>Save in Google:</value>\n  </data>\n  <data name=\"MainForm_Setup_Deck_OptimizeAbortButton\" xml:space=\"preserve\">\n    <value>Abort</value>\n  </data>\n  <data name=\"MainForm_Setup_Deck_OptimizeStartButton\" xml:space=\"preserve\">\n    <value>Optimize deck for NPC</value>\n  </data>\n  <data name=\"MainForm_Setup_Deck_Title\" xml:space=\"preserve\">\n    <value>Active deck, click card for options:</value>\n  </data>\n  <data name=\"MainForm_Setup_Fav_AddSlotButton\" xml:space=\"preserve\">\n    <value>Add favorite deck slot</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC\" xml:space=\"preserve\">\n    <value>NPC:</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_DeckPower\" xml:space=\"preserve\">\n    <value>Deck power:</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_Location\" xml:space=\"preserve\">\n    <value>Location:</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_Rules\" xml:space=\"preserve\">\n    <value>Rules:</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_WinChance\" xml:space=\"preserve\">\n    <value>Win chance:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_NumOwned\" xml:space=\"preserve\">\n    <value>Num owned cards:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_NumPossible\" xml:space=\"preserve\">\n    <value>Num possible decks:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_NumTested\" xml:space=\"preserve\">\n    <value>Num tested decks:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_Progress\" xml:space=\"preserve\">\n    <value>Progress:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_TimeLeft\" xml:space=\"preserve\">\n    <value>Time left:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizeStats_Title\" xml:space=\"preserve\">\n    <value>Optimization stats:</value>\n  </data>\n  <data name=\"MainForm_Setup_RulesToggle\" xml:space=\"preserve\">\n    <value>Toggle npc rules, disable for tournament</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_Region1\" xml:space=\"preserve\">\n    <value>Region rule 1:</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_Region2\" xml:space=\"preserve\">\n    <value>Region rule 2:</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_Tournament\" xml:space=\"preserve\">\n    <value>Tournament:</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_TournamentRules\" xml:space=\"preserve\">\n    <value>Rules:</value>\n  </data>\n  <data name=\"MainForm_Setup_Title\" xml:space=\"preserve\">\n    <value>Setup</value>\n  </data>\n  <data name=\"MainForm_Simulate_Debug_ForceCached\" xml:space=\"preserve\">\n    <value>Force using cached image</value>\n  </data>\n  <data name=\"MainForm_Simulate_Debug_Info\" xml:space=\"preserve\">\n    <value>Take game screenshot: DEBUG mode</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_ApplyRuleButton\" xml:space=\"preserve\">\n    <value>Apply rule</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_KnownCards\" xml:space=\"preserve\">\n    <value>Known cards:</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_ListHint\" xml:space=\"preserve\">\n    <value>[Drag &amp; drop cards on board]</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_SkipRuleButton\" xml:space=\"preserve\">\n    <value>Skip</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_SpecialRule\" xml:space=\"preserve\">\n    <value>Special Rule &gt;&gt;</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_UnknownCards\" xml:space=\"preserve\">\n    <value>Unknown cards remaining: {0}</value>\n  </data>\n  <data name=\"MainForm_Simulate_Open_Hint\" xml:space=\"preserve\">\n    <value>Select visible cards:</value>\n  </data>\n  <data name=\"MainForm_Simulate_Random_Info\" xml:space=\"preserve\">\n    <value>Adjust randomized deck :(</value>\n  </data>\n  <data name=\"MainForm_Simulate_ResetButton\" xml:space=\"preserve\">\n    <value>Reset</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule1\" xml:space=\"preserve\">\n    <value>Pick roulette rule #1</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule2\" xml:space=\"preserve\">\n    <value>Pick roulette rule #2</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule3\" xml:space=\"preserve\">\n    <value>Pick roulette rule #3</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule4\" xml:space=\"preserve\">\n    <value>Pick roulette rule #4</value>\n  </data>\n  <data name=\"MainForm_Simulate_RuleList\" xml:space=\"preserve\">\n    <value>Current rules:</value>\n  </data>\n  <data name=\"MainForm_Simulate_Title\" xml:space=\"preserve\">\n    <value>Play: Simulate</value>\n  </data>\n  <data name=\"MainForm_Simulate_UndoRedMoveButton\" xml:space=\"preserve\">\n    <value>Undo last Red move</value>\n  </data>\n  <data name=\"MainForm_Simulate_WinChance\" xml:space=\"preserve\">\n    <value>Win chance:</value>\n  </data>\n  <data name=\"MainForm_UpdateNotify\" xml:space=\"preserve\">\n    <value>New version downloaded, please restart program to finish update. Click here to hide.</value>\n  </data>\n  <data name=\"OverlayForm_Capture_AutoScan\" xml:space=\"preserve\">\n    <value>Auto scan</value>\n  </data>\n  <data name=\"OverlayForm_Capture_Button\" xml:space=\"preserve\">\n    <value>Capture</value>\n  </data>\n  <data name=\"OverlayForm_Capture_Details\" xml:space=\"preserve\">\n    <value>Show details</value>\n  </data>\n  <data name=\"OverlayForm_Capture_Status\" xml:space=\"preserve\">\n    <value>Status</value>\n  </data>\n  <data name=\"OverlayForm_CardInfo_Swapped\" xml:space=\"preserve\">\n    <value>SWAPPED</value>\n  </data>\n  <data name=\"OverlayForm_DeckInfo_Mismatch\" xml:space=\"preserve\">\n    <value>Blue deck is not matching Setup page! Solver needs to observe a few games to guess correct one.</value>\n  </data>\n  <data name=\"OverlayForm_Details_Npc\" xml:space=\"preserve\">\n    <value>NPC: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedDeck\" xml:space=\"preserve\">\n    <value>Red deck details:</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedInfo\" xml:space=\"preserve\">\n    <value>&lt; Potential red cards, not including visible ones below:</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedPlacedAll\" xml:space=\"preserve\">\n    <value>All placed: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedPlacedVariable\" xml:space=\"preserve\">\n    <value>Var placed: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_Rules\" xml:space=\"preserve\">\n    <value>Rules: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_ScanId\" xml:space=\"preserve\">\n    <value>Scan Id: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_NpcUnknown\" xml:space=\"preserve\">\n    <value>unknown</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_RulesWaiting\" xml:space=\"preserve\">\n    <value>npc changed, waiting for scan...</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_ActiveTurn\" xml:space=\"preserve\">\n    <value>Ready (active turn!)</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_AutoScanMouseOverBoard\" xml:space=\"preserve\">\n    <value>Move cursor away from scan zone!</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_CactpotReady\" xml:space=\"preserve\">\n    <value>Mini cactpot: Ready</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_FailedCardMatching\" xml:space=\"preserve\">\n    <value>Unknown cards! Check Play:Screenshot for details</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingCards\" xml:space=\"preserve\">\n    <value>Can't find blue deck</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingGameProcess\" xml:space=\"preserve\">\n    <value>Game is not running</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingGameWindow\" xml:space=\"preserve\">\n    <value>Can't find game window</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingGrid\" xml:space=\"preserve\">\n    <value>Can't find board</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_NoInputImage\" xml:space=\"preserve\">\n    <value>Can't retrieve image to analyze</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_NoScannerMatch\" xml:space=\"preserve\">\n    <value>Can't find minigame window</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_Ready\" xml:space=\"preserve\">\n    <value>Ready</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_ScannerErrors\" xml:space=\"preserve\">\n    <value>Failed to recognize minigame</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_UnknownHash\" xml:space=\"preserve\">\n    <value>Unknown pattern! Check Play:Screenshot for details</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_WaitingForTurn\" xml:space=\"preserve\">\n    <value>Waiting for blue turn</value>\n  </data>\n  <data name=\"Settings_AlwaysOnTop\" xml:space=\"preserve\">\n    <value>Always on top</value>\n  </data>\n  <data name=\"Settings_AlwaysSmallIcons\" xml:space=\"preserve\">\n    <value>Always use small icons</value>\n  </data>\n  <data name=\"Settings_DisableHardwareAcceleration\" xml:space=\"preserve\">\n    <value>Disable hardware acceleration</value>\n  </data>\n  <data name=\"Settings_FontSize\" xml:space=\"preserve\">\n    <value>Font size</value>\n  </data>\n  <data name=\"Settings_MarkerDurationCactpot\" xml:space=\"preserve\">\n    <value>Marker duration: cactpot</value>\n  </data>\n  <data name=\"Settings_MarkerDurationCard\" xml:space=\"preserve\">\n    <value>Marker duration: card</value>\n  </data>\n  <data name=\"Settings_MarkerDurationSwap\" xml:space=\"preserve\">\n    <value>Marker duration: swap</value>\n  </data>\n  <data name=\"Settings_SkipOptionalSimulateRules\" xml:space=\"preserve\">\n    <value>Skip optional rules in simulate</value>\n  </data>\n  <data name=\"Settings_Title\" xml:space=\"preserve\">\n    <value>Settings</value>\n  </data>\n</root>"
  },
  {
    "path": "sources/loc/strings.zh.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\" id=\"root\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\"/>\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\"/>\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\"/>\n              <xsd:attribute name=\"type\" type=\"xsd:string\"/>\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\"/>\n              <xsd:attribute ref=\"xml:space\"/>\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\"/>\n              <xsd:attribute name=\"name\" type=\"xsd:string\"/>\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\"/>\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\"/>\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\"/>\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\"/>\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\"/>\n              <xsd:attribute ref=\"xml:space\"/>\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\"/>\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\"/>\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <data name=\"AdjustForm_CancelButton\" xml:space=\"preserve\">\n    <value>取消</value>\n  </data>\n  <data name=\"AdjustForm_CardDown\" xml:space=\"preserve\">\n    <value>下:</value>\n  </data>\n  <data name=\"AdjustForm_CardLeft\" xml:space=\"preserve\">\n    <value>左:</value>\n  </data>\n  <data name=\"AdjustForm_CardList\" xml:space=\"preserve\">\n    <value>调整后:</value>\n  </data>\n  <data name=\"AdjustForm_CardRight\" xml:space=\"preserve\">\n    <value>右:</value>\n  </data>\n  <data name=\"AdjustForm_CardStatus\" xml:space=\"preserve\">\n    <value>状态:</value>\n  </data>\n  <data name=\"AdjustForm_CardUp\" xml:space=\"preserve\">\n    <value>上:</value>\n  </data>\n  <data name=\"AdjustForm_Current\" xml:space=\"preserve\">\n    <value>当前:</value>\n  </data>\n  <data name=\"AdjustForm_Distance\" xml:space=\"preserve\">\n    <value>差距:</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_Hidden\" xml:space=\"preserve\">\n    <value>已隐藏</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_Locked\" xml:space=\"preserve\">\n    <value>已锁定</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_None\" xml:space=\"preserve\">\n    <value>无</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_PlacedBlue\" xml:space=\"preserve\">\n    <value>已放置：蓝色</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_PlacedRed\" xml:space=\"preserve\">\n    <value>已放置：红色</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_CardState_Visible\" xml:space=\"preserve\">\n    <value>可见</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Digit_Default\" xml:space=\"preserve\">\n    <value>自动</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Digit_Override\" xml:space=\"preserve\">\n    <value>自定义</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_Classifier\" xml:space=\"preserve\">\n    <value>&lt;= 基于分类器</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_DefaultHint\" xml:space=\"preserve\">\n    <value>&lt;= 越低越准确</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_Exact\" xml:space=\"preserve\">\n    <value>&lt;= 精确匹配</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_Distance_NotAvail\" xml:space=\"preserve\">\n    <value>N/A</value>\n  </data>\n  <data name=\"AdjustForm_Dynamic_UnknownOwner\" xml:space=\"preserve\">\n    <value>未知</value>\n  </data>\n  <data name=\"AdjustForm_HashList\" xml:space=\"preserve\">\n    <value>检测:</value>\n  </data>\n  <data name=\"AdjustForm_SaveButton\" xml:space=\"preserve\">\n    <value>保存</value>\n  </data>\n  <data name=\"AdjustForm_Title\" xml:space=\"preserve\">\n    <value>检测调整</value>\n  </data>\n  <data name=\"App_Title\" xml:space=\"preserve\">\n    <value>FF幻卡助手</value>\n  </data>\n  <data name=\"DeckCtrl_CtxMenu_LockForOptimization\" xml:space=\"preserve\">\n    <value>优化卡组时锁定此幻卡</value>\n  </data>\n  <data name=\"DeckCtrl_CtxMenu_PickCard\" xml:space=\"preserve\">\n    <value>选择幻卡:</value>\n  </data>\n  <data name=\"DeckCtrl_CtxMenu_UseOnlyOwned\" xml:space=\"preserve\">\n    <value>仅使用已拥有的幻卡</value>\n  </data>\n  <data name=\"FavDeckCtrl_Edit\" xml:space=\"preserve\">\n    <value>编辑</value>\n  </data>\n  <data name=\"FavDeckForm_AddButton\" xml:space=\"preserve\">\n    <value>添加</value>\n  </data>\n  <data name=\"FavDeckForm_Dynamic_AutoName\" xml:space=\"preserve\">\n    <value>收藏 #{0}</value>\n  </data>\n  <data name=\"FavDeckForm_Dynamic_RemoveMsg\" xml:space=\"preserve\">\n    <value>卡组收藏将被移除，是否继续？</value>\n  </data>\n  <data name=\"FavDeckForm_Dynamic_UpdateButton\" xml:space=\"preserve\">\n    <value>更新</value>\n  </data>\n  <data name=\"FavDeckForm_Info\" xml:space=\"preserve\">\n    <value>卡组收藏，点击下方幻卡查看选项:</value>\n  </data>\n  <data name=\"FavDeckForm_Name\" xml:space=\"preserve\">\n    <value>卡组名称:</value>\n  </data>\n  <data name=\"FavDeckForm_RemoveButton\" xml:space=\"preserve\">\n    <value>移除</value>\n  </data>\n  <data name=\"FavDeckForm_Title\" xml:space=\"preserve\">\n    <value>编辑卡组收藏</value>\n  </data>\n  <data name=\"Game_ProcessName_DX11\" xml:space=\"preserve\">\n    <value>ffxiv_dx11</value>\n  </data>\n  <data name=\"Game_ProcessName_DX9\" xml:space=\"preserve\">\n    <value>ffxiv</value>\n  </data>\n  <data name=\"Game_WindowNamePrefix\" xml:space=\"preserve\">\n    <value>最终幻想</value>\n  </data>\n  <data name=\"LocalSaves_Export\" xml:space=\"preserve\">\n    <value>导出设置</value>\n  </data>\n  <data name=\"LocalSaves_Import\" xml:space=\"preserve\">\n    <value>导入设置</value>\n  </data>\n  <data name=\"LocalSaves_ShowBackupFolder\" xml:space=\"preserve\">\n    <value>显示备份</value>\n  </data>\n  <data name=\"LocalSaves_Title\" xml:space=\"preserve\">\n    <value>本地存档</value>\n  </data>\n  <data name=\"MainForm_Cards_IconsTitle\" xml:space=\"preserve\">\n    <value>图标视图</value>\n  </data>\n  <data name=\"MainForm_Cards_ListTitle\" xml:space=\"preserve\">\n    <value>列表视图</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnId\" xml:space=\"preserve\">\n    <value>编号</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnName\" xml:space=\"preserve\">\n    <value>名称</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnOwned\" xml:space=\"preserve\">\n    <value>已拥有</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnPower\" xml:space=\"preserve\">\n    <value>数值</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnRarity\" xml:space=\"preserve\">\n    <value>稀有度</value>\n  </data>\n  <data name=\"MainForm_Cards_List_ColumnType\" xml:space=\"preserve\">\n    <value>类型</value>\n  </data>\n  <data name=\"MainForm_Cards_NumOwned\" xml:space=\"preserve\">\n    <value>已拥有幻卡数:</value>\n  </data>\n  <data name=\"MainForm_Cards_Title\" xml:space=\"preserve\">\n    <value>幻卡</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_CardInfo_FindOnline\" xml:space=\"preserve\">\n    <value>在线检索</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_CardInfo_NpcReward\" xml:space=\"preserve\">\n    <value>NPC奖励:</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_FindCard\" xml:space=\"preserve\">\n    <value>查找幻卡:</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_FindNpc\" xml:space=\"preserve\">\n    <value>查找NPC:</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_Learn_Adjust\" xml:space=\"preserve\">\n    <value>调整...</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_Learn_Delete\" xml:space=\"preserve\">\n    <value>删除</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_SelectNpc_Rewards\" xml:space=\"preserve\">\n    <value>奖励（点击添加/移除）</value>\n  </data>\n  <data name=\"MainForm_CtxMenu_SelectNpc_Select\" xml:space=\"preserve\">\n    <value>选择此NPC进行对战</value>\n  </data>\n  <data name=\"MainForm_Dynamic_CardOwnedColumn\" xml:space=\"preserve\">\n    <value>是</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_HasDuplicates\" xml:space=\"preserve\">\n    <value>幻卡重复！</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_MissingCards\" xml:space=\"preserve\">\n    <value>卡片缺失！</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_TooMany4Star\" xml:space=\"preserve\">\n    <value>多余两张4星+幻卡！</value>\n  </data>\n  <data name=\"MainForm_Dynamic_DeckState_TooMany5Star\" xml:space=\"preserve\">\n    <value>只能有一张5星卡！</value>\n  </data>\n  <data name=\"MainForm_Dynamic_NpcCompletedColumn\" xml:space=\"preserve\">\n    <value>是</value>\n  </data>\n  <data name=\"MainForm_Dynamic_RuleListEmpty\" xml:space=\"preserve\">\n    <value>（无）</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenhot_Status_NoErrors\" xml:space=\"preserve\">\n    <value>激活</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_BoardRow_Bottom\" xml:space=\"preserve\">\n    <value>下</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_BoardRow_Middle\" xml:space=\"preserve\">\n    <value>中</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_BoardRow_Top\" xml:space=\"preserve\">\n    <value>上</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardLocation_BlueDeck\" xml:space=\"preserve\">\n    <value>蓝方 {0}</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardLocation_Board\" xml:space=\"preserve\">\n    <value>牌桌 {0} {1}</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardLocation_RedDeck\" xml:space=\"preserve\">\n    <value>红方 {0}</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_CardNotDetected\" xml:space=\"preserve\">\n    <value>未检测到！</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_HashType_Card\" xml:space=\"preserve\">\n    <value>幻卡</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_HashType_Number\" xml:space=\"preserve\">\n    <value>数值</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_HashType_Rule\" xml:space=\"preserve\">\n    <value>规则</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_MatchType_Auto\" xml:space=\"preserve\">\n    <value>自动</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_MatchType_Exact\" xml:space=\"preserve\">\n    <value>精确</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_MatchType_Similar\" xml:space=\"preserve\">\n    <value>近似</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_SelectDetectionMatch\" xml:space=\"preserve\">\n    <value>（选择匹配）</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_Disabled\" xml:space=\"preserve\">\n    <value>禁用</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_FailedCardMatching\" xml:space=\"preserve\">\n    <value>未能检测某些幻卡! 游戏状态将不准确</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_MissingGridOrCards\" xml:space=\"preserve\">\n    <value>无法找到牌桌！请尝试调整界面位置，关闭画面滤镜，等...</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Screenshot_Status_UnknownHash\" xml:space=\"preserve\">\n    <value>无法检测图案！请查看下方详情</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudSave_Loaded\" xml:space=\"preserve\">\n    <value>已启用云端保存</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_ApiFailure\" xml:space=\"preserve\">\n    <value>API调用失败</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_AuthInProgress\" xml:space=\"preserve\">\n    <value>授权中...</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_Disabled\" xml:space=\"preserve\">\n    <value>禁用，仅在本地使用</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NoDatabase\" xml:space=\"preserve\">\n    <value>数据库故障</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NoErrors\" xml:space=\"preserve\">\n    <value>激活</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NotAuthorized\" xml:space=\"preserve\">\n    <value>需要授权</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_NotInitialized\" xml:space=\"preserve\">\n    <value>扫描中...</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_Synced\" xml:space=\"preserve\">\n    <value>已同步</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_CloudStatus_Uploaded\" xml:space=\"preserve\">\n    <value>已保存</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Setup_OptimizerProgressAborted\" xml:space=\"preserve\">\n    <value>已在 {0:P0} 中断</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_BlueStartButton\" xml:space=\"preserve\">\n    <value>蓝方先手</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_ChanceIsDraw\" xml:space=\"preserve\">\n    <value>（平局）</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_ChangeBlueHint\" xml:space=\"preserve\">\n    <value>点击切换幻卡</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_EndGame_BlueDraw\" xml:space=\"preserve\">\n    <value>平局</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_EndGame_BlueLost\" xml:space=\"preserve\">\n    <value>输</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_EndGame_BlueWin\" xml:space=\"preserve\">\n    <value>耶！</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_LastCardHint\" xml:space=\"preserve\">\n    <value>[放置最后一张红卡以触发规则]</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_RedStartButton\" xml:space=\"preserve\">\n    <value>红方先手: 请将幻卡拖放到牌桌</value>\n  </data>\n  <data name=\"MainForm_Dynamic_Simulate_SwapRuleButton\" xml:space=\"preserve\">\n    <value>从双方卡组中各选一张幻卡进行交换</value>\n  </data>\n  <data name=\"MainForm_Info_BugReports\" xml:space=\"preserve\">\n    <value>问题反馈:</value>\n  </data>\n  <data name=\"MainForm_Info_HomePage\" xml:space=\"preserve\">\n    <value>项目主页:</value>\n  </data>\n  <data name=\"MainForm_Info_Localization\" xml:space=\"preserve\">\n    <value>界面本地化:</value>\n  </data>\n  <data name=\"MainForm_Info_Title\" xml:space=\"preserve\">\n    <value>信息与设定</value>\n  </data>\n  <data name=\"MainForm_Info_TranslatorLove\" xml:space=\"preserve\">\n    <value>感谢所有贡献翻译的人！你太棒了！</value>\n  </data>\n  <data name=\"MainForm_Info_TranslatorNeeded\" xml:space=\"preserve\">\n    <value>(仍需要翻译志愿者 ;) )</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnCompleted\" xml:space=\"preserve\">\n    <value>完成</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnLocation\" xml:space=\"preserve\">\n    <value>位置</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnName\" xml:space=\"preserve\">\n    <value>名称</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnPower\" xml:space=\"preserve\">\n    <value>强度</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnReward\" xml:space=\"preserve\">\n    <value>奖励</value>\n  </data>\n  <data name=\"MainForm_Npcs_List_ColumnRules\" xml:space=\"preserve\">\n    <value>规则</value>\n  </data>\n  <data name=\"MainForm_Npcs_NumKnown\" xml:space=\"preserve\">\n    <value>未完全击破的NPC数:</value>\n  </data>\n  <data name=\"MainForm_Npcs_Title\" xml:space=\"preserve\">\n    <value>NPC</value>\n  </data>\n  <data name=\"MainForm_Screenshot_CurrentState\" xml:space=\"preserve\">\n    <value>当前状态:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_CardsColumnDetection\" xml:space=\"preserve\">\n    <value>最后一次检测</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_CardsColumnSides\" xml:space=\"preserve\">\n    <value>数值 [上-左-下-右]</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_CardsColumnType\" xml:space=\"preserve\">\n    <value>类型</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_HashColumnDetection\" xml:space=\"preserve\">\n    <value>最后一次检测</value>\n  </data>\n  <data name=\"MainForm_Screenshot_History_HashColumnType\" xml:space=\"preserve\">\n    <value>类型</value>\n  </data>\n  <data name=\"MainForm_Screenshot_InfoLines\" xml:space=\"preserve\">\n    <value>说明:\n- 游戏必须处于普通窗口或窗口全屏模式\n- 界面: 将基本尺寸调整为100%, 使用深色主题\n- 必须选中NPC\n- 待所有幻卡动画结束后，在蓝方回合进行捕获（！！！）\n\n手柄:同时按下LT与RT按键进行捕获\n\n此模式也适用于仙人微彩 :D</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_DetectList\" xml:space=\"preserve\">\n    <value>检测:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_DiscardAllButton\" xml:space=\"preserve\">\n    <value>丢弃所有</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_DiscardAllInfo\" xml:space=\"preserve\">\n    <value>&lt;&lt; 重置扫描仪状态</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_PendingPlural\" xml:space=\"preserve\">\n    <value>（还有{0}个待处理）</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_PendingSingular\" xml:space=\"preserve\">\n    <value>（还有1个待处理）</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_SaveButton\" xml:space=\"preserve\">\n    <value>保存</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_SourceImage\" xml:space=\"preserve\">\n    <value>来源图像:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Learn_Type\" xml:space=\"preserve\">\n    <value>类型:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_ListHint\" xml:space=\"preserve\">\n    <value>^ 右键列表进行编辑</value>\n  </data>\n  <data name=\"MainForm_Screenshot_RemovePatternsButton\" xml:space=\"preserve\">\n    <value>移除本地图案</value>\n  </data>\n  <data name=\"MainForm_Screenshot_RemovePatternsTitle\" xml:space=\"preserve\">\n    <value>清除所有图案缓存以重置检测功能:</value>\n  </data>\n  <data name=\"MainForm_Screenshot_Title\" xml:space=\"preserve\">\n    <value>对局:捕获模式</value>\n  </data>\n  <data name=\"MainForm_Setup_Cloud_AuthButton\" xml:space=\"preserve\">\n    <value>登录:</value>\n  </data>\n  <data name=\"MainForm_Setup_Cloud_Desc\" xml:space=\"preserve\">\n    <value>保存在谷歌网盘:</value>\n  </data>\n  <data name=\"MainForm_Setup_Deck_OptimizeAbortButton\" xml:space=\"preserve\">\n    <value>中断</value>\n  </data>\n  <data name=\"MainForm_Setup_Deck_OptimizeStartButton\" xml:space=\"preserve\">\n    <value>为此NPC优化卡组</value>\n  </data>\n  <data name=\"MainForm_Setup_Deck_Title\" xml:space=\"preserve\">\n    <value>生效中卡组，点击下方幻卡查看选项</value>\n  </data>\n  <data name=\"MainForm_Setup_Fav_AddSlotButton\" xml:space=\"preserve\">\n    <value>添加收藏卡组</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC\" xml:space=\"preserve\">\n    <value>NPC:</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_DeckPower\" xml:space=\"preserve\">\n    <value>卡组强度:</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_Location\" xml:space=\"preserve\">\n    <value>位置:</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_Rules\" xml:space=\"preserve\">\n    <value>规则:</value>\n  </data>\n  <data name=\"MainForm_Setup_NPC_WinChance\" xml:space=\"preserve\">\n    <value>获胜概率:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_NumOwned\" xml:space=\"preserve\">\n    <value>已拥有幻卡数:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_NumPossible\" xml:space=\"preserve\">\n    <value>卡组可能性:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_NumTested\" xml:space=\"preserve\">\n    <value>已测试卡组:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_Progress\" xml:space=\"preserve\">\n    <value>进度:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizerStats_TimeLeft\" xml:space=\"preserve\">\n    <value>剩余时间:</value>\n  </data>\n  <data name=\"MainForm_Setup_OptimizeStats_Title\" xml:space=\"preserve\">\n    <value>优化详情:</value>\n  </data>\n  <data name=\"MainForm_Setup_RulesToggle\" xml:space=\"preserve\">\n    <value>切换NPC规则，取消勾选为大赛专用</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_Region1\" xml:space=\"preserve\">\n    <value>流行规则1:</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_Region2\" xml:space=\"preserve\">\n    <value>流行规则2:</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_Tournament\" xml:space=\"preserve\">\n    <value>大赛:</value>\n  </data>\n  <data name=\"MainForm_Setup_Rules_TournamentRules\" xml:space=\"preserve\">\n    <value>规则:</value>\n  </data>\n  <data name=\"MainForm_Setup_Title\" xml:space=\"preserve\">\n    <value>配置</value>\n  </data>\n  <data name=\"MainForm_Simulate_Debug_ForceCached\" xml:space=\"preserve\">\n    <value>强制使用缓存图像</value>\n  </data>\n  <data name=\"MainForm_Simulate_Debug_Info\" xml:space=\"preserve\">\n    <value>拍摄游戏截图:DEBUG模式</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_ApplyRuleButton\" xml:space=\"preserve\">\n    <value>应用规则</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_KnownCards\" xml:space=\"preserve\">\n    <value>已知幻卡:</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_ListHint\" xml:space=\"preserve\">\n    <value>[请将幻卡拖放到牌桌]</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_SkipRuleButton\" xml:space=\"preserve\">\n    <value>跳过</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_SpecialRule\" xml:space=\"preserve\">\n    <value>特殊规则 &gt;&gt;</value>\n  </data>\n  <data name=\"MainForm_Simulate_Game_UnknownCards\" xml:space=\"preserve\">\n    <value>剩余未知幻卡: {0}</value>\n  </data>\n  <data name=\"MainForm_Simulate_Open_Hint\" xml:space=\"preserve\">\n    <value>选择可见幻卡:</value>\n  </data>\n  <data name=\"MainForm_Simulate_Random_Info\" xml:space=\"preserve\">\n    <value>调整随机卡组 :(</value>\n  </data>\n  <data name=\"MainForm_Simulate_ResetButton\" xml:space=\"preserve\">\n    <value>重置</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule1\" xml:space=\"preserve\">\n    <value>选择天选规则 #1</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule2\" xml:space=\"preserve\">\n    <value>选择天选规则 #2</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule3\" xml:space=\"preserve\">\n    <value>选择天选规则 #3</value>\n  </data>\n  <data name=\"MainForm_Simulate_Roulette_Rule4\" xml:space=\"preserve\">\n    <value>选择天选规则 #4</value>\n  </data>\n  <data name=\"MainForm_Simulate_RuleList\" xml:space=\"preserve\">\n    <value>当前规则:</value>\n  </data>\n  <data name=\"MainForm_Simulate_Title\" xml:space=\"preserve\">\n    <value>对局:模拟模式</value>\n  </data>\n  <data name=\"MainForm_Simulate_UndoRedMoveButton\" xml:space=\"preserve\">\n    <value>撤销红方上一步</value>\n  </data>\n  <data name=\"MainForm_Simulate_WinChance\" xml:space=\"preserve\">\n    <value>获胜概率:</value>\n  </data>\n  <data name=\"MainForm_UpdateNotify\" xml:space=\"preserve\">\n    <value>新版本已下载完成，请重新启动程序以完成更新。点击此处隐藏。</value>\n  </data>\n  <data name=\"OverlayForm_Capture_AutoScan\" xml:space=\"preserve\">\n    <value>自动扫描</value>\n  </data>\n  <data name=\"OverlayForm_Capture_Button\" xml:space=\"preserve\">\n    <value>捕获</value>\n  </data>\n  <data name=\"OverlayForm_Capture_Details\" xml:space=\"preserve\">\n    <value>展开详情</value>\n  </data>\n  <data name=\"OverlayForm_Capture_Status\" xml:space=\"preserve\">\n    <value>状态</value>\n  </data>\n  <data name=\"OverlayForm_CardInfo_Swapped\" xml:space=\"preserve\">\n    <value>已交换</value>\n  </data>\n  <data name=\"OverlayForm_DeckInfo_Mismatch\" xml:space=\"preserve\">\n    <value>蓝方卡组与设置页不匹配！求解器需要观察几局才能猜出正确的幻卡。</value>\n  </data>\n  <data name=\"OverlayForm_Details_Npc\" xml:space=\"preserve\">\n    <value>NPC: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedDeck\" xml:space=\"preserve\">\n    <value>红方卡组详情:</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedInfo\" xml:space=\"preserve\">\n    <value>&lt; 隐藏的红方幻卡，不包含下方可见的:</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedPlacedAll\" xml:space=\"preserve\">\n    <value>必带卡: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_RedPlacedVariable\" xml:space=\"preserve\">\n    <value>选带卡: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_Rules\" xml:space=\"preserve\">\n    <value>规则: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Details_ScanId\" xml:space=\"preserve\">\n    <value>扫描Id: {0}</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_NpcUnknown\" xml:space=\"preserve\">\n    <value>未知</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_RulesWaiting\" xml:space=\"preserve\">\n    <value>NPC已改变，正在等待扫描...</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_ActiveTurn\" xml:space=\"preserve\">\n    <value>准备就绪（我的回合！）</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_AutoScanMouseOverBoard\" xml:space=\"preserve\">\n    <value>将鼠标光标从扫描区移开！</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_CactpotReady\" xml:space=\"preserve\">\n    <value>仙人微彩:准备就绪</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_FailedCardMatching\" xml:space=\"preserve\">\n    <value>未知幻卡！查看[对局:捕获模式]选项卡了解详情</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingCards\" xml:space=\"preserve\">\n    <value>找不到蓝色卡组</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingGameProcess\" xml:space=\"preserve\">\n    <value>游戏没有运行</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingGameWindow\" xml:space=\"preserve\">\n    <value>无法找到游戏窗口</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_MissingGrid\" xml:space=\"preserve\">\n    <value>无法找到牌桌</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_NoInputImage\" xml:space=\"preserve\">\n    <value>无法检索到待分析的图像</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_NoScannerMatch\" xml:space=\"preserve\">\n    <value>无法找到小游戏窗口</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_Ready\" xml:space=\"preserve\">\n    <value>准备就绪</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_ScannerErrors\" xml:space=\"preserve\">\n    <value>无法识别小游戏</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_UnknownHash\" xml:space=\"preserve\">\n    <value>未知图案！查看[对局:捕获模式]选项卡了解详情</value>\n  </data>\n  <data name=\"OverlayForm_Dynamic_Status_WaitingForTurn\" xml:space=\"preserve\">\n    <value>等待蓝方回合</value>\n  </data>\n  <data name=\"Settings_AlwaysOnTop\" xml:space=\"preserve\">\n    <value>窗口总在最前</value>\n  </data>\n  <data name=\"Settings_AlwaysSmallIcons\" xml:space=\"preserve\">\n    <value>始终使用小图标</value>\n  </data>\n  <data name=\"Settings_DisableHardwareAcceleration\" xml:space=\"preserve\">\n    <value>禁用硬件加速</value>\n  </data>\n  <data name=\"Settings_FontSize\" xml:space=\"preserve\">\n    <value>字体大小</value>\n  </data>\n  <data name=\"Settings_MarkerDurationCactpot\" xml:space=\"preserve\">\n    <value>标记持续时间: 仙人微彩</value>\n  </data>\n  <data name=\"Settings_MarkerDurationCard\" xml:space=\"preserve\">\n    <value>标记持续时间: 幻卡</value>\n  </data>\n  <data name=\"Settings_MarkerDurationSwap\" xml:space=\"preserve\">\n    <value>标记持续时间: 交换</value>\n  </data>\n  <data name=\"Settings_SkipOptionalSimulateRules\" xml:space=\"preserve\">\n    <value>在模拟中跳过可选规则</value>\n  </data>\n  <data name=\"Settings_Title\" xml:space=\"preserve\">\n    <value>设置</value>\n  </data>\n</root>"
  },
  {
    "path": "sources/ui/App.xaml",
    "content": "﻿<Application x:Class=\"FFTriadBuddy.UI.App\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:local=\"clr-namespace:FFTriadBuddy.UI\"\n             Startup=\"Application_Startup\"\n             Exit=\"Application_Exit\">\n    <Application.Resources>\n        <SolidColorBrush x:Key=\"BrushWindowBackground\" Color=\"#FFF0F0F0\"/>\n        <SolidColorBrush x:Key=\"brushBorder\" Color=\"#FFACACAC\"/>\n\n        <BooleanToVisibilityConverter x:Key=\"convVis\"/>\n        <local:BoolToInvisibilityConverter x:Key=\"convInvis\"/>\n        <local:InversedBoolConverter x:Key=\"convInverse\"/>\n        <local:SystemIconConverter x:Key=\"convIcon\"/>\n\n        <Style TargetType=\"{x:Type ComboBoxItem}\">\n            <Setter Property=\"HorizontalContentAlignment\" Value=\"Left\" />\n            <Setter Property=\"VerticalContentAlignment\" Value=\"Center\" />\n        </Style>\n        \n        <Style x:Key=\"tabControlSwitcher\" TargetType=\"{x:Type TabControl}\">\n            <Setter Property=\"Background\" Value=\"Transparent\"/>\n            <Setter Property=\"Padding\" Value=\"0\"/>\n            <Setter Property=\"BorderThickness\" Value=\"0\"/>\n            <Setter Property=\"ItemContainerStyle\">\n                <Setter.Value>\n                    <Style TargetType=\"{x:Type TabItem}\">\n                        <Setter Property=\"Template\">\n                            <Setter.Value>\n                                <ControlTemplate TargetType=\"{x:Type TabItem}\"/>\n                            </Setter.Value>\n                        </Setter>\n                    </Style>\n                </Setter.Value>\n            </Setter>\n        </Style>\n\n        <LinearGradientBrush x:Key=\"FlatButtonBorder\" StartPoint=\"0.499,0\" EndPoint=\"0.501,1\">\n            <GradientStop Color=\"#FFF\" Offset=\"0\" />\n            <GradientStop Color=\"#DDD\" Offset=\"0.01\" />\n            <GradientStop Color=\"#AAA\" Offset=\"0.99\" />\n            <GradientStop Color=\"#888\" Offset=\"1\" />\n        </LinearGradientBrush>\n        <Style x:Key=\"tabItemFlat\" TargetType=\"{x:Type TabItem}\">\n            <Setter Property=\"Template\">\n                <Setter.Value>\n                    <ControlTemplate TargetType=\"{x:Type TabItem}\">\n                        <Border Name=\"Panel\" BorderBrush=\"Transparent\" BorderThickness=\"1,0\">\n                            <ContentPresenter x:Name=\"ContentSite\"\n                                                      VerticalAlignment=\"Center\"\n                                                      HorizontalAlignment=\"Center\"\n                                                      ContentSource=\"Header\"\n                                                      Margin=\"30,2\"/>\n                        </Border>\n                        <ControlTemplate.Triggers>\n                            <Trigger Property=\"IsSelected\" Value=\"True\">\n                                <Setter TargetName=\"Panel\" Property=\"Background\" Value=\"#BBBBBB\" />\n                                <Setter TargetName=\"Panel\" Property=\"BorderBrush\" Value=\"{StaticResource FlatButtonBorder}\"/>\n                            </Trigger>\n                            <MultiTrigger>\n                                <MultiTrigger.Conditions>\n                                    <Condition Property=\"IsMouseOver\" Value=\"True\"/>\n                                    <Condition Property=\"IsSelected\" Value=\"False\"/>\n                                </MultiTrigger.Conditions>\n                                <Setter TargetName=\"Panel\" Property=\"Background\" Value=\"#FFF0F0F0\"/>\n                                <Setter TargetName=\"Panel\" Property=\"BorderBrush\" Value=\"{x:Null}\"/>\n                            </MultiTrigger>\n                        </ControlTemplate.Triggers>\n                    </ControlTemplate>\n                </Setter.Value>\n            </Setter>\n        </Style>\n        <Style x:Key=\"tabControlFlat\" TargetType=\"{x:Type TabControl}\">\n            <Setter Property=\"Padding\" Value=\"0\"/>\n            <Setter Property=\"BorderThickness\">\n                <Setter.Value>\n                    <Thickness Top=\"1\" Bottom=\"1\" Left=\"1\" Right=\"1\"/>\n                </Setter.Value>\n            </Setter>\n            <Setter Property=\"ItemContainerStyle\" Value=\"{StaticResource tabItemFlat}\"/>\n        </Style>\n\n        <ControlTemplate x:Key=\"ctxActionSeparator\">\n            <Separator />\n        </ControlTemplate>\n\n        <Style x:Key=\"ctxActionStyle\" TargetType=\"{x:Type MenuItem}\">\n            <Setter Property=\"Header\" Value=\"{Binding Name}\"/>\n            <Setter Property=\"Command\" Value=\"{Binding Command}\"/>\n            <Setter Property=\"IsChecked\" Value=\"{Binding IsChecked}\"/>\n            <Setter Property=\"CommandParameter\" Value=\"{Binding Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}\"/>\n\n            <Style.Triggers>\n                <DataTrigger Binding=\"{Binding IsSeparator}\" Value=\"True\">\n                    <Setter Property=\"Template\" Value=\"{StaticResource ctxActionSeparator}\" />\n                </DataTrigger>\n                <DataTrigger Binding=\"{Binding IsCheckbox}\" Value=\"True\">\n                    <Setter Property=\"IsCheckable\" Value=\"True\"/>\n                    <Setter Property=\"StaysOpenOnClick\" Value=\"True\"/>\n                </DataTrigger>\n            </Style.Triggers>\n        </Style>\n    </Application.Resources>\n</Application>\n"
  },
  {
    "path": "sources/ui/App.xaml.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System;\nusing System.Globalization;\nusing System.Reflection;\nusing System.Resources;\nusing System.Windows;\nusing System.Windows.Media;\n\nnamespace FFTriadBuddy.UI\n{\n    /// <summary>\n    /// Interaction logic for App.xaml\n    /// </summary>\n    public partial class App : Application\n    {\n        private bool canSaveSettings = false;\n\n        private void Application_Startup(object sender, StartupEventArgs e)\n        {\n            Logger.Initialize(e.Args);\n\n            bool canStart = false;\n\n            bool updatePending = GithubUpdater.FindAndApplyUpdates();\n            if (!updatePending)\n            {\n                bool hasAssets = LoadAssets();\n                if (hasAssets)\n                {\n                    canStart = true;\n                }\n                else\n                {\n                    string appName = Assembly.GetEntryAssembly().GetName().Name;\n                    MessageBox.Show(\"Failed to initialize resources!\", appName, MessageBoxButton.OK, MessageBoxImage.Stop);\n                }\n            }\n\n#if DEBUG\n            if (Array.Find(e.Args, x => x == \"-runTests\") != null)\n            {\n                TestManager.RunTests();\n                canStart = false;\n            }\n\n            if (Array.Find(e.Args, x => x == \"-dataConvert\") != null)\n            {\n                var converter = new DataConverter();\n                converter.Run();\n                canStart = false;\n            }\n\n            if (Array.Find(e.Args, x => x == \"-runSolverAccTest\") != null)\n            {\n                TriadGameTests.RunSolverAccuracyTests();\n                canStart = false;\n            }\n            else if (Array.Find(e.Args, x => x == \"-runSolverStressTest\") != null)\n            {\n                TriadGameTests.RunSolverStressTest();\n                canStart = false;\n            }\n            else if (Array.Find(e.Args, x => x == \"-generateSolverTrainingData\") != null)\n            {\n                TriadGameTests.GenerateAccuracyTrainingData();\n                canStart = false;\n            }\n#endif // DEBUG\n\n            if (canStart)\n            {\n                int renderingTier = RenderCapability.Tier >> 16;\n                Logger.WriteLine(\"Rendering tier:{0}\", renderingTier);\n\n                DialogWindowService.Initialize();\n                OverlayWindowService.Initialize();\n                AppWindowService.Initialize();\n\n                canSaveSettings = true;\n\n                var settingsDB = PlayerSettingsDB.Get();\n                ViewModelServices.AppWindow.SetSoftwareRendering(settingsDB.useSoftwareRendering);\n\n                var window = new MainWindow();\n                window.FontSize = settingsDB.fontSize;\n                window.Topmost = settingsDB.alwaysOnTop;\n\n                if (settingsDB.lastHeight > window.MinHeight) { window.Height = settingsDB.lastHeight; }\n                if (settingsDB.lastWidth > window.MinWidth) { window.Width = settingsDB.lastWidth; }\n\n                window.Show();\n            }\n            else\n            {\n                Shutdown();\n            }\n        }\n\n        private void Application_Exit(object sender, ExitEventArgs e)\n        {\n            if (canSaveSettings)\n            {\n                SettingsModel.Close();\n            }\n\n            XInputStub.StopPolling();\n            Logger.Close();\n        }\n\n        private bool LoadAssets()\n        {\n            bool bResult = false;\n\n            try\n            {\n                var resManager = new ResourceManager(\"FFTriadBuddy.Properties.Resources\", Assembly.GetExecutingAssembly());\n                var assets = (byte[])resManager.GetObject(\"assets\");\n\n                if (AssetManager.Get().Init(assets))\n                {\n                    LocalizationDB.SetCurrentUserLanguage(CultureInfo.CurrentCulture.Name);\n\n                    bResult = TriadCardDB.Get().Load();\n                    bResult = bResult && TriadNpcDB.Get().Load();\n                    bResult = bResult && ImageHashDB.Get().Load();\n                    bResult = bResult && TriadTournamentDB.Get().Load();\n                    bResult = bResult && LocalizationDB.Get().Load();\n\n                    if (bResult)\n                    {\n                        SettingsModel.Initialize();\n                        IconDB.Get().Load();\n                        ModelProxyDB.Get().Load();\n\n                        TriadGameSimulation.StaticInitialize();\n                    }\n                }\n            }\n            catch (Exception ex)\n            {\n                Logger.WriteLine(\"Init failed: \" + ex);\n                bResult = false;\n            }\n\n            return bResult;\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/modelproxy/BulkObservableCollection.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing System.Collections.Specialized;\n\nnamespace FFTriadBuddy.UI\n{\n    public class BulkObservableCollection<T> : ObservableCollection<T>\n    {\n        private bool isNotifySuspended = false;\n        public bool IsNotifySuspended => isNotifySuspended;\n\n        private bool needsNotify = false;\n        private int cachedCount = 0;\n\n        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)\n        {\n            if (!isNotifySuspended)\n            {\n                base.OnCollectionChanged(e);\n            }\n            else\n            {\n                needsNotify = true;\n            }\n        }\n\n        public void SuspendNotifies()\n        {\n            cachedCount = Items.Count;\n            isNotifySuspended = true;\n            needsNotify = false;\n        }\n\n        public void ResumeNotifies()\n        {\n            isNotifySuspended = false;\n            if (needsNotify)\n            {\n                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));\n            }\n        }\n\n        public void AddRange(IEnumerable<T> newItems)\n        {\n            SuspendNotifies();\n            foreach (var item in newItems)\n            {\n                Add(item);\n            }\n\n            isNotifySuspended = false;\n            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItems, cachedCount));\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/modelproxy/CardModelProxy.cs",
    "content": "﻿using System;\n\nnamespace FFTriadBuddy.UI\n{\n    // viewmodel wrapper for model class: card\n    public class CardModelProxy : BaseViewModel, IComparable, IImageHashMatch\n    {\n        public readonly TriadCard cardOb;\n\n        public string NameLocalized => cardOb.Name.GetLocalized();\n        public string DescDeckPicker => string.Format(\"{0} ({1})\", NameLocalized, new string('*', (int)cardOb.Rarity + 1));\n        public int GameSortGroup => cardOb.SortOrder / 1000;\n        public int GameSortOrder => cardOb.SortOrder;\n        public int Id => cardOb.Id;\n\n        public string DescPower => string.Format(\"{0:X}-{1:X}-{2:X}-{3:X}\", cardOb.Sides[(int)ETriadGameSide.Up], cardOb.Sides[(int)ETriadGameSide.Left], cardOb.Sides[(int)ETriadGameSide.Down], cardOb.Sides[(int)ETriadGameSide.Right]);\n        public string DescRarity { get; private set; }\n        public ETriadCardRarity Rarity => cardOb.Rarity;\n        public string DescCardType => LocalizationDB.Get().mapCardTypes[cardOb.Type].GetLocalized();\n        public ETriadCardType CardType => cardOb.Type;\n\n        private bool isOwned;\n        public bool IsOwned\n        {\n            get => isOwned;\n            set\n            {\n                if (isOwned != value)\n                {\n                    isOwned = value;\n                    OnPropertyChanged();\n                    ModelProxyDB.Get().UpdateOwnedCard(this);\n                }\n            }\n        }\n\n        public int CompareTo(object obj)\n        {\n            var otherCard = obj as CardModelProxy;\n            return (otherCard != null) ? NameLocalized.CompareTo(otherCard.NameLocalized) : 0;\n        }\n\n        public CardModelProxy(TriadCard triadCard)\n        {\n            cardOb = triadCard;\n\n            DescRarity = \"*\";\n            for (int idx = 0; idx < (int)triadCard.Rarity; idx++)\n            {\n                DescRarity += \" *\";\n            }\n        }\n\n        public object GetMatchOwner()\n        {\n            return cardOb;\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/modelproxy/IconDB.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Windows.Media.Imaging;\n\nnamespace FFTriadBuddy.UI\n{\n    public class IconDB\n    {\n        public List<BitmapImage> mapCardImages;\n        public List<BitmapImage> mapCardImagesBig;\n        public Dictionary<ETriadCardType, BitmapImage> mapCardTypes;\n        public Dictionary<ETriadCardRarity, BitmapImage> mapCardRarities;\n        public Dictionary<string, BitmapImage> mapFlags;\n\n        private static IconDB instance = new IconDB();\n        public static IconDB Get() { return instance; }\n\n        public void Load()\n        {\n            LoadCardImages();\n            LoadCardTypes();\n            LoadCardRarities();\n            LoadFlags();\n        }\n\n        private BitmapImage LoadImageFromAsset(string path)\n        {\n            var image = new BitmapImage();\n            using (var fileStream = AssetManager.Get().GetAsset(path))\n            {\n                using (var memStream = new MemoryStream())\n                {\n                    if (fileStream != null)\n                    {\n                        fileStream.CopyTo(memStream);\n                        memStream.Position = 0;\n\n                        image.BeginInit();\n                        image.CacheOption = BitmapCacheOption.OnLoad;\n                        image.StreamSource = memStream;\n                        image.EndInit();\n                        image.Freeze();\n                    }\n                }\n            }\n\n            return image;\n        }\n\n        private void LoadCardImages()\n        {\n            mapCardImages = new List<BitmapImage>();\n            mapCardImagesBig = new List<BitmapImage>();\n\n            string nullImagePath = \"icons/088001.png\";\n            var nullImg = LoadImageFromAsset(nullImagePath);\n\n            string nullImageBigPath = \"icons/087000.png\";\n            var nullImgBig = LoadImageFromAsset(nullImageBigPath);\n\n            TriadCardDB cardDB = TriadCardDB.Get();\n            for (int idx = 0; idx < cardDB.cards.Count; idx++)\n            {\n                var cardOb = cardDB.cards[idx];\n                if (cardOb != null && cardOb.IsValid())\n                {\n                    string loadPathSmall = \"icons/\" + cardOb.SmallIconId.ToString(\"000000\") + \".png\";\n                    var loadedImage = LoadImageFromAsset(loadPathSmall);\n                    mapCardImages.Add(loadedImage);\n\n                    string loadPathBig = \"icons/\" + cardOb.BigIconId.ToString(\"000000\") + \".png\";\n                    loadedImage = LoadImageFromAsset(loadPathBig);\n                    mapCardImagesBig.Add(loadedImage);\n                }\n                else\n                {\n                    mapCardImages.Add(nullImg);\n                    mapCardImagesBig.Add(nullImgBig);\n                }\n            }\n        }\n\n        private void LoadCardTypes()\n        {\n            mapCardTypes = new Dictionary<ETriadCardType, BitmapImage>();\n            mapCardTypes.Add(ETriadCardType.None, null);\n            mapCardTypes.Add(ETriadCardType.Beastman, LoadImageFromAsset(\"parts/typeBeastman.png\"));\n            mapCardTypes.Add(ETriadCardType.Primal, LoadImageFromAsset(\"parts/typePrimal.png\"));\n            mapCardTypes.Add(ETriadCardType.Scion, LoadImageFromAsset(\"parts/typeScions.png\"));\n            mapCardTypes.Add(ETriadCardType.Garlean, LoadImageFromAsset(\"parts/typeGarland.png\"));\n        }\n\n        private void LoadCardRarities()\n        {\n            mapCardRarities = new Dictionary<ETriadCardRarity, BitmapImage>();\n            mapCardRarities.Add(ETriadCardRarity.Common, LoadImageFromAsset(\"parts/rarityCommon.png\"));\n            mapCardRarities.Add(ETriadCardRarity.Uncommon, LoadImageFromAsset(\"parts/rarityUncommon.png\"));\n            mapCardRarities.Add(ETriadCardRarity.Rare, LoadImageFromAsset(\"parts/rarityRare.png\"));\n            mapCardRarities.Add(ETriadCardRarity.Epic, LoadImageFromAsset(\"parts/rarityEpic.png\"));\n            mapCardRarities.Add(ETriadCardRarity.Legendary, LoadImageFromAsset(\"parts/rarityLegendary.png\"));\n        }\n\n        private void LoadFlags()\n        {\n            mapFlags = new Dictionary<string, BitmapImage>();\n            mapFlags.Add(\"de\", LoadImageFromAsset(\"flags/flag-germany.png\"));\n            mapFlags.Add(\"en\", LoadImageFromAsset(\"flags/flag-usa.png\"));\n            mapFlags.Add(\"es\", LoadImageFromAsset(\"flags/flag-spain.png\"));\n            mapFlags.Add(\"fr\", LoadImageFromAsset(\"flags/flag-france.png\"));\n            mapFlags.Add(\"ja\", LoadImageFromAsset(\"flags/flag-japan.png\"));\n            mapFlags.Add(\"zh\", LoadImageFromAsset(\"flags/flag-china.png\"));\n            mapFlags.Add(\"ko\", LoadImageFromAsset(\"flags/flag-southkorea.png\"));\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/modelproxy/ImageHashDataModelProxy.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Drawing.Imaging;\nusing System.IO;\nusing System.Windows.Media.Imaging;\n\nnamespace FFTriadBuddy.UI\n{\n    public interface IImageHashMatch\n    {\n        string NameLocalized { get; }\n        object GetMatchOwner();\n    }\n\n    public class ImageHashDataModelProxy : LocalizedViewModel\n    {\n        public class NumberVM : IComparable, IImageHashMatch\n        {\n            public int Value;\n            public string NameLocalized => Value.ToString(\"X\");\n\n            public int CompareTo(object obj)\n            {\n                return Value.CompareTo((obj as NumberVM).Value);\n            }\n\n            public object GetMatchOwner()\n            {\n                return Value;\n            }\n        }\n\n        public readonly ImageHashData hashData;\n\n        public string DescMatch =>\n            hashData.isAuto ? loc.strings.MainForm_Dynamic_Screenshot_MatchType_Auto :\n            (hashData.matchDistance == 0) ? loc.strings.MainForm_Dynamic_Screenshot_MatchType_Exact :\n            loc.strings.MainForm_Dynamic_Screenshot_MatchType_Similar;\n\n        private string cachedName;\n        public string NameLocalized => cachedName;\n\n        private string cachedType;\n        public string TypeLocalized => cachedType;\n\n        private BitmapImage cachedPreview;\n        public BitmapImage PreviewImage { get { GeneratePreview(); return cachedPreview; } }\n\n        private List<IImageHashMatch> listMatches = new List<IImageHashMatch>();\n        public List<IImageHashMatch> ListMatches { get { GenerateMatches(); return listMatches; } }\n\n        private IImageHashMatch currentMatch = null;\n        public IImageHashMatch CurrentMatch { get { GenerateMatches(); return currentMatch; } }\n\n        private static List<NumberVM> listCactpotNumbers;\n        private static List<NumberVM> listCardNumbers;\n\n        public ImageHashDataModelProxy(ImageHashData hashData)\n        {\n            this.hashData = hashData;\n            UpdateCachedText();\n        }\n\n        public override void RefreshLocalization()\n        {\n            UpdateCachedText();\n            OnPropertyChanged(\"DescMatch\");\n            OnPropertyChanged(\"NameLocalized\");\n            OnPropertyChanged(\"TypeLocalized\");\n        }\n\n        public void UpdateCachedText()\n        {\n            switch (hashData.type)\n            {\n                case EImageHashType.Rule: cachedType = loc.strings.MainForm_Dynamic_Screenshot_HashType_Rule; break;\n                case EImageHashType.Cactpot: cachedType = loc.strings.MainForm_Dynamic_Screenshot_HashType_Number; break;\n                case EImageHashType.CardImage: cachedType = loc.strings.MainForm_Dynamic_Screenshot_HashType_Card; break;\n                case EImageHashType.CardNumber: cachedType = loc.strings.MainForm_Dynamic_Screenshot_HashType_Number; break;\n                default: cachedType = \"??\"; break;\n            }\n\n            string descName = \"??\";\n            if (hashData.ownerOb != null)\n            {\n                switch (hashData.type)\n                {\n                    case EImageHashType.Rule: descName = (hashData.ownerOb as TriadGameModifier).GetLocalizedName(); break;\n                    case EImageHashType.Cactpot: descName = ((int)hashData.ownerOb).ToString(); break;\n                    case EImageHashType.CardImage: descName = (hashData.ownerOb as TriadCard).ToShortLocalizedString(); break;\n                    case EImageHashType.CardNumber: descName = ((int)hashData.ownerOb).ToString(); break;\n                    default: break;\n                }\n            }\n\n            cachedName = cachedType + \": \" + descName;\n        }\n\n        private void GeneratePreview()\n        {\n            if (cachedPreview == null)\n            {\n                hashData.UpdatePreviewImage();\n\n                using (var memory = new MemoryStream())\n                {\n                    hashData.previewImage.Save(memory, ImageFormat.Png);\n                    memory.Position = 0;\n\n                    cachedPreview = new BitmapImage();\n                    cachedPreview.BeginInit();\n                    cachedPreview.StreamSource = memory;\n                    cachedPreview.CacheOption = BitmapCacheOption.OnLoad;\n                    cachedPreview.EndInit();\n                    cachedPreview.Freeze();\n                }\n            }\n        }\n\n        private void GenerateMatches()\n        {\n            if (listMatches.Count > 0)\n            {\n                return;\n            }\n\n            var modelProxyDB = ModelProxyDB.Get();\n            switch (hashData.type)\n            {\n                case EImageHashType.Rule:\n                    foreach (var rule in modelProxyDB.Rules)\n                    {\n                        if ((rule.modOb is TriadGameModifierNone) == false)\n                        {\n                            listMatches.Add(rule);\n                        }\n                    }\n                    listMatches.Sort();\n                    break;\n\n                case EImageHashType.Cactpot:\n                    if (listCactpotNumbers == null)\n                    {\n                        listCactpotNumbers = new List<NumberVM>();\n                        for (int idx = 1; idx <= 9; idx++)\n                        {\n                            listCactpotNumbers.Add(new NumberVM() { Value = idx });\n                        }\n                    }\n\n                    listMatches.AddRange(listCactpotNumbers);\n                    break;\n\n                case EImageHashType.CardImage:\n                    var sameNumberId = ((TriadCard)hashData.ownerOb).SameNumberId;\n                    foreach (var cardOb in TriadCardDB.Get().sameNumberMap[sameNumberId])\n                    {\n                        listMatches.Add(modelProxyDB.GetCardProxy(cardOb));\n                    }\n                    listMatches.Sort();\n                    break;\n\n                case EImageHashType.CardNumber:\n                    if (listCardNumbers == null)\n                    {\n                        listCardNumbers = new List<NumberVM>();\n                        for (int idx = 1; idx <= 10; idx++)\n                        {\n                            listCardNumbers.Add(new NumberVM() { Value = idx });\n                        }\n                    }\n\n                    listMatches.AddRange(listCardNumbers);\n                    break;\n\n                default:\n                    break;\n            }\n\n            currentMatch = listMatches.Find(x => x.GetMatchOwner().Equals(hashData.ownerOb));\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/modelproxy/ModelProxyDB.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Windows.Data;\n\nnamespace FFTriadBuddy.UI\n{\n    // helper class for translating between tool's data objects and Models used by UI\n    // technically, it's supposed to be ViewModel layer\n    public class ModelProxyDB\n    {\n        private List<CardModelProxy> cards = new List<CardModelProxy>();\n        public List<CardModelProxy> Cards => cards;\n\n        private BulkObservableCollection<CardModelProxy> ownedCards = new BulkObservableCollection<CardModelProxy>();\n        public BulkObservableCollection<CardModelProxy> OwnedCards => ownedCards;\n\n        private List<NpcModelProxy> npcs = new List<NpcModelProxy>();\n        public List<NpcModelProxy> Npcs => npcs;\n\n        private List<RuleModelProxy> rules = new List<RuleModelProxy>();\n        public List<RuleModelProxy> Rules => rules;\n\n        private List<TournamentModelProxy> tournaments = new List<TournamentModelProxy>();\n        public List<TournamentModelProxy> Tournaments => tournaments;\n\n        public event Action<CardModelProxy> OnCardOwnerChanged;\n\n        private static ModelProxyDB instance = new ModelProxyDB();\n        public static ModelProxyDB Get() { return instance; }\n\n        public void Load()\n        {\n            LoadCards();\n            LoadOwnedCards();\n            LoadNpc();\n            LoadRules();\n            LoadTournaments();\n\n            PlayerSettingsDB.Get().OnUpdated += ModelProxyDB_OnUpdated;\n            UpdateCompletedNpcs();\n\n            LocalizationDB.OnLanguageChanged += LocalizationDB_OnLanguageChanged;\n        }\n\n        private void LoadCards()\n        {\n            TriadCardDB cardDB = TriadCardDB.Get();\n            cards.Clear();\n\n            for (int idx = 0; idx < cardDB.cards.Count; idx++)\n            {\n                var cardEntry = cardDB.cards[idx];\n                if (cardEntry != null && cardEntry.IsValid())\n                {\n                    cards.Add(new CardModelProxy(cardEntry));\n                }\n            }\n        }\n\n        private void LoadOwnedCards()\n        {\n            List<CardModelProxy> removeList = new List<CardModelProxy>();\n            removeList.AddRange(ownedCards);\n\n            ownedCards.SuspendNotifies();\n            ownedCards.Clear();\n\n            var settingsDB = PlayerSettingsDB.Get();\n            foreach (var card in settingsDB.ownedCards)\n            {\n                var cardProxy = cards.Find(x => x.cardOb.Id == card.Id);\n                if (cardProxy != null)\n                {\n                    cardProxy.IsOwned = true;\n                    ownedCards.Add(cardProxy);\n                    removeList.Remove(cardProxy);\n                }\n            }\n\n            foreach (var card in removeList)\n            {\n                card.IsOwned = false;\n            }\n\n            ownedCards.ResumeNotifies();\n        }\n\n        private void LoadNpc()\n        {\n            TriadNpcDB npcDB = TriadNpcDB.Get();\n            npcs.Clear();\n\n            for (int idx = 0; idx < npcDB.npcs.Count; idx++)\n            {\n                var npcEntry = npcDB.npcs[idx];\n                if (npcEntry != null)\n                {\n                    npcs.Add(new NpcModelProxy(npcEntry));\n                }\n            }\n        }\n\n        private void LoadRules()\n        {\n            TriadGameModifierDB modDB = TriadGameModifierDB.Get();\n            rules.Clear();\n\n            for (int idx = 0; idx < modDB.mods.Count; idx++)\n            {\n                var modEntry = modDB.mods[idx];\n                if (modEntry != null)\n                {\n                    rules.Add(new RuleModelProxy(modEntry));\n                }\n            }\n\n            var view = CollectionViewSource.GetDefaultView(rules);\n            if (view.SortDescriptions.Count == 0)\n            {\n                view.SortDescriptions.Add(new SortDescription());\n            }\n        }\n\n        private void LoadTournaments()\n        {\n            TriadTournamentDB tournamentDB = TriadTournamentDB.Get();\n            tournaments.Clear();\n\n            for (int idx = 0; idx < tournamentDB.tournaments.Count; idx++)\n            {\n                var tourEntry = tournamentDB.tournaments[idx];\n                if (tourEntry != null)\n                {\n                    tournaments.Add(new TournamentModelProxy(tourEntry));\n                }\n            }\n\n            var view = CollectionViewSource.GetDefaultView(tournaments);\n            if (view.SortDescriptions.Count == 0)\n            {\n                view.SortDescriptions.Add(new SortDescription());\n            }\n        }\n\n        private void ModelProxyDB_OnUpdated(bool bCards, bool bNpcs, bool bDecks)\n        {\n            if (bCards)\n            {\n                LoadOwnedCards();\n            }\n        }\n\n        private void LocalizationDB_OnLanguageChanged()\n        {\n            foreach (var npc in npcs)\n            {\n                npc.RefreshLocalization();\n            }\n\n            foreach (var tournament in tournaments)\n            {\n                tournament.RefreshLocalization();\n            }\n\n            foreach (var rule in rules)\n            {\n                rule.RefreshLocalization();\n            }\n\n            CollectionViewSource.GetDefaultView(npcs).Refresh();\n            CollectionViewSource.GetDefaultView(cards).Refresh();\n            CollectionViewSource.GetDefaultView(rules).Refresh();\n            CollectionViewSource.GetDefaultView(tournaments).Refresh();\n        }\n\n        public void UpdateOwnedCard(CardModelProxy cardProxy)\n        {\n            if (!ownedCards.IsNotifySuspended)\n            {\n                var settingsDB = PlayerSettingsDB.Get();\n                var hasChanges = false;\n\n                if (cardProxy.IsOwned && !OwnedCards.Contains(cardProxy))\n                {\n                    //Logger.WriteLine(\"Adding owned card: {0}\", cardProxy.cardOb.Name.GetCodeName());\n                    OwnedCards.Add(cardProxy);\n                    settingsDB.ownedCards.Add(cardProxy.cardOb);\n                    hasChanges = true;\n                }\n                else if (!cardProxy.IsOwned && OwnedCards.Contains(cardProxy))\n                {\n                    //Logger.WriteLine(\"Removing owned card: {0}\", cardProxy.cardOb.Name.GetCodeName());\n                    OwnedCards.Remove(cardProxy);\n                    settingsDB.ownedCards.Remove(cardProxy.cardOb);\n                    hasChanges = true;\n                }\n\n                if (hasChanges)\n                {\n                    settingsDB.MarkDirty();\n                    UpdateCompletedNpcs();\n\n                    OnCardOwnerChanged?.Invoke(cardProxy);\n                }\n            }\n        }\n\n        private void UpdateCompletedNpcs()\n        {\n            var settingsDB = PlayerSettingsDB.Get();\n            foreach (var npc in npcs)\n            {\n                var notOwnedReward = npc.npcOb.Rewards.Find(x => !settingsDB.ownedCards.Contains(x));\n                npc.IsCompleted = notOwnedReward == null;\n                npc.UpdateCachedText();\n            }\n        }\n\n        public NpcModelProxy GetNpcProxy(TriadNpc npcOb)\n        {\n            return Npcs.Find(x => x.npcOb == npcOb);\n        }\n\n        public CardModelProxy GetCardProxy(TriadCard cardOb)\n        {\n            return Cards.Find(x => x.cardOb == cardOb);\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/modelproxy/NpcModelProxy.cs",
    "content": "﻿using System;\n\nnamespace FFTriadBuddy.UI\n{\n    // viewmodel wrapper for model class: npc\n    public class NpcModelProxy : LocalizedViewModel, IComparable\n    {\n        public readonly TriadNpc npcOb;\n\n        public string NameLocalized => npcOb.Name.GetLocalized();\n        public string LocationLocalized => npcOb.GetLocationDesc();\n        public int DescPower => npcOb.Deck.GetPower();\n\n        private bool isCompleted = false;\n        public bool IsCompleted\n        {\n            get { return isCompleted; }\n            set { isCompleted = value; OnPropertyChanged(); }\n        }\n\n        private string descReward;\n        public string DescReward\n        {\n            get { return descReward; }\n            set { descReward = value; OnPropertyChanged(); }\n        }\n\n        private string descRules;\n        public string DescRules\n        {\n            get { return descRules; }\n            set { descRules = value; OnPropertyChanged(); }\n        }\n\n        private string descCompleted;\n        public string DescCompleted\n        {\n            get { return descCompleted; }\n            set { descCompleted = value; OnPropertyChanged(); }\n        }\n\n        public int CompareTo(object obj)\n        {\n            var otherNpc = obj as NpcModelProxy;\n            return (otherNpc != null) ? NameLocalized.CompareTo(otherNpc.NameLocalized) : 0;\n        }\n\n        public NpcModelProxy(TriadNpc triadNpc)\n        {\n            npcOb = triadNpc;\n\n            UpdateCachedText();\n        }\n\n        public void UpdateCachedText(bool sendNotifies = true)\n        {\n            var newDescRules = \"\";\n            foreach (var rule in npcOb.Rules)\n            {\n                if (newDescRules.Length > 0) { newDescRules += \", \"; }\n                newDescRules += rule.GetLocalizedName();\n            }\n\n            if (newDescRules.Length == 0)\n            {\n                newDescRules = loc.strings.MainForm_Dynamic_RuleListEmpty;\n            }\n\n            PlayerSettingsDB settingsDB = PlayerSettingsDB.Get();\n            var newDescRewards = \"\";\n            foreach (var reward in npcOb.Rewards)\n            {\n                if (!settingsDB.ownedCards.Contains(reward))\n                {\n                    if (newDescRewards.Length > 0) { newDescRewards += \", \"; }\n                    newDescRewards += reward.Name.GetLocalized();\n                }\n            }\n\n            descRules = newDescRules;\n            descReward = newDescRewards;\n            descCompleted = IsCompleted ? loc.strings.MainForm_Dynamic_NpcCompletedColumn : \"\";\n\n            if (sendNotifies)\n            {\n                DescRules = descRules;\n                DescReward = descReward;\n                DescCompleted = descCompleted;\n            }\n        }\n\n        public override void RefreshLocalization()\n        {\n            OnPropertyChanged(\"NameLocalized\");\n            OnPropertyChanged(\"LocationLocalized\");\n            UpdateCachedText();\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/modelproxy/RuleModelProxy.cs",
    "content": "﻿using System;\n\nnamespace FFTriadBuddy.UI\n{\n    // viewmodel wrapper for model class: modifier / rule\n    public class RuleModelProxy : LocalizedViewModel, IComparable, IImageHashMatch\n    {\n        public readonly TriadGameModifier modOb;\n\n        public string NameLocalized => modOb.GetLocalizedName();\n\n        public RuleModelProxy(TriadGameModifier triadMod)\n        {\n            modOb = triadMod;\n        }\n\n        public int CompareTo(object obj)\n        {\n            var otherRule = obj as RuleModelProxy;\n            return (otherRule != null) ? NameLocalized.CompareTo(otherRule.NameLocalized) : 0;\n        }\n\n        public object GetMatchOwner()\n        {\n            return modOb;\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/modelproxy/SettingsModel.cs",
    "content": "﻿using MgAl2O4.GoogleAPI;\nusing MgAl2O4.Utils;\nusing System;\nusing System.Threading.Tasks;\n\nnamespace FFTriadBuddy.UI\n{\n    public class SettingsModel\n    {\n        public enum CloudSaveState\n        {\n            None,\n            Loaded,\n            Saved,\n            UpToDate,\n        }\n\n        public static GoogleDriveService CloudStorage;\n        public static event Action<GoogleDriveService.EState> OnCloudStorageApiUpdate;\n        public static event Action<CloudSaveState> OnCloudStorageStateUpdate;\n\n        private static bool cloudSettingsInitialized = false;\n        private static bool cloudSettingsCanSave = false;\n        private static object cloudSettingsSyncLock = new object();\n        private static Task cloudSettingsUpdateTask;\n\n        private static CloudSaveState cachedSaveState;\n        private static GoogleDriveService.EState cachedApiState;\n\n        public static void Initialize()\n        {\n            var settingsDB = PlayerSettingsDB.Get();\n\n            bool loaded = settingsDB.Load();\n            if (loaded)\n            {\n                settingsDB.SaveBackup();\n            }\n            else\n            {\n                Logger.WriteLine(\"Warning: failed to load player settings!\");\n            }\n\n            if (!string.IsNullOrEmpty(settingsDB.forcedLanguage))\n            {\n                LocalizationDB.SetCurrentUserLanguage(settingsDB.forcedLanguage);\n            }\n\n            if (settingsDB.useXInput)\n            {\n                XInputStub.StartPolling();\n            }\n\n            CloudStorage = new GoogleDriveService(\n                GoogleClientIdentifiers.Keys,\n                new GoogleOAuth2.Token() { refreshToken = settingsDB.cloudToken });\n        }\n\n        public static void Close()\n        {\n            lock (cloudSettingsSyncLock)\n            {\n                cloudSettingsCanSave = false;\n            }\n\n            var settingsDB = PlayerSettingsDB.Get();\n            if (settingsDB.isDirty && settingsDB.useCloudStorage)\n            {\n                _ = CloudStorageSave();\n            }\n\n            settingsDB.Save();\n        }\n\n        public static void SetUseCloudSaves(bool useCloud)\n        {\n            GoogleOAuth2.KillPendingAuthorization();\n\n            var settingsDB = PlayerSettingsDB.Get();\n            settingsDB.useCloudStorage = useCloud;\n\n            if (useCloud && CloudStorage != null)\n            {\n                if (!cloudSettingsInitialized)\n                {\n                    CloudStorageInit();\n                }\n                else\n                {\n                    CloudStorageSendNotifies(CloudSaveState.UpToDate);\n                }\n            }\n\n            lock (cloudSettingsSyncLock)\n            {\n                cloudSettingsCanSave = useCloud;\n            }\n\n            if (cloudSettingsUpdateTask == null)\n            {\n                cloudSettingsUpdateTask = new Task(async () =>\n                {\n                    const int intervalMs = 2 * 60 * 1000;\n                    while (true)\n                    {\n                        await Task.Delay(intervalMs);\n\n                        bool canSave = false;\n                        lock (cloudSettingsSyncLock)\n                        {\n                            canSave = cloudSettingsCanSave;\n                        }\n\n                        if (canSave)\n                        {\n                            if (PlayerSettingsDB.Get().isDirty)\n                            {\n                                await CloudStorageSave();\n                            }\n                            else\n                            {\n                                CloudStorageSendNotifies(CloudSaveState.UpToDate);\n                            }\n                        }\n                    }\n                });\n                cloudSettingsUpdateTask.Start();\n            }\n        }\n\n        public static async void CloudStorageInit()\n        {\n            cachedApiState = GoogleDriveService.EState.AuthInProgress;\n            OnCloudStorageApiUpdate.Invoke(cachedApiState);\n\n            try\n            {\n                await CloudStorage.InitFileList();\n            }\n            catch (Exception ex)\n            {\n                Logger.WriteLine(\"Exception: \" + ex);\n            }\n\n            OnCloudStorageApiUpdate.Invoke(CloudStorage.GetState());\n\n            var settingsDB = PlayerSettingsDB.Get();\n            settingsDB.cloudToken = CloudStorage.GetAuthToken().refreshToken;\n\n            bool needsSave = await CloudStorageLoad();\n            cloudSettingsInitialized = true;\n\n            if (needsSave)\n            {\n                await CloudStorageSave();\n            }\n        }\n\n        private static async Task<bool> CloudStorageLoad()\n        {\n            string fileContent = null;\n            try\n            {\n                fileContent = await CloudStorage.DownloadTextFile(\"FFTriadBuddy-settings.json\");\n\n                Logger.WriteLine(\"Loaded cloud save, API response: \" + CloudStorage.GetLastApiResponse());\n            }\n            catch (Exception ex)\n            {\n                Logger.WriteLine(\"Exception: \" + ex);\n            }\n\n            CloudStorageSendNotifies(CloudSaveState.Loaded);\n\n            bool needsSave = true;\n            if (!string.IsNullOrEmpty(fileContent))\n            {\n                needsSave = PlayerSettingsDB.Get().MergeWithContent(fileContent);\n            }\n\n            return needsSave;\n        }\n\n        private static async Task CloudStorageSave()\n        {\n            string fileContent = PlayerSettingsDB.Get().SaveToString();\n            if (!string.IsNullOrEmpty(fileContent))\n            {\n                try\n                {\n                    await CloudStorage.UploadTextFile(\"FFTriadBuddy-settings.json\", fileContent);\n\n                    Logger.WriteLine(\"Created cloud save, API response: \" + CloudStorage.GetLastApiResponse());\n                }\n                catch (Exception ex)\n                {\n                    Logger.WriteLine(\"Exception: \" + ex);\n                }\n\n                CloudStorageSendNotifies(CloudSaveState.Saved);\n            }\n        }\n\n        private static void CloudStorageSendNotifies(CloudSaveState state)\n        {\n            cachedApiState = CloudStorage.GetState();\n            cachedSaveState = state;\n            CloudStorageRequestState();\n        }\n\n        public static void CloudStorageRequestState()\n        {\n            if (cachedApiState == GoogleDriveService.EState.NoErrors)\n            {\n                OnCloudStorageStateUpdate.Invoke(cachedSaveState);\n            }\n            else\n            {\n                OnCloudStorageApiUpdate.Invoke(cachedApiState);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/modelproxy/TournamentModelProxy.cs",
    "content": "﻿using System;\n\nnamespace FFTriadBuddy.UI\n{\n    // viewmodel wrapper for model class: tournament\n    public class TournamentModelProxy : LocalizedViewModel, IComparable\n    {\n        public readonly TriadTournament tournamentOb;\n\n        public string NameLocalized => tournamentOb.Name.GetLocalized();\n\n        private string descRules;\n        public string DescRules\n        {\n            get { return descRules; }\n            set { descRules = value; OnPropertyChanged(); }\n        }\n\n        public TournamentModelProxy(TriadTournament triadTournament)\n        {\n            tournamentOb = triadTournament;\n            UpdateCachedText();\n        }\n\n        public int CompareTo(object obj)\n        {\n            var otherTournament = obj as TournamentModelProxy;\n            return (otherTournament != null) ? NameLocalized.CompareTo(otherTournament.NameLocalized) : 0;\n        }\n\n        public void UpdateCachedText(bool sendNotifies = true)\n        {\n            var newDescRules = \"\";\n            foreach (var rule in tournamentOb.Rules)\n            {\n                if (newDescRules.Length > 0) { newDescRules += \", \"; }\n                newDescRules += rule.GetLocalizedName();\n            }\n\n            if (newDescRules.Length == 0)\n            {\n                newDescRules = loc.strings.MainForm_Dynamic_RuleListEmpty;\n            }\n\n            descRules = newDescRules;\n            if (sendNotifies)\n            {\n                DescRules = descRules;\n            }\n        }\n\n        public override void RefreshLocalization()\n        {\n            OnPropertyChanged(\"NameLocalized\");\n            UpdateCachedText();\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/modelproxy/TriadGameModel.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\n\nnamespace FFTriadBuddy.UI\n{\n    // model / state for keeping current game\n    public class TriadGameModel\n    {\n        public class Move\n        {\n            public TriadCard Card;\n            public int CardIdx;\n            public int BoardIdx;\n            public SolverResult WinChance;\n        }\n\n        public TriadNpc Npc { get; private set; }\n        public TriadDeck PlayerDeck { get; private set; }\n        public List<TriadGameModifier> Rules { get; } = new List<TriadGameModifier>();\n\n        public TriadGameSolver Solver = new TriadGameSolver();\n        public TriadGameSimulationState GameState = null;\n        public SolverResult CachedWinChance;\n\n        public List<TriadGameSimulationState> UndoStateRed = new List<TriadGameSimulationState>();\n        public TriadGameSimulationState UndoStateBlue = null;\n\n        public event Action<TriadNpc> OnNpcChanged;\n        public event Action<TriadDeck> OnDeckChanged;\n        public event Action<TriadGameModel> OnSetupChanged;\n        public event Action<TriadGameSimulationState, Move> OnGameStateChanged;\n        public event Action<TriadGameModel> OnCachedWinChanceChanged;\n\n        private bool isTournament = false;\n\n        public TriadGameModel()\n        {\n            TriadNpc npcOb = TriadNpcDB.Get().npcs.Find(x => x?.Id == PlayerSettingsDB.Get().lastNpcId);\n            if (npcOb == null)\n            {\n                npcOb = TriadNpcDB.Get().Find(\"Triple Triad master\");\n            }\n\n            SetNpc(npcOb);\n        }\n\n        public void SetNpc(TriadNpc npc)\n        {\n            if (npc == Npc) { return; }\n\n            Logger.WriteLine(\"Game.SetNpc: {0}:{1}\", npc?.Id, npc?.Name.GetCodeName());\n            Npc = npc;\n            OnNpcChanged?.Invoke(npc);\n\n            var useDeck = FindDeckToUseFor(npc);\n            SetPlayerDeck(useDeck, notifySetupChange: false);\n\n            UpdateSession();\n\n            PlayerSettingsDB.Get().lastNpcId = npc.Id;\n        }\n\n        public void SetGameRules(List<TriadGameModifier> mods)\n        {\n            if (mods.Count == 2)\n            {\n                SetGameRules(mods[0], mods[1], true);\n            }\n        }\n\n        public void SetGameRules(TriadGameModifier mod1, TriadGameModifier mod2, bool isTournament = false)\n        {\n            this.isTournament = isTournament;\n            Logger.WriteLine(\"Game.SetRules: '{0}' + '{1}'{2}\",\n                mod1?.GetCodeName(), mod2?.GetCodeName(),\n                isTournament ? \" (tournament mode)\" : \"\");\n\n            Rules.Clear();\n            Rules.Add(mod1);\n            Rules.Add(mod2);\n\n            UpdateSession();\n        }\n\n        public void SetPlayerDeck(TriadDeck deck, bool notifySetupChange = true)\n        {\n            Logger.WriteLine(\"Game.SetPlayerDeck: {0}\", deck);\n            PlayerDeck = deck;\n\n            if (Npc != null && deck != null)\n            {\n                PlayerSettingsDB.Get().UpdatePlayerDeckForNpc(Npc, deck);\n            }\n\n            OnDeckChanged?.Invoke(deck);\n            if (notifySetupChange)\n            {\n                OnSetupChanged?.Invoke(this);\n            }\n        }\n\n        public void SetCachedWinChance(TriadDeck deck, SolverResult winChance)\n        {\n            if (PlayerDeck.Equals(deck))\n            {\n                CachedWinChance = winChance;\n                OnCachedWinChanceChanged?.Invoke(this);\n            }\n        }\n\n        public void ResolveSpecialRule(ETriadGameSpecialMod specialMod)\n        {\n            GameState.resolvedSpecial |= specialMod;\n            OnGameStateChanged?.Invoke(GameState, null);\n        }\n\n        private TriadDeck FindDeckToUseFor(TriadNpc npc)\n        {\n            PlayerSettingsDB settingsDB = PlayerSettingsDB.Get();\n            TriadCard[] cardsCopy = null;\n            if (settingsDB.lastDeck.ContainsKey(npc))\n            {\n                TriadDeck savedDeck = PlayerSettingsDB.Get().lastDeck[npc];\n                if (savedDeck != null && savedDeck.knownCards.Count == 5)\n                {\n                    cardsCopy = savedDeck.knownCards.ToArray();\n                }\n            }\n\n            if (cardsCopy == null)\n            {\n                cardsCopy = new TriadCard[5];\n\n                if (PlayerDeck == null)\n                {\n                    Array.Copy(settingsDB.starterCards, cardsCopy, cardsCopy.Length);\n                }\n                else\n                {\n                    Array.Copy(PlayerDeck.knownCards.ToArray(), cardsCopy, cardsCopy.Length);\n                }\n            }\n\n            return new TriadDeck(cardsCopy);\n        }\n\n        private void UpdateSession(bool notifySetupChange = true)\n        {\n            Solver = new TriadGameSolver();\n            Solver.InitializeSimulation(Rules, isTournament ? null : Npc.Rules);\n\n#if DEBUG\n            Solver.agent.debugFlags = TriadGameAgent.DebugFlags.AgentInitialize | TriadGameAgent.DebugFlags.ShowMoveStart | TriadGameAgent.DebugFlags.ShowMoveDetails | TriadGameAgent.DebugFlags.ShowMoveDetailsRng;\n#endif // DEBUG\n\n            GameReset();\n\n            if (notifySetupChange)\n            {\n                OnSetupChanged?.Invoke(this);\n            }\n        }\n\n        public void GameReset()\n        {\n            Logger.WriteLine(\"Game.Reset\");\n\n            GameState = Solver.StartSimulation(PlayerDeck, Npc.Deck, ETriadGameState.InProgressRed);\n            UndoStateBlue = null;\n            UndoStateRed.Clear();\n\n            OnGameStateChanged?.Invoke(GameState, null);\n        }\n\n        public void GameUndoRed()\n        {\n            if (UndoStateRed.Count > 0)\n            {\n                GameState = UndoStateRed[UndoStateRed.Count - 1];\n                UndoStateRed.RemoveAt(UndoStateRed.Count - 1);\n\n                OnGameStateChanged?.Invoke(GameState, null);\n            }\n        }\n\n        public void GameStartBlue()\n        {\n            if (GameState != null && GameState.numCardsPlaced == 0)\n            {\n                GameState.state = ETriadGameState.InProgressBlue;\n                GamePlayBlueCard();\n            }\n        }\n\n        public void SetGameForcedBlueCard(TriadCard card)\n        {\n            if (GameState != null && GameState.state == ETriadGameState.InProgressRed && UndoStateBlue != null)\n            {\n                var blueDeckEx = GameState.deckBlue as TriadDeckInstanceManual;\n                if (Solver.HasSimulationRule(ETriadGameSpecialMod.BlueCardSelection) && blueDeckEx != null)\n                {\n                    int deckSlotIdx = blueDeckEx.GetCardIndex(card);\n                    if (GameState.forcedCardIdx != deckSlotIdx && !blueDeckEx.IsPlaced(deckSlotIdx))\n                    {\n                        Logger.WriteLine(\"Force blue card: {0}\", card.Name.GetCodeName());\n\n                        GameState = UndoStateBlue;\n                        GameState.forcedCardIdx = deckSlotIdx;\n                        GamePlayBlueCard();\n                    }\n                }\n            }\n        }\n\n        public void SetGameRedCard(TriadCard card, int boardIdx)\n        {\n            if (GameState != null)\n            {\n                GameState.forcedCardIdx = -1;\n                TriadGameSimulationState newUndoState = new TriadGameSimulationState(GameState);\n\n                Logger.WriteLine(\"Red> [{0}]: {1}\", boardIdx, card.Name.GetCodeName());\n                GameState.bDebugRules = true;\n                bool bPlaced = Solver.simulation.PlaceCard(GameState, card, ETriadCardOwner.Red, boardIdx);\n                GameState.bDebugRules = false;\n\n                // additional debug logs\n                int numBoardPlaced = 0;\n                {\n                    int availBoardMask = 0;\n                    for (int Idx = 0; Idx < GameState.board.Length; Idx++)\n                    {\n                        if (GameState.board[Idx] != null)\n                        {\n                            numBoardPlaced++;\n                        }\n                        else\n                        {\n                            availBoardMask |= (1 << Idx);\n                        }\n                    }\n\n                    Logger.WriteLine(\"  Board cards:{0} ({1:x}), placed:{2}\", numBoardPlaced, availBoardMask, bPlaced);\n                }\n\n                if (bPlaced)\n                {\n                    if (numBoardPlaced == GameState.board.Length)\n                    {\n                        OnGameStateChanged?.Invoke(GameState, null);\n                    }\n                    else\n                    {\n                        GamePlayBlueCard();\n                    }\n\n                    UndoStateRed.Add(newUndoState);\n                }\n            }\n        }\n\n        private void GamePlayBlueCard()\n        {\n            if (GameState.state == ETriadGameState.InProgressBlue)\n            {\n                if (Solver.HasSimulationRule(ETriadGameSpecialMod.BlueCardSelection))\n                {\n                    UndoStateBlue = new TriadGameSimulationState(GameState);\n                }\n\n                bool hasMove = Solver.FindNextMove(GameState, out int bestCardIdx, out int bestNextPos, out SolverResult bestChance);\n                if (hasMove)\n                {\n                    var bestCardOb = GameState.deckBlue.GetCard(bestCardIdx);\n                    Logger.WriteLine(\"Blue> [{0}]: {1} => {2}: {3:P0}\",\n                        bestNextPos, bestCardOb.Name.GetCodeName(),\n                        bestChance.expectedResult == ETriadGameState.BlueDraw ? \"draw\" : \"win\",\n                        bestChance.expectedResult == ETriadGameState.BlueDraw ? bestChance.drawChance : bestChance.winChance);\n\n                    GameState.bDebugRules = true;\n                    Solver.simulation.PlaceCard(GameState, bestCardIdx, GameState.deckBlue, ETriadCardOwner.Blue, bestNextPos);\n                    GameState.bDebugRules = false;\n\n                    OnGameStateChanged?.Invoke(GameState, new Move() { Card = bestCardOb, CardIdx = bestCardIdx, BoardIdx = bestNextPos, WinChance = bestChance });\n                }\n                else\n                {\n                    OnGameStateChanged?.Invoke(GameState, null);\n                }\n            }\n        }\n\n        public void GameRouletteApplied()\n        {\n            Logger.WriteLine(\"Game.Roulette applied\");\n            Solver.simulation.UpdateSpecialRules();\n            OnSetupChanged?.Invoke(this);\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/AdjustCardDialog.xaml",
    "content": "﻿<UserControl x:Class=\"FFTriadBuddy.UI.AdjustCardDialog\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \n             xmlns:local=\"clr-namespace:FFTriadBuddy.UI\"\n             mc:Ignorable=\"d\"\n             d:DataContext=\"{d:DesignInstance Type=local:AdjustCardViewModel, IsDesignTimeCreatable=True}\">\n    <Grid>\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"10\"/>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition MinHeight=\"50\"/>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition Height=\"10\"/>\n        </Grid.RowDefinitions>\n        <Grid.ColumnDefinitions>\n            <ColumnDefinition Width=\"10\"/>\n            <ColumnDefinition Width=\"Auto\"/>\n            <ColumnDefinition Width=\"10\"/>\n            <ColumnDefinition Width=\"Auto\" MinWidth=\"100\"/>\n            <ColumnDefinition MinWidth=\"200\"/>\n            <ColumnDefinition Width=\"10\"/>\n        </Grid.ColumnDefinitions>\n        <TextBlock Grid.Column=\"1\" Grid.Row=\"1\" Text=\"{Binding AdjustForm_Current}\"/>\n        <TextBlock Grid.Column=\"3\" Grid.Row=\"1\" Grid.ColumnSpan=\"2\" Text=\"{Binding CardDesc}\"/>\n        <Grid Grid.Column=\"1\" Grid.Row=\"2\" Grid.ColumnSpan=\"3\" Margin=\"10\">\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition Width=\"Auto\"/>\n                <ColumnDefinition Width=\"10\"/>\n                <ColumnDefinition/>\n            </Grid.ColumnDefinitions>\n            <Grid.RowDefinitions>\n                <RowDefinition Height=\"Auto\"/>\n                <RowDefinition Height=\"Auto\"/>\n                <RowDefinition Height=\"Auto\"/>\n                <RowDefinition Height=\"Auto\"/>\n            </Grid.RowDefinitions>\n            <TextBlock Grid.Column=\"0\" Grid.Row=\"0\" Text=\"{Binding AdjustForm_CardUp}\"/>\n            <TextBlock Grid.Column=\"0\" Grid.Row=\"1\" Text=\"{Binding AdjustForm_CardLeft}\"/>\n            <TextBlock Grid.Column=\"0\" Grid.Row=\"2\" Text=\"{Binding AdjustForm_CardDown}\"/>\n            <TextBlock Grid.Column=\"0\" Grid.Row=\"3\" Text=\"{Binding AdjustForm_CardRight}\"/>\n            <TextBlock Grid.Column=\"2\" Grid.Row=\"0\" Text=\"{Binding DescSideUp}\"/>\n            <TextBlock Grid.Column=\"2\" Grid.Row=\"1\" Text=\"{Binding DescSideLeft}\"/>\n            <TextBlock Grid.Column=\"2\" Grid.Row=\"2\" Text=\"{Binding DescSideDown}\"/>\n            <TextBlock Grid.Column=\"2\" Grid.Row=\"3\" Text=\"{Binding DescSideRight}\"/>\n        </Grid>\n        <TextBlock Grid.Column=\"1\" Grid.Row=\"3\" Text=\"{Binding AdjustForm_CardStatus}\"/>\n        <TextBlock Grid.Column=\"3\" Grid.Row=\"3\" Text=\"{Binding CardState}\"/>\n        <Grid Grid.Column=\"1\" Grid.Row=\"5\" Grid.ColumnSpan=\"3\" Margin=\"10\">\n            <Grid.Resources>\n                <Style TargetType=\"{x:Type Border}\">\n                    <Setter Property=\"BorderBrush\" Value=\"{StaticResource brushBorder}\"/>\n                    <Setter Property=\"BorderThickness\" Value=\"1\"/>\n                    <Setter Property=\"HorizontalAlignment\" Value=\"Right\"/>\n                    <Setter Property=\"Margin\" Value=\"1,2\"/>\n                    <Setter Property=\"Width\" Value=\"{Binding ActualHeight, RelativeSource={RelativeSource Self}}\"/>\n                </Style>\n                <Style TargetType=\"{x:Type local:NumTextBox}\">\n                    <Setter Property=\"Margin\" Value=\"1,2\"/>\n                </Style>\n                <Style TargetType=\"{x:Type Image}\">\n                    <Setter Property=\"MinWidth\" Value=\"20\"/>\n                    <Setter Property=\"MinHeight\" Value=\"20\"/>\n                </Style>\n            </Grid.Resources>\n            <Grid.RowDefinitions>\n                <RowDefinition Height=\"Auto\"/>\n                <RowDefinition Height=\"Auto\"/>\n                <RowDefinition Height=\"Auto\"/>\n            </Grid.RowDefinitions>\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition/>\n                <ColumnDefinition Width=\"Auto\"/>\n                <ColumnDefinition Width=\"Auto\"/>\n                <ColumnDefinition Width=\"Auto\"/>\n                <ColumnDefinition Width=\"Auto\"/>\n            </Grid.ColumnDefinitions>\n            <Border Grid.Column=\"2\" Grid.Row=\"0\">\n                <Image Source=\"{Binding ImageSideUp}\"/>\n            </Border>\n            <Border Grid.Column=\"3\" Grid.Row=\"1\">\n                <Image Source=\"{Binding ImageSideLeft}\"/>\n            </Border>\n            <Border Grid.Column=\"2\" Grid.Row=\"2\">\n                <Image Source=\"{Binding ImageSideDown}\"/>\n            </Border>\n            <Border Grid.Column=\"1\" Grid.Row=\"1\">\n                <Image Source=\"{Binding ImageSideRight}\"/>\n            </Border>\n            <local:NumTextBox Grid.Column=\"3\" Grid.Row=\"0\" Value=\"{Binding SideUp, Mode=TwoWay}\"/>\n            <local:NumTextBox Grid.Column=\"4\" Grid.Row=\"1\" Value=\"{Binding SideLeft, Mode=TwoWay}\"/>\n            <local:NumTextBox Grid.Column=\"3\" Grid.Row=\"2\" Value=\"{Binding SideDown, Mode=TwoWay}\"/>\n            <local:NumTextBox Grid.Column=\"2\" Grid.Row=\"1\" Value=\"{Binding SideRight, Mode=TwoWay}\"/>\n        </Grid>\n        <TextBlock Grid.Row=\"6\" Text=\"{Binding AdjustForm_CardList}\" HorizontalAlignment=\"Left\" VerticalAlignment=\"Center\" Grid.Column=\"1\"/>\n        <ComboBox Grid.Row=\"6\" Grid.Column=\"3\" Grid.ColumnSpan=\"2\" ItemsSource=\"{Binding MatchingCards}\" SelectedItem=\"{Binding SelectedMatch}\" DisplayMemberPath=\"NameLocalized\" IsEnabled=\"{Binding HasMultipleMatches}\"/>\n        <Image Grid.Row=\"2\" Grid.Column=\"4\" Grid.RowSpan=\"4\" Margin=\"0,10\" Source=\"{Binding ImageCard}\"/>\n        <StackPanel Grid.Column=\"1\" Grid.ColumnSpan=\"4\" Grid.Row=\"7\" Margin=\"0,10,0,0\" Orientation=\"Horizontal\" HorizontalAlignment=\"Right\">\n            <Button Content=\"{Binding AdjustForm_SaveButton}\" Command=\"{Binding CommandSave}\" Padding=\"20,0\" MinWidth=\"100\" Margin=\"10,0,0,0\"/>\n            <Button Content=\"{Binding AdjustForm_CancelButton}\" IsCancel=\"True\" Padding=\"20,0\" MinWidth=\"100\" Margin=\"10,0,0,0\"/>\n        </StackPanel>\n    </Grid>\n</UserControl>\n"
  },
  {
    "path": "sources/ui/view/AdjustCardDialog.xaml.cs",
    "content": "﻿using System.Windows.Controls;\n\nnamespace FFTriadBuddy.UI\n{\n    /// <summary>\n    /// Interaction logic for AdjustCardDialog.xaml\n    /// </summary>\n    public partial class AdjustCardDialog : UserControl\n    {\n        public AdjustCardDialog()\n        {\n            InitializeComponent();\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/AdjustHashDialog.xaml",
    "content": "﻿<UserControl x:Class=\"FFTriadBuddy.UI.AdjustHashDialog\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \n             xmlns:local=\"clr-namespace:FFTriadBuddy.UI\"\n             mc:Ignorable=\"d\" \n             d:DataContext=\"{d:DesignInstance Type=local:AdjustHashViewModel, IsDesignTimeCreatable=True}\">\n    <Grid>\n        <Grid.ColumnDefinitions>\n            <ColumnDefinition Width=\"10\"/>\n            <ColumnDefinition Width=\"Auto\"/>\n            <ColumnDefinition Width=\"10\"/>\n            <ColumnDefinition MinWidth=\"300\"/>\n            <ColumnDefinition Width=\"10\"/>\n        </Grid.ColumnDefinitions>\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"10\"/>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition Height=\"1*\" MinHeight=\"80\"/>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition Height=\"10\"/>\n        </Grid.RowDefinitions>\n        <TextBlock Grid.Row=\"1\" Text=\"{Binding AdjustForm_Current}\" Margin=\"0\" Grid.Column=\"1\"/>\n        <TextBlock Grid.Row=\"2\" Text=\"{Binding AdjustForm_Distance}\" Grid.Column=\"1\"/>\n        <TextBlock Grid.Row=\"3\" Text=\"{Binding AdjustForm_HashList}\" VerticalAlignment=\"Center\" Grid.Column=\"1\"/>\n        <TextBlock Grid.Column=\"3\" Grid.Row=\"1\" Text=\"{Binding HashProxy.TypeLocalized}\" Margin=\"0\"/>\n        <StackPanel Grid.Column=\"3\" Grid.Row=\"2\" Orientation=\"Horizontal\" Margin=\"0\">\n            <TextBlock Text=\"{Binding MatchDistance}\"/>\n            <TextBlock Text=\"{Binding MatchDistanceInfo}\" Margin=\"10,0,0,0\"/>\n        </StackPanel>\n        <ComboBox Grid.Column=\"3\" Grid.Row=\"3\" ItemsSource=\"{Binding HashProxy.ListMatches}\" SelectedItem=\"{Binding SelectedMatch}\" DisplayMemberPath=\"NameLocalized\" Margin=\"0\"/>\n        <Image Grid.Row=\"4\" Grid.ColumnSpan=\"3\" Margin=\"0,10,0,0\" Source=\"{Binding HashProxy.PreviewImage}\" Stretch=\"None\" HorizontalAlignment=\"Left\" VerticalAlignment=\"Top\" Grid.Column=\"1\"/>\n        <StackPanel Grid.ColumnSpan=\"3\" Grid.Row=\"5\" Margin=\"0,10,0,0\" Orientation=\"Horizontal\" HorizontalAlignment=\"Right\" Grid.Column=\"1\">\n            <Button Content=\"{Binding AdjustForm_SaveButton}\" Command=\"{Binding CommandSave}\" Padding=\"20,0\" MinWidth=\"100\" Margin=\"10,0,0,0\"/>\n            <Button Content=\"{Binding AdjustForm_CancelButton}\" IsCancel=\"True\" Padding=\"20,0\" MinWidth=\"100\" Margin=\"10,0,0,0\"/>\n        </StackPanel>\n    </Grid>\n</UserControl>\n"
  },
  {
    "path": "sources/ui/view/AdjustHashDialog.xaml.cs",
    "content": "﻿using System.Windows.Controls;\n\nnamespace FFTriadBuddy.UI\n{\n    /// <summary>\n    /// Interaction logic for AdjustHashDialog.xaml\n    /// </summary>\n    public partial class AdjustHashDialog : UserControl\n    {\n        public AdjustHashDialog()\n        {\n            InitializeComponent();\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/DialogWindow.xaml",
    "content": "﻿<Window x:Class=\"FFTriadBuddy.UI.DialogWindow\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \n             xmlns:local=\"clr-namespace:FFTriadBuddy.UI\"\n             mc:Ignorable=\"d\" Background=\"{StaticResource BrushWindowBackground}\" ShowInTaskbar=\"False\" WindowStartupLocation=\"CenterOwner\" WindowStyle=\"ToolWindow\" Height=\"100\" Width=\"100\">\n    <Window.Resources>\n        <DataTemplate DataType=\"{x:Type local:FavDeckEditViewModel}\">\n            <local:FavDeckEditDialog/>\n        </DataTemplate>\n        <DataTemplate DataType=\"{x:Type local:AdjustHashViewModel}\">\n            <local:AdjustHashDialog/>\n        </DataTemplate>\n        <DataTemplate DataType=\"{x:Type local:AdjustCardViewModel}\">\n            <local:AdjustCardDialog/>\n        </DataTemplate>\n    </Window.Resources>\n</Window>\n"
  },
  {
    "path": "sources/ui/view/DialogWindow.xaml.cs",
    "content": "﻿using System;\nusing System.Windows;\nusing System.Windows.Controls;\n\nnamespace FFTriadBuddy.UI\n{\n    /// <summary>\n    /// Interaction logic for DialogWindow.xaml\n    /// </summary>\n    public partial class DialogWindow : Window\n    {\n        public DialogWindow()\n        {\n            InitializeComponent();\n\n            Loaded += DialogWindow_Loaded;\n        }\n\n        private void DialogWindow_Loaded(object sender, RoutedEventArgs e)\n        {\n            var viewControl = ViewUtils.FindVisualChildRecursive(this, x => x is UserControl) as UserControl;\n            if (viewControl != null)\n            {\n                var controlRoot = viewControl.Content as FrameworkElement;\n                var presenter = viewControl.TemplatedParent as FrameworkElement;\n\n                if (presenter != null && controlRoot != null && controlRoot.ActualWidth > 0 && controlRoot.ActualHeight > 0)\n                {\n                    Size bounds = new Size(controlRoot.ActualWidth, controlRoot.ActualHeight);\n                    presenter.Height = bounds.Height;\n                    presenter.Width = bounds.Width;\n\n                    SizeToContent = SizeToContent.WidthAndHeight;\n                    MinHeight = ActualHeight;\n                    MinWidth = ActualWidth;\n\n                    SizeToContent = SizeToContent.Manual;\n                    presenter.Height = double.NaN;\n                    presenter.Width = double.NaN;\n\n                    // reposition to center after setting size\n                    Left = Owner.Left + Math.Max(0, (Owner.Width - Width) * 0.5f);\n                    Top = Owner.Top + Math.Max(0, (Owner.Height - Height) * 0.5f);\n                }\n            }\n        }\n    }\n\n    public class DialogWindowService : IDialogWindowService\n    {\n        public bool? ShowDialog(IDialogWindowViewModel viewModel)\n        {\n            var window = new DialogWindow\n            {\n                Content = viewModel,\n                Title = viewModel.GetDialogWindowTitle(),\n                FontSize = App.Current.MainWindow.FontSize,\n                Owner = App.Current.MainWindow\n            };\n\n            viewModel.RequestDialogWindowClose += (result) => { window.DialogResult = result; window.Close(); };\n            return window.ShowDialog();\n        }\n\n        public static void Initialize()\n        {\n            ViewModelServices.DialogWindow = new DialogWindowService();\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/FavDeckEditDialog.xaml",
    "content": "﻿<UserControl x:Class=\"FFTriadBuddy.UI.FavDeckEditDialog\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \n             xmlns:local=\"clr-namespace:FFTriadBuddy.UI\"\n             mc:Ignorable=\"d\" \n             d:DataContext=\"{d:DesignInstance Type=local:FavDeckEditViewModel, IsDesignTimeCreatable=True}\">\n    <Grid>\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition Height=\"1*\" MinHeight=\"120\"/>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition Height=\"Auto\"/>\n        </Grid.RowDefinitions>\n        <Grid.ColumnDefinitions>\n            <ColumnDefinition Width=\"Auto\"/>\n            <ColumnDefinition Width=\"1*\" MinWidth=\"420\"/>\n        </Grid.ColumnDefinitions>\n        <TextBlock HorizontalAlignment=\"Left\" Margin=\"10,10,0,0\" TextWrapping=\"Wrap\" Text=\"{Binding FavDeckForm_Info}\" VerticalAlignment=\"Top\" Grid.ColumnSpan=\"2\"/>\n        <local:DeckView DataContext=\"{Binding FavDeck}\" CanPickCards=\"True\" CanReorderCards=\"True\" Height=\"Auto\" Margin=\"10\" Width=\"Auto\" Grid.ColumnSpan=\"2\" Grid.Row=\"1\"/>\n        <TextBlock HorizontalAlignment=\"Left\" Margin=\"10,0,0,0\" TextWrapping=\"Wrap\" Text=\"{Binding FavDeckForm_Name}\" VerticalAlignment=\"Center\" Grid.Row=\"2\"/>\n        <TextBox Grid.Column=\"1\" Margin=\"10,0\" TextWrapping=\"Wrap\" Text=\"{Binding FavDeck.Name}\" Grid.Row=\"2\"/>\n        <StackPanel Grid.Column=\"1\" Grid.Row=\"3\" Margin=\"0,10,10,10\" Orientation=\"Horizontal\" HorizontalAlignment=\"Right\">\n            <Button Content=\"{Binding AdjustForm_SaveButton}\" Command=\"{Binding CommandSave}\" Padding=\"20,0\" MinWidth=\"100\" Margin=\"10,0,0,0\"/>\n            <Button Content=\"{Binding AdjustForm_CancelButton}\" IsCancel=\"True\" Padding=\"20,0\" MinWidth=\"100\" Margin=\"10,0,0,0\"/>\n        </StackPanel>\n    </Grid>\n</UserControl>\n"
  },
  {
    "path": "sources/ui/view/FavDeckEditDialog.xaml.cs",
    "content": "﻿using System.Windows.Controls;\n\nnamespace FFTriadBuddy.UI\n{\n    /// <summary>\n    /// Interaction logic for EditFavDeckview.xaml\n    /// </summary>\n    public partial class FavDeckEditDialog : UserControl\n    {\n        public FavDeckEditDialog()\n        {\n            InitializeComponent();\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/FavDeckPreview.xaml",
    "content": "﻿<UserControl x:Class=\"FFTriadBuddy.UI.FavDeckPreview\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \n             xmlns:local=\"clr-namespace:FFTriadBuddy.UI\"\n             mc:Ignorable=\"d\" \n             d:DataContext=\"{d:DesignInstance Type=local:CardCollectionViewModel, IsDesignTimeCreatable=True}\"\n             d:DesignHeight=\"50\" d:DesignWidth=\"280\">\n    <Grid>\n        <Viewbox>\n            <ItemsControl ItemsSource=\"{Binding Cards}\">\n                <ItemsControl.ItemsPanel>\n                    <ItemsPanelTemplate>\n                        <StackPanel Orientation=\"Horizontal\"/>\n                    </ItemsPanelTemplate>\n                </ItemsControl.ItemsPanel>\n                <ItemsControl.ItemTemplate>\n                    <DataTemplate>\n                        <Grid>\n                            <Image Source=\"{Binding CardImage}\" ToolTip=\"{Binding CardModel.NameLocalized}\" Margin=\"1\"/>\n                            <Image x:Name=\"imageType\" HorizontalAlignment=\"Right\" VerticalAlignment=\"Top\" Source=\"{Binding TypeImage}\" Height=\"20\" Width=\"20\" Opacity=\"0.75\" IsHitTestVisible=\"False\"/>\n                        </Grid>\n                    </DataTemplate>\n                </ItemsControl.ItemTemplate>\n            </ItemsControl>\n        </Viewbox>\n    </Grid>\n</UserControl>\n"
  },
  {
    "path": "sources/ui/view/FavDeckPreview.xaml.cs",
    "content": "﻿using System.Windows.Controls;\n\nnamespace FFTriadBuddy.UI\n{\n    /// <summary>\n    /// Interaction logic for FavDeckPreview.xaml\n    /// </summary>\n    public partial class FavDeckPreview : UserControl\n    {\n        public FavDeckPreview()\n        {\n            InitializeComponent();\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/MainWindow.xaml",
    "content": "﻿<Window x:Class=\"FFTriadBuddy.UI.MainWindow\"\n        xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n        xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n        xmlns:local=\"clr-namespace:FFTriadBuddy.UI\"\n        mc:Ignorable=\"d\"\n        Title=\"{Binding WindowTitle}\" Height=\"340\" MinHeight=\"340\" MinWidth=\"850\" Width=\"850\" PreviewKeyDown=\"Window_PreviewKeyDown\" SizeChanged=\"Window_SizeChanged\">\n    <Window.DataContext>\n        <local:MainWindowViewModel/>\n    </Window.DataContext>\n    <Grid Background=\"#FFE5E5E5\">\n        <TabControl SelectedIndex=\"{Binding ActivePageIndex}\" Style=\"{StaticResource tabControlFlat}\">\n            <TabItem Header=\"{Binding MainForm_Setup_Title}\">\n                <local:PageSetup DataContext=\"{Binding PageSetup}\"/>\n            </TabItem>\n            <TabItem Header=\"{Binding MainForm_Screenshot_Title}\">\n                <local:PageScreenshot DataContext=\"{Binding PageScreenshot}\"/>\n            </TabItem>\n            <TabItem Header=\"{Binding MainForm_Simulate_Title}\">\n                <local:PageSimulate DataContext=\"{Binding PageSimulate}\"/>\n            </TabItem>\n            <TabItem Header=\"{Binding MainForm_Cards_Title}\">\n                <local:PageCards DataContext=\"{Binding PageCards}\"/>\n            </TabItem>\n            <TabItem Header=\"{Binding MainForm_Npcs_Title}\">\n                <local:PageNpcs DataContext=\"{Binding PageNpcs}\"/>\n            </TabItem>\n            <TabItem Header=\"{Binding MainForm_Info_Title}\" MinHeight=\"24\">\n                <local:PageInfo DataContext=\"{Binding PageInfo}\"/>\n            </TabItem>\n        </TabControl>\n        <Button Margin=\"0,2,2,0\" Padding=\"1,0,1,0\" HorizontalAlignment=\"Right\" VerticalAlignment=\"Top\" Command=\"{Binding CommandChangeLanguage}\">\n            <Button.Content>\n                <Image Source=\"{Binding LanguageFlag}\" Width=\"40\" Height=\"20\" />\n            </Button.Content>\n        </Button>\n        <Button Content=\"{Binding MainForm_UpdateNotify}\" Margin=\"0\" Height=\"100\" VerticalAlignment=\"Bottom\" Background=\"#5CF898\" Visibility=\"{Binding IsUpdateNotifyVisible, Converter={StaticResource convVis}}\" Command=\"{Binding CommandHideUpdateNotify}\"/>\n    </Grid>\n</Window>\n"
  },
  {
    "path": "sources/ui/view/MainWindow.xaml.cs",
    "content": "﻿using System.Windows;\nusing System.Windows.Input;\nusing System.Windows.Interop;\nusing System.Windows.Media;\n\nnamespace FFTriadBuddy.UI\n{\n    /// <summary>\n    /// Interaction logic for MainWindow.xaml\n    /// </summary>\n    public partial class MainWindow : Window\n    {\n        public MainWindow()\n        {\n            InitializeComponent();\n        }\n\n        private void Window_PreviewKeyDown(object sender, KeyEventArgs e)\n        {\n            if (e.Key == Key.F12)\n            {\n                var mainVM = DataContext as MainWindowViewModel;\n                if (mainVM.CommandDebugScreenshot.CanExecute(null))\n                {\n                    mainVM.CommandDebugScreenshot.Execute(null);\n                    e.Handled = true;\n                }\n            }\n        }\n\n        private void Window_SizeChanged(object sender, SizeChangedEventArgs e)\n        {\n            var settingsDB = PlayerSettingsDB.Get();\n            settingsDB.lastWidth = (float)Width;\n            settingsDB.lastHeight = (float)Height;\n        }\n    }\n\n    public class AppWindowService : IAppWindowService\n    {\n        public void SetAlwaysOnTop(bool value)\n        {\n            var mainWindow = App.Current.MainWindow;\n            mainWindow.Topmost = value;\n        }\n\n        public void SetFontSize(float value)\n        {\n            var mainWindow = App.Current.MainWindow;\n            mainWindow.FontSize = value;\n\n            foreach (Window window in mainWindow.OwnedWindows)\n            {\n                window.FontSize = mainWindow.FontSize;\n            }\n        }\n\n        public void SetSoftwareRendering(bool value)\n        {\n            RenderOptions.ProcessRenderMode = value ? RenderMode.SoftwareOnly : RenderMode.Default;\n        }\n\n        public static void Initialize()\n        {\n            ViewModelServices.AppWindow = new AppWindowService();\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/OverlayWindowInteractive.xaml",
    "content": "﻿<Window x:Class=\"FFTriadBuddy.UI.OverlayWindowInteractive\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \n             xmlns:local=\"clr-namespace:FFTriadBuddy.UI\"\n             mc:Ignorable=\"d\" \n             d:DesignHeight=\"200\" d:DesignWidth=\"400\"\n             d:DataContext=\"{d:DesignInstance Type=local:OverlayWindowViewModel, IsDesignTimeCreatable=True}\"\n             Topmost=\"True\" ResizeMode=\"NoResize\" ShowInTaskbar=\"False\" WindowStyle=\"None\" \n             AllowsTransparency=\"True\" Background=\"{x:Null}\" Opacity=\"0.8\"\n             Loaded=\"Window_Loaded\" Closed=\"Window_Closed\" PreviewMouseLeftButtonUp=\"Window_PreviewMouseLeftButtonUp\">\n    <Window.Resources>\n        <Storyboard x:Key=\"animDash\">\n            <DoubleAnimation Storyboard.TargetProperty=\"StrokeDashOffset\" Duration=\"0:0:1\" From=\"0\" To=\"7\" RepeatBehavior=\"Forever\"/>\n        </Storyboard>\n        <Style x:Key=\"autoScanRect\" TargetType=\"{x:Type Rectangle}\">\n            <Setter Property=\"Visibility\" Value=\"Hidden\"/>\n            <Style.Triggers>\n                <DataTrigger Binding=\"{Binding IsAutoScanActive}\" Value=\"True\">\n                    <Setter Property=\"Visibility\" Value=\"Visible\"/>\n                    <DataTrigger.EnterActions>\n                        <BeginStoryboard Name=\"playAnimDash\" Storyboard=\"{StaticResource animDash}\"/>\n                    </DataTrigger.EnterActions>\n                    <DataTrigger.ExitActions>\n                        <StopStoryboard BeginStoryboardName=\"playAnimDash\"/>\n                    </DataTrigger.ExitActions>\n                </DataTrigger>\n            </Style.Triggers>\n        </Style>\n    </Window.Resources>\n    <Canvas x:Name=\"canvas\">\n        <Border x:Name=\"panelCapture\" BorderBrush=\"{StaticResource brushBorder}\" BorderThickness=\"1\" Canvas.Left=\"10\" Canvas.Top=\"10\" Background=\"{StaticResource BrushWindowBackground}\" MouseMove=\"Border_MouseMove\" MouseLeftButtonDown=\"Border_MouseLeftButtonDown\" IsHitTestVisible=\"True\"\n                local:CanvasExtensions.ScreenBounds=\"{Binding MarkerCapturePanel}\">\n            <Grid>\n                <Grid.ColumnDefinitions>\n                    <ColumnDefinition/>\n                    <ColumnDefinition Width=\"50\"/>\n                    <ColumnDefinition/>\n                </Grid.ColumnDefinitions>\n                <Grid.RowDefinitions>\n                    <RowDefinition Height=\"Auto\"/>\n                    <RowDefinition/>\n                    <RowDefinition Height=\"Auto\"/>\n                </Grid.RowDefinitions>\n                <Button x:Name=\"buttonCapture\" Grid.ColumnSpan=\"3\" Margin=\"5\" HorizontalContentAlignment=\"Stretch\" Command=\"{Binding CommandCapture}\">\n                    <Button.Content>\n                        <Grid>\n                            <TextBlock Text=\"{Binding OverlayForm_Capture_Button}\" Padding=\"8\" HorizontalAlignment=\"Center\"/>\n                            <Rectangle Margin=\"2\" Stroke=\"{StaticResource {x:Static SystemColors.HotTrackBrushKey}}\" RadiusX=\"5\" RadiusY=\"5\" StrokeDashArray=\"5 2\" StrokeThickness=\"2\" Style=\"{StaticResource autoScanRect}\"/>\n                        </Grid>\n                    </Button.Content>\n                </Button>\n                <CheckBox x:Name=\"checkDetails\" Content=\"{Binding OverlayForm_Capture_Details}\" Grid.Row=\"2\" Margin=\"5,5,0,5\" Command=\"{Binding CommandToggleDetails}\" CommandParameter=\"{Binding IsChecked, RelativeSource={RelativeSource Self}}\"/>\n                <CheckBox x:Name=\"checkAuto\" Content=\"{Binding OverlayForm_Capture_AutoScan}\" Grid.Row=\"2\" Grid.Column=\"2\" HorizontalAlignment=\"Right\" Margin=\"0,5,5,5\" IsChecked=\"{Binding UseAutoScan}\"/>\n                <Grid Grid.Row=\"1\" Grid.ColumnSpan=\"3\" Margin=\"5,0\">\n                    <Grid.ColumnDefinitions>\n                        <ColumnDefinition Width=\"Auto\"/>\n                        <ColumnDefinition/>\n                    </Grid.ColumnDefinitions>\n                    <Image Width=\"40\" Height=\"40\" Source=\"{Binding AnalyzerStateIcon}\"/>\n                    <local:NoAutoWidthDecorator Grid.Column=\"1\" Margin=\"5,0,0,0\">\n                        <TextBlock x:Name=\"noAutoText\" Text=\" \"/>\n                    </local:NoAutoWidthDecorator>\n                    <TextBlock TextWrapping=\"Wrap\" Grid.Column=\"1\" Margin=\"5,0,0,0\" Text=\"{Binding DescAnalyzerState}\" MaxWidth=\"{Binding ActualWidth, ElementName=noAutoText}\" VerticalAlignment=\"Center\"/>\n                </Grid>\n            </Grid>\n        </Border>\n    </Canvas>\n</Window>\n"
  },
  {
    "path": "sources/ui/view/OverlayWindowInteractive.xaml.cs",
    "content": "﻿using System;\nusing System.Runtime.InteropServices;\nusing System.Windows;\nusing System.Windows.Controls;\nusing System.Windows.Input;\nusing System.Windows.Interop;\n\nnamespace FFTriadBuddy.UI\n{\n    /// <summary>\n    /// Interaction logic for OverlayWindowInteractive.xaml\n    /// </summary>\n    public partial class OverlayWindowInteractive : Window\n    {\n        [DllImport(\"user32.dll\", SetLastError = true)]\n        public static extern int GetWindowLong(IntPtr hWnd, int nIndex);\n        [DllImport(\"user32.dll\")]\n        public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);\n        public const int GWL_EX_STYLE = -20;\n        public const int WS_EX_APPWINDOW = 0x00040000;\n        public const int WS_EX_TOOLWINDOW = 0x00000080;\n        public const int WS_EX_TRANSPARENT = 0x00000020;\n\n        public event Action<double, double> OnPanelMoved;\n        private Point moveStartPt;\n\n        public OverlayWindowInteractive()\n        {\n            InitializeComponent();\n\n            if (PlayerSettingsDB.Get().useXInput)\n            {\n                XInputStub.StartPolling();\n            }\n        }\n\n        public void SetOverlayActive(bool wantsActive)\n        {\n            Visibility = wantsActive ? Visibility.Visible : Visibility.Hidden;\n            SetXInputEnble(wantsActive);\n        }\n\n        private void Window_Loaded(object sender, RoutedEventArgs e)\n        {\n            var helper = new WindowInteropHelper(this).Handle;\n            var wndStyleEx = (OverlayWindowInteractive.GetWindowLong(helper, OverlayWindowInteractive.GWL_EX_STYLE) | OverlayWindowInteractive.WS_EX_TOOLWINDOW) & ~OverlayWindowInteractive.WS_EX_APPWINDOW;\n            OverlayWindowInteractive.SetWindowLong(helper, OverlayWindowInteractive.GWL_EX_STYLE, wndStyleEx);\n        }\n\n        private void Window_Closed(object sender, EventArgs e)\n        {\n            XInputStub.StopPolling();\n        }\n\n        private void Border_MouseMove(object sender, MouseEventArgs e)\n        {\n            if (Mouse.OverrideCursor != null)\n            {\n                if (Mouse.LeftButton != MouseButtonState.Pressed)\n                {\n                    Mouse.OverrideCursor = null;\n                }\n                else\n                {\n                    var border = sender as Border;\n                    var movePt = e.GetPosition(this);\n\n                    var newX = Canvas.GetLeft(border) + movePt.X - moveStartPt.X;\n                    var newY = Canvas.GetTop(border) + movePt.Y - moveStartPt.Y;\n                    SetPanelCanvasPos(newX, newY);\n\n                    moveStartPt = movePt;\n                }\n            }\n        }\n\n        public void SetPanelCanvasPos(double x, double y)\n        {\n            var clampedX = Math.Max(0, Math.Min(canvas.ActualWidth - panelCapture.ActualWidth, x));\n            var clampedY = Math.Max(0, Math.Min(canvas.ActualHeight - panelCapture.ActualHeight, y));\n\n            Canvas.SetLeft(panelCapture, clampedX);\n            Canvas.SetTop(panelCapture, clampedY);\n\n            OnPanelMoved?.Invoke(clampedX, clampedY);\n        }\n\n        private void Window_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)\n        {\n            if (Mouse.OverrideCursor == Cursors.SizeAll)\n            {\n                Mouse.OverrideCursor = null;\n            }\n        }\n\n        private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)\n        {\n            Mouse.OverrideCursor = Cursors.SizeAll;\n            moveStartPt = e.GetPosition(this);\n        }\n\n        public void SetXInputEnble(bool enable)\n        {\n            if (enable)\n            {\n                XInputStub.OnEventMotionTrigger += XInputEventMotion;\n            }\n            else\n            {\n                XInputStub.OnEventMotionTrigger -= XInputEventMotion;\n            }\n        }\n\n        private void XInputEventMotion()\n        {\n            App.Current.Dispatcher.Invoke(() =>\n            {\n                buttonCapture.Command.Execute(null);\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/OverlayWindowTransparent.xaml",
    "content": "﻿<Window x:Class=\"FFTriadBuddy.UI.OverlayWindowTransparent\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \n             xmlns:local=\"clr-namespace:FFTriadBuddy.UI\"\n             mc:Ignorable=\"d\" \n             d:DesignHeight=\"450\" d:DesignWidth=\"900\"\n             d:DataContext=\"{d:DesignInstance Type=local:OverlayWindowViewModel, IsDesignTimeCreatable=True}\"\n             Topmost=\"True\" ResizeMode=\"NoResize\" ShowInTaskbar=\"False\" WindowStyle=\"None\" \n             AllowsTransparency=\"True\" Background=\"{x:Null}\" Opacity=\"0.8\" Loaded=\"Window_Loaded\">\n    <Window.Resources>\n        <ScaleTransform x:Key=\"pathTM\" ScaleX=\"{Binding Width, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Path}}}\" ScaleY=\"{Binding Height, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Path}}}\"/>\n        <LineGeometry x:Key=\"pathH\" StartPoint=\"0,0.5\" EndPoint=\"1,0.5\" Transform=\"{StaticResource pathTM}\"/>\n        <LineGeometry x:Key=\"pathV\" StartPoint=\"0.5,0\" EndPoint=\"0.5,1\" Transform=\"{StaticResource pathTM}\"/>\n        <LineGeometry x:Key=\"pathSE\" StartPoint=\"0,0\" EndPoint=\"1,1\" Transform=\"{StaticResource pathTM}\"/>\n        <LineGeometry x:Key=\"pathSW\" StartPoint=\"1,0\" EndPoint=\"0,1\" Transform=\"{StaticResource pathTM}\"/>\n        <Style x:Key=\"cactpotPath\" TargetType=\"{x:Type Path}\">\n            <Setter Property=\"Data\" Value=\"{StaticResource pathSW}\"/>\n            <Style.Triggers>\n                <DataTrigger Binding=\"{Binding MarkerCactpotLine.Line}\" Value=\"Horizontal\">\n                    <Setter Property=\"Data\" Value=\"{StaticResource pathH}\"/>\n                </DataTrigger>\n                <DataTrigger Binding=\"{Binding MarkerCactpotLine.Line}\" Value=\"Vertical\">\n                    <Setter Property=\"Data\" Value=\"{StaticResource pathV}\"/>\n                </DataTrigger>\n                <DataTrigger Binding=\"{Binding MarkerCactpotLine.Line}\" Value=\"DiagSE\">\n                    <Setter Property=\"Data\" Value=\"{StaticResource pathSE}\"/>\n                </DataTrigger>\n            </Style.Triggers>\n        </Style>\n        <Style x:Key=\"listCard\" TargetType=\"{x:Type Image}\">\n            <Style.Triggers>\n                <DataTrigger Binding=\"{Binding IsPreview}\" Value=\"True\">\n                    <Setter Property=\"Opacity\" Value=\"0.3\"/>\n                </DataTrigger>\n            </Style.Triggers>\n        </Style>\n        <Style x:Key=\"boardCard\" TargetType=\"{x:Type local:CardView}\">\n            <Style.Triggers>\n                <DataTrigger Binding=\"{Binding CardImage}\" Value=\"{x:Null}\">\n                    <Setter Property=\"Visibility\" Value=\"Hidden\"/>\n                </DataTrigger>\n            </Style.Triggers>\n        </Style>\n        <Style x:Key=\"boardText\" TargetType=\"{x:Type TextBlock}\">\n            <Style.Triggers>\n                <DataTrigger Binding=\"{Binding CactpotNum}\" Value=\"0\">\n                    <Setter Property=\"Visibility\" Value=\"Hidden\"/>\n                </DataTrigger>\n            </Style.Triggers>\n        </Style>\n    </Window.Resources>\n    <Canvas x:Name=\"canvas\">\n        <Rectangle Height=\"50\" Canvas.Left=\"250\" Canvas.Top=\"10\" Width=\"50\" Stroke=\"Turquoise\" StrokeThickness=\"5\" IsHitTestVisible=\"False\" \n                   local:CanvasExtensions.ScreenBounds=\"{Binding MarkerDeck}\" local:CanvasExtensions.AutoHide=\"True\"/>\n        <Rectangle Height=\"50\" Canvas.Left=\"305\" Canvas.Top=\"10\" Width=\"50\" StrokeThickness=\"5\" IsHitTestVisible=\"False\"\n                   local:CanvasExtensions.ScreenBounds=\"{Binding MarkerBoard}\" local:CanvasExtensions.AutoHide=\"True\">\n            <Rectangle.Style>\n                <Style TargetType=\"{x:Type Rectangle}\">\n                    <Setter Property=\"Stroke\" Value=\"Red\"/>\n                    <Style.Triggers>\n                        <DataTrigger Binding=\"{Binding MarkerBoard.Result}\" Value=\"Win\">\n                            <Setter Property=\"Stroke\" Value=\"Lime\"/>\n                        </DataTrigger>\n                        <DataTrigger Binding=\"{Binding MarkerBoard.Result}\" Value=\"Draw\">\n                            <Setter Property=\"Stroke\" Value=\"Gold\"/>\n                        </DataTrigger>\n                    </Style.Triggers>\n                </Style>\n            </Rectangle.Style>\n        </Rectangle>\n        <Path Height=\"50\" Canvas.Left=\"360\" Canvas.Top=\"10\" Width=\"50\" Stroke=\"Lime\" StrokeThickness=\"5\" IsHitTestVisible=\"False\" \n              local:CanvasExtensions.ScreenBounds=\"{Binding MarkerCactpotLine}\" local:CanvasExtensions.AutoHide=\"True\" Style=\"{StaticResource cactpotPath}\"/>\n        <Rectangle Height=\"50\" Canvas.Left=\"420\" Canvas.Top=\"10\" Width=\"50\" Stroke=\"Lime\" StrokeThickness=\"5\" IsHitTestVisible=\"False\" \n                   local:CanvasExtensions.ScreenBounds=\"{Binding MarkerCactpotCircle}\" local:CanvasExtensions.AutoHide=\"True\" RadiusY=\"20\" RadiusX=\"20\"/>\n        <Border BorderBrush=\"Black\" BorderThickness=\"1\" Height=\"Auto\" Canvas.Left=\"250\" Canvas.Top=\"75\" Width=\"270\" Background=\"{StaticResource BrushWindowBackground}\" IsHitTestVisible=\"False\"\n                local:CanvasExtensions.ScreenBounds=\"{Binding MarkerSwapWarning}\" local:CanvasExtensions.AutoHide=\"True\">\n            <Grid Margin=\"5\">\n                <Grid.ColumnDefinitions>\n                    <ColumnDefinition Width=\"Auto\"/>\n                    <ColumnDefinition/>\n                </Grid.ColumnDefinitions>\n                <Image Width=\"40\" Height=\"40\" Source=\"{Binding Converter={StaticResource convIcon}, ConverterParameter=Warning}\"/>\n                <TextBlock Grid.Column=\"1\" Text=\"{Binding OverlayForm_DeckInfo_Mismatch}\" TextWrapping=\"Wrap\" Margin=\"10,0,0,0\"  />\n            </Grid>\n        </Border>\n        <Grid Height=\"100\" Canvas.Left=\"545\" Canvas.Top=\"19\" Width=\"100\" IsHitTestVisible=\"False\"\n              local:CanvasExtensions.ScreenBounds=\"{Binding MarkerSwapCard}\" local:CanvasExtensions.AutoHide=\"True\">\n            <Grid.RowDefinitions>\n                <RowDefinition Height=\"1*\"/>\n                <RowDefinition Height=\"Auto\"/>\n                <RowDefinition Height=\"2*\"/>\n            </Grid.RowDefinitions>\n            <Border Grid.Row=\"1\" Background=\"LightCoral\"  >\n                <TextBlock Text=\"{Binding OverlayForm_CardInfo_Swapped}\" HorizontalAlignment=\"Center\" FontWeight=\"Bold\"  />\n            </Border>\n        </Grid>\n        <Border x:Name=\"panelDetails\" BorderBrush=\"{StaticResource brushBorder}\" BorderThickness=\"1\" Height=\"Auto\" Canvas.Left=\"10\" Canvas.Top=\"140\" Width=\"Auto\" Background=\"{StaticResource BrushWindowBackground}\" Visibility=\"{Binding ShowDetailsDeck, Converter={StaticResource convVis}}\" IsHitTestVisible=\"False\" SizeChanged=\"panelDetails_SizeChanged\" IsVisibleChanged=\"panelDetails_IsVisibleChanged\">\n            <Grid Margin=\"5\">\n                <Grid.ColumnDefinitions>\n                    <ColumnDefinition Width=\"Auto\"/>\n                    <ColumnDefinition Width=\"10\"/>\n                    <ColumnDefinition Width=\"Auto\"/>\n                </Grid.ColumnDefinitions>\n                <Grid Grid.Column=\"0\">\n                    <Grid.RowDefinitions>\n                        <RowDefinition Height=\"Auto\"/>\n                        <RowDefinition/>\n                        <RowDefinition Height=\"Auto\"/>\n                        <RowDefinition Height=\"Auto\"/>\n                        <RowDefinition Height=\"Auto\"/>\n                        <RowDefinition Height=\"Auto\"/>\n                    </Grid.RowDefinitions>\n                    <TextBlock Text=\"{Binding DescScanId}\" />\n                    <TextBlock Grid.Row=\"2\" Text=\"{Binding OverlayForm_Details_RedDeck}\" />\n                    <TextBlock Grid.Row=\"3\" Margin=\"20,0\" Text=\"{Binding DescRedPlaced}\" />\n                    <TextBlock Grid.Row=\"4\" Margin=\"20,0\" Text=\"{Binding DescRedVarPlaced}\" />\n                    <Border Grid.Row=\"5\" BorderBrush=\"{StaticResource brushBorder}\" Background=\"LightCoral\" BorderThickness=\"1\" Margin=\"0,5,0,0\" >\n                        <Grid>\n                            <Grid.RowDefinitions>\n                                <RowDefinition/>\n                                <RowDefinition/>\n                            </Grid.RowDefinitions>\n                            <!-- width = image(margin + width + margin) * 5; height = image(margin + height + margin) -->\n                            <ItemsControl Grid.Row=\"0\" ItemsSource=\"{Binding RedKnownCards.Cards}\" MinWidth=\"170\" MinHeight=\"34\">\n                                <ItemsControl.ItemsPanel>\n                                    <ItemsPanelTemplate>\n                                        <UniformGrid Rows=\"1\" Columns=\"5\" Margin=\"2\"/>\n                                    </ItemsPanelTemplate>\n                                </ItemsControl.ItemsPanel>\n                                <ItemsControl.ItemTemplate>\n                                    <DataTemplate>\n                                        <Image Width=\"32\" Height=\"32\" Margin=\"1\" Source=\"{Binding CardImage}\" Style=\"{StaticResource listCard}\"/>\n                                    </DataTemplate>\n                                </ItemsControl.ItemTemplate>\n                            </ItemsControl>\n                            <ItemsControl Grid.Row=\"1\" ItemsSource=\"{Binding RedUnknownCards.Cards}\" MinWidth=\"170\" MinHeight=\"34\">\n                                <ItemsControl.ItemsPanel>\n                                    <ItemsPanelTemplate>\n                                        <UniformGrid Rows=\"1\" Columns=\"5\" Margin=\"2\"/>\n                                    </ItemsPanelTemplate>\n                                </ItemsControl.ItemsPanel>\n                                <ItemsControl.ItemTemplate>\n                                    <DataTemplate>\n                                        <Image Width=\"32\" Height=\"32\" Margin=\"1\" Source=\"{Binding CardImage}\" Style=\"{StaticResource listCard}\"/>\n                                    </DataTemplate>\n                                </ItemsControl.ItemTemplate>\n                            </ItemsControl>\n                        </Grid>\n                    </Border>\n                </Grid>\n                <Grid Grid.Column=\"2\">\n                    <Grid.RowDefinitions>\n                        <RowDefinition Height=\"Auto\"/>\n                        <RowDefinition Height=\"Auto\"/>\n                        <RowDefinition Height=\"Auto\"/>\n                        <RowDefinition/>\n                        <RowDefinition  Height=\"Auto\"/>\n                    </Grid.RowDefinitions>\n                    <Rectangle Fill=\"#FF0096A6\" Grid.Row=\"0\" />\n                    <Rectangle Fill=\"LightCoral\" Grid.Row=\"4\" />\n                    <local:DeckView DataContext=\"{Binding BlueDeck}\" Grid.Row=\"0\" />\n                    <local:DeckView DataContext=\"{Binding RedDeck}\" Grid.Row=\"4\" />\n                    <TextBlock Text=\"{Binding DescNpc}\" Grid.Row=\"1\" />\n                    <TextBlock Text=\"{Binding DescRules}\" Grid.Row=\"2\" />\n                    <TextBlock Text=\"{Binding OverlayForm_Details_RedInfo}\" Grid.Row=\"3\" VerticalAlignment=\"Bottom\" />\n                </Grid>\n            </Grid>\n        </Border>\n        <Border x:Name=\"panelBoard\" Height=\"Auto\" Canvas.Left=\"497\" Canvas.Top=\"142\" Width=\"Auto\" Background=\"AntiqueWhite\" Visibility=\"{Binding ShowDetailsBoard, Converter={StaticResource convVis}}\" IsHitTestVisible=\"False\" IsVisibleChanged=\"panelBoard_IsVisibleChanged\" BorderBrush=\"{StaticResource brushBorder}\" BorderThickness=\"1\">\n            <!-- width & height = image(margin + width + margin) * 3 -->\n            <ItemsControl ItemsSource=\"{Binding Board}\" MinWidth=\"156\" MinHeight=\"156\">\n                <ItemsControl.ItemsPanel>\n                    <ItemsPanelTemplate>\n                        <UniformGrid Rows=\"3\" Columns=\"3\"/>\n                    </ItemsPanelTemplate>\n                </ItemsControl.ItemsPanel>\n                <ItemsControl.ItemTemplate>\n                    <DataTemplate>\n                        <Border Width=\"50\" Height=\"50\" Margin=\"1\" Background=\"White\" BorderBrush=\"{StaticResource brushBorder}\" BorderThickness=\"1\">\n                            <Grid>\n                                <local:CardView DataContext=\"{Binding Card}\" Style=\"{StaticResource boardCard}\"/>\n                                <TextBlock Text=\"{Binding CactpotNum}\" FontSize=\"30\" HorizontalAlignment=\"Center\" VerticalAlignment=\"Center\" Style=\"{StaticResource boardText}\"/>\n                            </Grid>\n                        </Border>\n                    </DataTemplate>\n                </ItemsControl.ItemTemplate>\n            </ItemsControl>\n        </Border>\n    </Canvas>\n</Window>\n"
  },
  {
    "path": "sources/ui/view/OverlayWindowTransparent.xaml.cs",
    "content": "﻿using System.Windows;\nusing System.Windows.Controls;\nusing System.Windows.Interop;\n\nnamespace FFTriadBuddy.UI\n{\n    /// <summary>\n    /// Interaction logic for OverlayWindowTransparent.xaml\n    /// </summary>\n    public partial class OverlayWindowTransparent : Window\n    {\n        public FrameworkElement panelCapture;\n\n        public OverlayWindowTransparent()\n        {\n            InitializeComponent();\n        }\n\n        public void SetOverlayActive(bool wantsActive)\n        {\n            Visibility = wantsActive ? Visibility.Visible : Visibility.Hidden;\n        }\n\n        private void Window_Loaded(object sender, RoutedEventArgs e)\n        {\n            var helper = new WindowInteropHelper(this).Handle;\n            var wndStyleEx = (OverlayWindowInteractive.GetWindowLong(helper, OverlayWindowInteractive.GWL_EX_STYLE) | OverlayWindowInteractive.WS_EX_TOOLWINDOW | OverlayWindowInteractive.WS_EX_TRANSPARENT) & ~OverlayWindowInteractive.WS_EX_APPWINDOW;\n            OverlayWindowInteractive.SetWindowLong(helper, OverlayWindowInteractive.GWL_EX_STYLE, wndStyleEx);\n        }\n\n        public void SetDetailsCanvasPos(double x = -1, double y = -1)\n        {\n            var clampedX = (x < 0) ? Canvas.GetLeft(panelCapture) : x;\n            var clampedY = (y < 0) ? Canvas.GetTop(panelCapture) : y;\n\n            if (panelDetails.ActualWidth > 0)\n            {\n                Canvas.SetLeft(panelDetails, clampedX - panelDetails.ActualWidth - 10);\n                Canvas.SetTop(panelDetails, clampedY);\n            }\n\n            if (panelCapture.ActualWidth > 0)\n            {\n                Canvas.SetLeft(panelBoard, clampedX + panelCapture.ActualWidth + 10);\n                Canvas.SetTop(panelBoard, clampedY);\n            }\n        }\n\n        private void panelDetails_SizeChanged(object sender, SizeChangedEventArgs e)\n        {\n            SetDetailsCanvasPos();\n        }\n\n        private void panelDetails_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)\n        {\n            if ((bool)e.NewValue)\n            {\n                SetDetailsCanvasPos();\n            }\n        }\n\n        private void panelBoard_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)\n        {\n            if ((bool)e.NewValue)\n            {\n                SetDetailsCanvasPos();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/PageCards.xaml",
    "content": "﻿<UserControl x:Class=\"FFTriadBuddy.UI.PageCards\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \n             xmlns:i=\"http://schemas.microsoft.com/expression/2010/interactivity\"\n             xmlns:local=\"clr-namespace:FFTriadBuddy.UI\"\n             mc:Ignorable=\"d\" \n             d:DataContext=\"{d:DesignInstance Type=local:PageCardsViewModel, IsDesignTimeCreatable=True}\"\n             d:DesignHeight=\"450\" d:DesignWidth=\"800\">\n    <TabControl Style=\"{StaticResource tabControlFlat}\">\n        <TabItem Header=\"{Binding MainForm_Cards_ListTitle}\" PreviewKeyDown=\"TabItem_PreviewKeyDown\">\n            <Grid Background=\"{StaticResource BrushWindowBackground}\">\n                <Grid.RowDefinitions>\n                    <RowDefinition/>\n                    <RowDefinition Height=\"Auto\"/>\n                </Grid.RowDefinitions>\n                <ListView x:Name=\"listCards\" Margin=\"3\"\n                    ItemsSource=\"{Binding Cards}\" VirtualizingPanel.VirtualizationMode=\"Recycling\"\n                    local:ListViewExtensions.EnableSortOnClick=\"True\" local:ListViewExtensions.SyncScrollItem=\"{Binding FoundItem, Mode=OneWay}\" ContextMenuOpening=\"listCards_ContextMenuOpening\">\n                    <ListView.Resources>\n                        <Style TargetType=\"{x:Type ListViewItem}\">\n                            <Style.Triggers>\n                                <DataTrigger Binding=\"{Binding IsOwned}\" Value=\"True\">\n                                    <Setter Property=\"Background\" Value=\"#B8FCD2\"/>\n                                </DataTrigger>\n                                <MultiDataTrigger>\n                                    <MultiDataTrigger.Conditions>\n                                        <Condition Binding=\"{Binding IsOwned}\" Value=\"True\"/>\n                                        <Condition Binding=\"{Binding IsSelected, RelativeSource={RelativeSource Self}}\" Value=\"True\"/>\n                                    </MultiDataTrigger.Conditions>\n                                    <Setter Property=\"Background\" Value=\"#5CF898\"/>\n                                </MultiDataTrigger>\n                            </Style.Triggers>\n                        </Style>\n                        <ContextMenu x:Key=\"searchCtx\">\n                            <MenuItem>\n                                <MenuItem.Header>\n                                    <StackPanel Orientation=\"Horizontal\">\n                                        <TextBlock Text=\"{Binding MainForm_CtxMenu_FindCard}\"/>\n                                        <TextBox x:Name=\"searchTextBox\" Width=\"200\" Margin=\"10,0,0,0\" TextChanged=\"searchTextBox_TextChanged\"/>\n                                    </StackPanel>\n                                </MenuItem.Header>\n                            </MenuItem>\n                        </ContextMenu>\n                    </ListView.Resources>\n                    <ListView.ContextMenu>\n                        <ContextMenu ItemsSource=\"{Binding ContextActions}\" ItemContainerStyle=\"{StaticResource ctxActionStyle}\" Tag=\"{Binding PlacementTarget.SelectedItem, RelativeSource={RelativeSource Self}}\"/>\n                    </ListView.ContextMenu>\n                    <ListView.View>\n                        <GridView AllowsColumnReorder=\"False\">\n                            <GridViewColumn Header=\"{Binding DataContext.MainForm_Cards_List_ColumnOwned, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}\" local:ListViewExtensions.ColumnSortProperty=\"IsOwned\">\n                                <GridViewColumn.CellTemplate>\n                                    <DataTemplate>\n                                        <CheckBox IsChecked=\"{Binding IsOwned, Mode=OneWay}\" Command=\"{Binding DataContext.CommandToggleOwnedCard, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:PageCards}}}\" CommandParameter=\"{Binding}\"/>\n                                    </DataTemplate>\n                                </GridViewColumn.CellTemplate>\n                            </GridViewColumn>\n                            <GridViewColumn Header=\"{Binding DataContext.MainForm_Cards_List_ColumnName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}\" DisplayMemberBinding=\"{Binding NameLocalized}\" Width=\"200\"/>\n                            <GridViewColumn Header=\"{Binding DataContext.MainForm_Cards_List_ColumnPower, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}\" DisplayMemberBinding=\"{Binding DescPower}\" Width=\"60\"/>\n                            <GridViewColumn Header=\"{Binding DataContext.MainForm_Cards_List_ColumnRarity, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}\" DisplayMemberBinding=\"{Binding DescRarity}\" Width=\"60\"/>\n                            <GridViewColumn Header=\"{Binding DataContext.MainForm_Cards_List_ColumnType, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}\" DisplayMemberBinding=\"{Binding DescCardType}\" Width=\"100\"/>\n                            <GridViewColumn Header=\"{Binding DataContext.MainForm_Cards_List_ColumnId, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}\" DisplayMemberBinding=\"{Binding GameSortOrder}\" Width=\"40\" local:ListViewExtensions.InitSorting=\"ASC\"/>\n                        </GridView>\n                    </ListView.View>\n                </ListView>\n                <StackPanel Orientation=\"Horizontal\" Grid.Row=\"1\" Margin=\"0,0,0,2\">\n                    <TextBlock Text=\"{Binding MainForm_Cards_NumOwned}\" Margin=\"3,0,0,0\"/>\n                    <TextBlock Text=\"{Binding NumOwnedCards}\" Margin=\"3,0,0,0\"/>\n                </StackPanel>\n            </Grid>\n        </TabItem>\n        <TabItem Header=\"{Binding MainForm_Cards_IconsTitle}\">\n            <ScrollViewer>\n                <ItemsControl ItemsSource=\"{Binding GridViews}\" Background=\"{StaticResource BrushWindowBackground}\">\n                    <ItemsControl.ItemsPanel>\n                        <ItemsPanelTemplate>\n                            <WrapPanel/>\n                        </ItemsPanelTemplate>\n                    </ItemsControl.ItemsPanel>\n                    <ItemsControl.ItemTemplate>\n                        <DataTemplate>\n                            <local:CardGridView Height=\"200\" Margin=\"3\"/>\n                        </DataTemplate>\n                    </ItemsControl.ItemTemplate>\n                </ItemsControl>\n            </ScrollViewer>\n        </TabItem>\n    </TabControl>\n</UserControl>\n"
  },
  {
    "path": "sources/ui/view/PageCards.xaml.cs",
    "content": "﻿using System.Windows.Controls;\nusing System.Windows.Controls.Primitives;\nusing System.Windows.Input;\n\nnamespace FFTriadBuddy.UI\n{\n    /// <summary>\n    /// Interaction logic for PageCards.xaml\n    /// </summary>\n    public partial class PageCards : UserControl\n    {\n        public PageCards()\n        {\n            InitializeComponent();\n        }\n\n        private void TabItem_PreviewKeyDown(object sender, KeyEventArgs e)\n        {\n            if (e.Key == Key.F && Keyboard.Modifiers == ModifierKeys.Control)\n            {\n                var findCtxMenu = listCards.FindResource(\"searchCtx\") as ContextMenu;\n                if (findCtxMenu != null)\n                {\n                    const int PlacementPadding = 10;\n                    findCtxMenu.Placement = PlacementMode.RelativePoint;\n                    findCtxMenu.PlacementTarget = listCards;\n                    findCtxMenu.VerticalOffset = PlacementPadding;\n                    findCtxMenu.IsOpen = true;\n\n                    findCtxMenu.HorizontalOffset = listCards.ActualWidth - findCtxMenu.ActualWidth - PlacementPadding;\n\n                    var textBox = ViewUtils.FindVisualChildRecursive(findCtxMenu, x => x is TextBox) as TextBox;\n                    textBox?.Focus();\n                    textBox?.SelectAll();\n                }\n            }\n        }\n\n        private void searchTextBox_TextChanged(object sender, TextChangedEventArgs e)\n        {\n            var textBox = sender as TextBox;\n            var pageVM = DataContext as PageCardsViewModel;\n\n            if (pageVM.CommandSearchCard.CanExecute(textBox.Text))\n            {\n                pageVM.CommandSearchCard.Execute(textBox.Text);\n            }\n        }\n\n        private void listCards_ContextMenuOpening(object sender, ContextMenuEventArgs e)\n        {\n            var listView = e.Source as ListView;\n            var pageVM = DataContext as PageCardsViewModel;\n            var paramOb = (listView.ContextMenu.Tag ?? listView.SelectedItem) as CardModelProxy;\n\n            if (pageVM.CommandBuildContextActions.CanExecute(paramOb))\n            {\n                pageVM.CommandBuildContextActions.Execute(paramOb);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/PageInfo.xaml",
    "content": "﻿<UserControl x:Class=\"FFTriadBuddy.UI.PageInfo\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \n             xmlns:local=\"clr-namespace:FFTriadBuddy.UI\"\n             mc:Ignorable=\"d\" \n             d:DataContext=\"{d:DesignInstance Type=local:PageInfoViewModel, IsDesignTimeCreatable=True}\"\n             d:DesignHeight=\"450\" d:DesignWidth=\"800\" Background=\"{StaticResource BrushWindowBackground}\">\n    <UserControl.Resources>\n        <Style x:Key=\"settingText\" TargetType=\"{x:Type TextBox}\">\n            <Style.Triggers>\n                <Trigger Property=\"Validation.HasError\" Value=\"true\">\n                    <Setter Property=\"ToolTip\" Value=\"{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}\"/>\n                </Trigger>\n            </Style.Triggers>\n        </Style>\n    </UserControl.Resources>\n    <Grid Margin=\"15\">\n        <Grid.ColumnDefinitions>\n            <ColumnDefinition Width=\"Auto\"/>\n            <ColumnDefinition Width=\"5\"/>\n            <ColumnDefinition Width=\"1*\"/>\n        </Grid.ColumnDefinitions>\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition Height=\"5\"/>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition Height=\"30\"/>\n            <RowDefinition Height=\"1*\"/>\n        </Grid.RowDefinitions>\n        <TextBlock Grid.Column=\"0\" Grid.Row=\"0\" Text=\"{Binding MainForm_Info_HomePage}\"/>\n        <TextBlock Grid.Column=\"2\" Grid.Row=\"0\">\n            <Hyperlink NavigateUri=\"https://github.com/MgAl2O4/FFTriadBuddy\" RequestNavigate=\"Hyperlink_RequestNavigate\">\n                <Run Text=\"https://github.com/MgAl2O4/FFTriadBuddy\"/>\n            </Hyperlink>\n        </TextBlock>\n        <TextBlock Grid.Column=\"0\" Grid.Row=\"2\" Text=\"{Binding MainForm_Info_BugReports}\"/>\n        <TextBlock Grid.Column=\"2\" Grid.Row=\"2\">\n            <Hyperlink NavigateUri=\"https://github.com/MgAl2O4/FFTriadBuddy/issues/new\" RequestNavigate=\"Hyperlink_RequestNavigate\">\n                <Run Text=\"https://github.com/MgAl2O4/FFTriadBuddy/issues/new\"/>\n            </Hyperlink>\n        </TextBlock>\n        <TextBlock Grid.Column=\"0\" Grid.Row=\"3\" Text=\" \"/>\n        <TextBlock Grid.Column=\"0\" Grid.Row=\"4\" Text=\"{Binding MainForm_Info_Localization}\"/>\n        <TextBlock Grid.Column=\"2\" Grid.Row=\"4\">\n            <Hyperlink NavigateUri=\"https://crowdin.com/project/fftriadbuddy\" RequestNavigate=\"Hyperlink_RequestNavigate\">\n                <Run Text=\"https://crowdin.com/project/fftriadbuddy\"/>\n            </Hyperlink>\n        </TextBlock>\n        <TextBlock Grid.Column=\"2\" Grid.Row=\"5\" Text=\"{Binding MainForm_Info_TranslatorLove}\"/>\n        <TextBlock Grid.Column=\"2\" Grid.Row=\"6\" Text=\"{Binding MainForm_Info_TranslatorNeeded}\"/>\n        <Grid Grid.Column=\"0\" Grid.ColumnSpan=\"3\" Grid.Row=\"8\">\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition/>\n                <ColumnDefinition Width=\"50\"/>\n                <ColumnDefinition Width=\"Auto\"/>\n            </Grid.ColumnDefinitions>\n            <GroupBox Grid.Column=\"0\" Header=\"{Binding Settings_Title}\" VerticalAlignment=\"Top\">\n                <ScrollViewer>\n                    <Grid>\n                        <Grid.RowDefinitions>\n                            <RowDefinition Height=\"Auto\"/>\n                            <RowDefinition Height=\"Auto\"/>\n                            <RowDefinition Height=\"Auto\"/>\n                            <RowDefinition Height=\"10\"/>\n                            <RowDefinition Height=\"Auto\"/>\n                            <RowDefinition Height=\"Auto\"/>\n                            <RowDefinition Height=\"Auto\"/>\n                            <RowDefinition Height=\"Auto\"/>\n                            <RowDefinition Height=\"Auto\"/>\n                        </Grid.RowDefinitions>\n                        <Grid.ColumnDefinitions>\n                            <ColumnDefinition Width=\"Auto\"/>\n                            <ColumnDefinition Width=\"5\"/>\n                            <ColumnDefinition MinWidth=\"60\"/>\n                            <ColumnDefinition Width=\"10*\"/>\n                        </Grid.ColumnDefinitions>\n\n                        <TextBlock Grid.Column=\"0\" Grid.Row=\"0\" Text=\"{Binding Settings_FontSize}\" HorizontalAlignment=\"Right\"/>\n                        <TextBox Grid.Column=\"2\" Grid.Row=\"0\" Text=\"{Binding ValueFontSize, ValidatesOnDataErrors=True, ValidatesOnExceptions=True, UpdateSourceTrigger=LostFocus}\" Style=\"{StaticResource settingText}\"/>\n\n                        <TextBlock Grid.Column=\"0\" Grid.Row=\"1\" Text=\"{Binding Settings_AlwaysOnTop}\" HorizontalAlignment=\"Right\"/>\n                        <CheckBox Grid.Column=\"2\" Grid.Row=\"1\" IsChecked=\"{Binding ValueAlwaysOnTop}\" VerticalAlignment=\"Center\"/>\n\n                        <TextBlock Grid.Column=\"0\" Grid.Row=\"2\" Text=\"{Binding Settings_DisableHardwareAcceleration}\" HorizontalAlignment=\"Right\"/>\n                        <CheckBox Grid.Column=\"2\" Grid.Row=\"2\" IsChecked=\"{Binding ValueDisableHardwareAcceleration}\" VerticalAlignment=\"Center\"/>\n\n                        <TextBlock Grid.Column=\"0\" Grid.Row=\"4\" Text=\"{Binding Settings_MarkerDurationCard}\" HorizontalAlignment=\"Right\"/>\n                        <TextBox Grid.Column=\"2\" Grid.Row=\"4\" Text=\"{Binding ValueMarkerCard, ValidatesOnDataErrors=True, ValidatesOnExceptions=True, UpdateSourceTrigger=LostFocus}\" Style=\"{StaticResource settingText}\"/>\n\n                        <TextBlock Grid.Column=\"0\" Grid.Row=\"5\" Text=\"{Binding Settings_MarkerDurationSwap}\" HorizontalAlignment=\"Right\"/>\n                        <TextBox Grid.Column=\"2\" Grid.Row=\"5\" Text=\"{Binding ValueMarkerSwap, ValidatesOnDataErrors=True, ValidatesOnExceptions=True, UpdateSourceTrigger=LostFocus}\" Style=\"{StaticResource settingText}\"/>\n\n                        <TextBlock Grid.Column=\"0\" Grid.Row=\"6\" Text=\"{Binding Settings_MarkerDurationCactpot}\" HorizontalAlignment=\"Right\"/>\n                        <TextBox Grid.Column=\"2\" Grid.Row=\"6\" Text=\"{Binding ValueMarkerCactpot, ValidatesOnDataErrors=True, ValidatesOnExceptions=True, UpdateSourceTrigger=LostFocus}\" Style=\"{StaticResource settingText}\"/>\n\n                        <TextBlock Grid.Column=\"0\" Grid.Row=\"7\" Text=\"{Binding Settings_SkipOptionalSimulateRules}\" HorizontalAlignment=\"Right\"/>\n                        <CheckBox Grid.Column=\"2\" Grid.Row=\"7\" IsChecked=\"{Binding ValueSkipOptionalRules}\" VerticalAlignment=\"Center\"/>\n\n                        <TextBlock Grid.Column=\"0\" Grid.Row=\"8\" Text=\"{Binding Settings_AlwaysSmallIcons}\" HorizontalAlignment=\"Right\"/>\n                        <CheckBox Grid.Column=\"2\" Grid.Row=\"8\" IsChecked=\"{Binding ValueUseSmallIcons}\" VerticalAlignment=\"Center\"/>\n                    </Grid>\n                </ScrollViewer>\n            </GroupBox>\n            <GroupBox Grid.Column=\"2\" Header=\"{Binding LocalSaves.LocalSaves_Title}\" HorizontalAlignment=\"Right\" VerticalAlignment=\"Top\">\n                <StackPanel Margin=\"10,0,10,0\">\n                    <Button Grid.Row=\"1\" Content=\"{Binding LocalSaves.LocalSaves_Export}\" Margin=\"5\" Padding=\"50,0\" Command=\"{Binding LocalSaves.CommandExport}\"/>\n                    <Button Grid.Row=\"1\" Content=\"{Binding LocalSaves.LocalSaves_Import}\" Margin=\"5\" Padding=\"50,0\" Command=\"{Binding LocalSaves.CommandImport}\"/>\n                    <Button Grid.Row=\"1\" Content=\"{Binding LocalSaves.LocalSaves_ShowBackupFolder}\" Margin=\"5\" Padding=\"50,0\" Command=\"{Binding LocalSaves.CommandViewBackups}\"/>\n                </StackPanel>\n            </GroupBox>\n        </Grid>\n    </Grid>\n</UserControl>\n"
  },
  {
    "path": "sources/ui/view/PageInfo.xaml.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Windows.Controls;\nusing System.Windows.Navigation;\n\nnamespace FFTriadBuddy.UI\n{\n    /// <summary>\n    /// Interaction logic for PageInfo.xaml\n    /// </summary>\n    public partial class PageInfo : UserControl\n    {\n        public PageInfo()\n        {\n            InitializeComponent();\n        }\n\n        private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)\n        {\n            Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri) { UseShellExecute = true });\n            e.Handled = true;\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/PageNpcs.xaml",
    "content": "﻿<UserControl x:Class=\"FFTriadBuddy.UI.PageNpcs\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \n             xmlns:local=\"clr-namespace:FFTriadBuddy.UI\"\n             mc:Ignorable=\"d\" \n             d:DataContext=\"{d:DesignInstance Type=local:PageNpcsViewModel, IsDesignTimeCreatable=True}\"\n             d:DesignHeight=\"450\" d:DesignWidth=\"800\" PreviewKeyDown=\"UserControl_PreviewKeyDown\"\n             Background=\"{StaticResource BrushWindowBackground}\">\n    <Grid Margin=\"1,1,1,1\">\n        <Grid.RowDefinitions>\n            <RowDefinition/>\n            <RowDefinition Height=\"Auto\"/>\n        </Grid.RowDefinitions>\n        <ListView x:Name=\"listNpcs\" Margin=\"3\"\n                    ItemsSource=\"{Binding Npcs}\" VirtualizingPanel.VirtualizationMode=\"Recycling\"\n                    local:ListViewExtensions.EnableSortOnClick=\"True\" local:ListViewExtensions.SyncScrollItem=\"{Binding FoundItem, Mode=OneWay}\" ContextMenuOpening=\"listNpcs_ContextMenuOpening\">\n            <ListView.Resources>\n                <Style TargetType=\"{x:Type ListViewItem}\">\n                    <Style.Triggers>\n                        <DataTrigger Binding=\"{Binding IsCompleted}\" Value=\"True\">\n                            <Setter Property=\"Background\" Value=\"#B8FCD2\"/>\n                        </DataTrigger>\n                        <MultiDataTrigger>\n                            <MultiDataTrigger.Conditions>\n                                <Condition Binding=\"{Binding IsCompleted}\" Value=\"True\"/>\n                                <Condition Binding=\"{Binding IsSelected, RelativeSource={RelativeSource Self}}\" Value=\"True\"/>\n                            </MultiDataTrigger.Conditions>\n                            <Setter Property=\"Background\" Value=\"#5CF898\"/>\n                        </MultiDataTrigger>\n                    </Style.Triggers>\n                </Style>\n                <ContextMenu x:Key=\"searchCtx\">\n                    <MenuItem>\n                        <MenuItem.Header>\n                            <StackPanel Orientation=\"Horizontal\">\n                                <TextBlock Text=\"{Binding MainForm_CtxMenu_FindNpc}\"/>\n                                <TextBox x:Name=\"searchTextBox\" Width=\"200\" Margin=\"10,0,0,0\" TextChanged=\"searchTextBox_TextChanged\"/>\n                            </StackPanel>\n                        </MenuItem.Header>\n                    </MenuItem>\n                </ContextMenu>\n            </ListView.Resources>\n            <ListView.ContextMenu>\n                <ContextMenu ItemsSource=\"{Binding ContextActions}\" ItemContainerStyle=\"{StaticResource ctxActionStyle}\" Tag=\"{Binding PlacementTarget.SelectedItem, RelativeSource={RelativeSource Self}}\"/>\n            </ListView.ContextMenu>\n            <ListView.View>\n                <GridView AllowsColumnReorder=\"False\">\n                    <GridViewColumn Header=\"{Binding DataContext.MainForm_Npcs_List_ColumnName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}\" DisplayMemberBinding=\"{Binding NameLocalized}\" Width=\"200\"/>\n                    <GridViewColumn Header=\"{Binding DataContext.MainForm_Npcs_List_ColumnPower, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}\" DisplayMemberBinding=\"{Binding DescPower}\" Width=\"40\"/>\n                    <GridViewColumn Header=\"{Binding DataContext.MainForm_Npcs_List_ColumnLocation, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}\" DisplayMemberBinding=\"{Binding LocationLocalized}\" Width=\"160\"/>\n                    <GridViewColumn Header=\"{Binding DataContext.MainForm_Npcs_List_ColumnRules, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}\" DisplayMemberBinding=\"{Binding DescRules}\" Width=\"160\"/>\n                    <GridViewColumn Header=\"{Binding DataContext.MainForm_Npcs_List_ColumnCompleted, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}\" DisplayMemberBinding=\"{Binding DescCompleted}\"/>\n                    <GridViewColumn Header=\"{Binding DataContext.MainForm_Npcs_List_ColumnReward, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}\" DisplayMemberBinding=\"{Binding DescReward}\" Width=\"160\"/>\n                </GridView>\n            </ListView.View>\n        </ListView>\n        <StackPanel Orientation=\"Horizontal\" Grid.Row=\"1\" Margin=\"0,0,0,2\">\n            <TextBlock Text=\"{Binding MainForm_Npcs_NumKnown}\" Margin=\"3,0,0,0\"/>\n            <TextBlock Text=\"{Binding RemainingNpcWithReward}\" Margin=\"3,0,0,0\"/>\n        </StackPanel>\n    </Grid>\n</UserControl>\n"
  },
  {
    "path": "sources/ui/view/PageNpcs.xaml.cs",
    "content": "﻿using System.Windows.Controls;\nusing System.Windows.Controls.Primitives;\nusing System.Windows.Input;\n\nnamespace FFTriadBuddy.UI\n{\n    /// <summary>\n    /// Interaction logic for PageNpcs.xaml\n    /// </summary>\n    public partial class PageNpcs : UserControl\n    {\n        public PageNpcs()\n        {\n            InitializeComponent();\n        }\n\n        private void UserControl_PreviewKeyDown(object sender, KeyEventArgs e)\n        {\n            if (e.Key == Key.F && Keyboard.Modifiers == ModifierKeys.Control)\n            {\n                var findCtxMenu = listNpcs.FindResource(\"searchCtx\") as ContextMenu;\n                if (findCtxMenu != null)\n                {\n                    const int PlacementPadding = 10;\n                    findCtxMenu.Placement = PlacementMode.RelativePoint;\n                    findCtxMenu.PlacementTarget = listNpcs;\n                    findCtxMenu.VerticalOffset = PlacementPadding;\n                    findCtxMenu.IsOpen = true;\n\n                    findCtxMenu.HorizontalOffset = listNpcs.ActualWidth - findCtxMenu.ActualWidth - PlacementPadding;\n\n                    var textBox = ViewUtils.FindVisualChildRecursive(findCtxMenu, x => x is TextBox) as TextBox;\n                    textBox?.Focus();\n                    textBox?.SelectAll();\n                }\n            }\n        }\n\n        private void searchTextBox_TextChanged(object sender, TextChangedEventArgs e)\n        {\n            var textBox = sender as TextBox;\n            var pageVM = DataContext as PageNpcsViewModel;\n\n            if (pageVM.CommandSearchNpc.CanExecute(textBox.Text))\n            {\n                pageVM.CommandSearchNpc.Execute(textBox.Text);\n            }\n        }\n\n        private void listNpcs_ContextMenuOpening(object sender, ContextMenuEventArgs e)\n        {\n            var listView = e.Source as ListView;\n            var pageVM = DataContext as PageNpcsViewModel;\n            var paramOb = (listView.ContextMenu.Tag ?? listView.SelectedItem) as NpcModelProxy;\n\n            if (pageVM.CommandBuildContextActions.CanExecute(paramOb))\n            {\n                pageVM.CommandBuildContextActions.Execute(paramOb);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/PageScreenshot.xaml",
    "content": "﻿<UserControl x:Class=\"FFTriadBuddy.UI.PageScreenshot\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \n             xmlns:local=\"clr-namespace:FFTriadBuddy.UI\"\n             mc:Ignorable=\"d\" \n             d:DataContext=\"{d:DesignInstance Type=local:PageScreenshotViewModel, IsDesignTimeCreatable=True}\"\n             d:DesignHeight=\"450\" d:DesignWidth=\"800\" Background=\"{StaticResource BrushWindowBackground}\">\n    <Grid Margin=\"5\">\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition/>\n            <RowDefinition Height=\"Auto\"/>\n        </Grid.RowDefinitions>\n        <Border BorderBrush=\"{StaticResource brushBorder}\" BorderThickness=\"1\" PreviewMouseLeftButtonDown=\"Border_PreviewMouseLeftButtonDown\">\n            <Border.Style>\n                <Style TargetType=\"{x:Type Border}\">\n                    <Setter Property=\"Background\" Value=\"Transparent\"/>\n                    <Style.Triggers>\n                        <DataTrigger Binding=\"{Binding CurrentAnalyzerState}\" Value=\"Error\">\n                            <Setter Property=\"Background\" Value=\"#FFFA8795\"/>\n                        </DataTrigger>\n                        <DataTrigger Binding=\"{Binding CurrentAnalyzerState}\" Value=\"Warning\">\n                            <Setter Property=\"Background\" Value=\"#FFFAEC87\"/>\n                        </DataTrigger>\n                        <DataTrigger Binding=\"{Binding CurrentAnalyzerState}\" Value=\"Active\">\n                            <Setter Property=\"Background\" Value=\"#FF95FA87\"/>\n                        </DataTrigger>\n                    </Style.Triggers>\n                </Style>\n            </Border.Style>\n            <StackPanel x:Name=\"overlayState\" Orientation=\"Horizontal\" Margin=\"5\" VerticalAlignment=\"Center\">\n                <Viewbox Height=\"{Binding Path=ActualHeight, ElementName=overlayState}\">\n                    <CheckBox x:Name=\"checkToggleOverlay\" Command=\"{Binding CommandToggleOverlay}\" IsChecked=\"{Binding IsOverlayActive}\" IsHitTestVisible=\"False\"/>\n                </Viewbox>\n                <TextBlock Text=\"{Binding MainForm_Screenshot_CurrentState}\" Margin=\"5,-1,0,0\"/>\n                <TextBlock Text=\"{Binding DescAnalyzerState}\" Margin=\"5,-1,0,0\"/>\n            </StackPanel>\n        </Border>\n        <Grid Grid.Row=\"2\">\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition Width=\"Auto\"/>\n                <ColumnDefinition/>\n                <ColumnDefinition Width=\"Auto\"/>\n            </Grid.ColumnDefinitions>\n            <TextBlock Grid.Column=\"0\" Text=\"{Binding MainForm_Screenshot_ListHint}\">\n                <TextBlock.Style>\n                    <Style TargetType=\"{x:Type TextBlock}\">\n                        <Setter Property=\"Visibility\" Value=\"Hidden\"/>\n                        <Style.Triggers>\n                            <DataTrigger Binding=\"{Binding ActiveMode}\" Value=\"Hashes\">\n                                <Setter Property=\"Visibility\" Value=\"Visible\"/>\n                            </DataTrigger>\n                        </Style.Triggers>\n                    </Style>\n                </TextBlock.Style>\n            </TextBlock>\n            <TextBlock Grid.Column=\"1\" Text=\"{Binding MainForm_Screenshot_RemovePatternsTitle}\" HorizontalAlignment=\"Right\" Margin=\"0,0,10,0\"/>\n            <Button Grid.Column=\"2\" Content=\"{Binding MainForm_Screenshot_RemovePatternsButton}\" Padding=\"10,0\" Command=\"{Binding CommandRemoveLocalHashes}\"/>\n        </Grid>\n        <TabControl Grid.Row=\"1\" SelectedIndex=\"{Binding ActiveModeSwitcherIdx, Mode=OneWay}\" Style=\"{StaticResource tabControlSwitcher}\" Margin=\"0,5\">\n            <TabItem>\n                <TextBlock Margin=\"10,0\" Text=\"{Binding MainForm_Screenshot_InfoLines}\"/>\n            </TabItem>\n            <TabItem>\n                <Grid>\n                    <Grid.ColumnDefinitions>\n                        <ColumnDefinition Width=\"1*\"/>\n                        <ColumnDefinition Width=\"10\"/>\n                        <ColumnDefinition Width=\"2*\"/>\n                    </Grid.ColumnDefinitions>\n                    <ListView ItemsSource=\"{Binding Hashes}\" IsEnabled=\"{Binding HasAnyHashes}\" ContextMenuOpening=\"ListView_ContextMenuOpening\"\n                              VirtualizingPanel.IsVirtualizing=\"False\"\n                              local:ListViewExtensions.EnableSortOnClick=\"True\">\n                        <ListView.ContextMenu>\n                            <ContextMenu ItemsSource=\"{Binding ContextActions}\" ItemContainerStyle=\"{StaticResource ctxActionStyle}\" Tag=\"{Binding PlacementTarget.SelectedItem, RelativeSource={RelativeSource Self}}\"/>\n                        </ListView.ContextMenu>\n                        <ListView.View>\n                            <GridView AllowsColumnReorder=\"False\">\n                                <GridViewColumn Header=\"{Binding DataContext.MainForm_Screenshot_History_HashColumnType, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}\" DisplayMemberBinding=\"{Binding DescMatch}\" Width=\"100\"/>\n                                <GridViewColumn Header=\"{Binding DataContext.MainForm_Screenshot_History_HashColumnDetection, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}\" DisplayMemberBinding=\"{Binding NameLocalized}\" Width=\"160\"/>\n                            </GridView>\n                        </ListView.View>\n                    </ListView>\n                    <ListView ItemsSource=\"{Binding Cards}\" IsEnabled=\"{Binding HasAnyCards}\" Grid.Column=\"2\" VirtualizingPanel.IsVirtualizing=\"False\"\n                        local:ListViewExtensions.EnableSortOnClick=\"True\" ContextMenuOpening=\"ListView_ContextMenuOpening\">\n                        <ListView.Resources>\n                            <Style TargetType=\"{x:Type ListViewItem}\">\n                                <Style.Triggers>\n                                    <DataTrigger Binding=\"{Binding IsDetected}\" Value=\"False\">\n                                        <Setter Property=\"Background\" Value=\"#FFFFE4E1\"/>\n                                    </DataTrigger>\n                                </Style.Triggers>\n                            </Style>\n                        </ListView.Resources>\n                        <ListView.ContextMenu>\n                            <ContextMenu ItemsSource=\"{Binding ContextActions}\" ItemContainerStyle=\"{StaticResource ctxActionStyle}\" Tag=\"{Binding PlacementTarget.SelectedItem, RelativeSource={RelativeSource Self}}\"/>\n                        </ListView.ContextMenu>\n                        <ListView.View>\n                            <GridView AllowsColumnReorder=\"False\">\n                                <GridViewColumn Header=\"{Binding DataContext.MainForm_Screenshot_History_CardsColumnType, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}\" DisplayMemberBinding=\"{Binding DescType}\" Width=\"140\"/>\n                                <GridViewColumn Header=\"{Binding DataContext.MainForm_Screenshot_History_CardsColumnSides, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}\" DisplayMemberBinding=\"{Binding DescSides}\" Width=\"120\"/>\n                                <GridViewColumn Header=\"{Binding DataContext.MainForm_Screenshot_History_CardsColumnDetection, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}\" DisplayMemberBinding=\"{Binding NameLocalized}\" Width=\"200\"/>\n                            </GridView>\n                        </ListView.View>\n                    </ListView>\n                </Grid>\n            </TabItem>\n            <TabItem>\n                <Grid>\n                    <Grid.RowDefinitions>\n                        <RowDefinition Height=\"Auto\"/>\n                        <RowDefinition/>\n                    </Grid.RowDefinitions>\n                    <Grid.ColumnDefinitions>\n                        <ColumnDefinition Width=\"1*\"/>\n                        <ColumnDefinition Width=\"10\"/>\n                        <ColumnDefinition Width=\"2*\"/>\n                    </Grid.ColumnDefinitions>\n                    <Grid>\n                        <Grid.RowDefinitions>\n                            <RowDefinition Height=\"Auto\"/>\n                            <RowDefinition Height=\"5\"/>\n                            <RowDefinition Height=\"Auto\"/>\n                            <RowDefinition/>\n                        </Grid.RowDefinitions>\n                        <Grid.ColumnDefinitions>\n                            <ColumnDefinition Width=\"Auto\"/>\n                            <ColumnDefinition Width=\"10\"/>\n                            <ColumnDefinition/>\n                        </Grid.ColumnDefinitions>\n                        <TextBlock Grid.Column=\"0\" Grid.Row=\"0\" Text=\"{Binding MainForm_Screenshot_Learn_Type}\"/>\n                        <TextBlock Grid.Column=\"0\" Grid.Row=\"2\" Text=\"{Binding MainForm_Screenshot_Learn_DetectList}\" VerticalAlignment=\"Center\"/>\n                        <TextBlock Grid.Column=\"2\" Grid.Row=\"0\" Text=\"{Binding UnknownHash.TypeLocalized}\"/>\n                        <ComboBox Grid.Column=\"2\" Grid.Row=\"2\" ItemsSource=\"{Binding UnknownHash.ListMatches}\" SelectedItem=\"{Binding SelectedLearnMatch}\" DisplayMemberPath=\"NameLocalized\" Text=\"{Binding MainForm_Dynamic_Screenshot_SelectDetectionMatch, Mode=OneWay}\" IsEditable=\"True\" IsReadOnly=\"True\" Focusable=\"True\"/>\n                    </Grid>\n                    <Grid Grid.Row=\"1\">\n                        <Grid.RowDefinitions>\n                            <RowDefinition Height=\"Auto\"/>\n                            <RowDefinition Height=\"Auto\"/>\n                            <RowDefinition Height=\"10\"/>\n                            <RowDefinition Height=\"Auto\"/>\n                            <RowDefinition/>\n                        </Grid.RowDefinitions>\n                        <Grid.ColumnDefinitions>\n                            <ColumnDefinition Width=\"Auto\"/>\n                            <ColumnDefinition Width=\"10\"/>\n                            <ColumnDefinition/>\n                        </Grid.ColumnDefinitions>\n                        <Button Grid.Column=\"0\" Grid.Row=\"1\" Content=\"{Binding MainForm_Screenshot_Learn_SaveButton}\" Padding=\"10,0\" Command=\"{Binding CommandLearnHash}\"/>\n                        <Button Grid.Column=\"0\" Grid.Row=\"3\" Content=\"{Binding MainForm_Screenshot_Learn_DiscardAllButton}\" Padding=\"10,0\" Command=\"{Binding CommandLearnDiscardAll}\"/>\n                        <TextBlock Grid.Column=\"2\" Grid.Row=\"1\" Text=\"{Binding MainForm_Screenshot_Learn_PendingPlural}\">\n                            <TextBlock.Style>\n                                <Style TargetType=\"{x:Type TextBlock}\">\n                                    <Setter Property=\"Visibility\" Value=\"Visible\"/>\n                                    <Style.Triggers>\n                                        <DataTrigger Binding=\"{Binding NumUnknownHashes}\" Value=\"1\">\n                                            <Setter Property=\"Visibility\" Value=\"Hidden\"/>\n                                        </DataTrigger>\n                                        <DataTrigger Binding=\"{Binding NumUnknownHashes}\" Value=\"2\">\n                                            <Setter Property=\"Visibility\" Value=\"Hidden\"/>\n                                        </DataTrigger>\n                                    </Style.Triggers>\n                                </Style>\n                            </TextBlock.Style>\n                        </TextBlock>\n                        <TextBlock Grid.Column=\"2\" Grid.Row=\"1\" Text=\"{Binding MainForm_Screenshot_Learn_PendingSingular}\">\n                            <TextBlock.Style>\n                                <Style TargetType=\"{x:Type TextBlock}\">\n                                    <Setter Property=\"Visibility\" Value=\"Hidden\"/>\n                                    <Style.Triggers>\n                                        <DataTrigger Binding=\"{Binding NumUnknownHashes}\" Value=\"2\">\n                                            <Setter Property=\"Visibility\" Value=\"Visible\"/>\n                                        </DataTrigger>\n                                    </Style.Triggers>\n                                </Style>\n                            </TextBlock.Style>\n                        </TextBlock>\n                        <TextBlock Grid.Column=\"2\" Grid.Row=\"3\" Text=\"{Binding MainForm_Screenshot_Learn_DiscardAllInfo}\"/>\n                        <TextBlock Grid.Column=\"0\" Grid.Row=\"0\" Text=\"a&#x0a;a&#x0a;a\" Visibility=\"Hidden\"/>\n                    </Grid>\n                    <Grid Grid.Column=\"2\" Grid.RowSpan=\"2\">\n                        <Grid.RowDefinitions>\n                            <RowDefinition Height=\"Auto\"/>\n                            <RowDefinition Height=\"5\"/>\n                            <RowDefinition/>\n                        </Grid.RowDefinitions>\n                        <TextBlock Text=\"{Binding MainForm_Screenshot_Learn_SourceImage}\"/>\n                        <Image Grid.Row=\"2\" Source=\"{Binding UnknownHash.PreviewImage}\" Stretch=\"None\" VerticalAlignment=\"Top\" HorizontalAlignment=\"Left\"/>\n                    </Grid>\n                </Grid>\n            </TabItem>\n        </TabControl>\n    </Grid>\n</UserControl>\n"
  },
  {
    "path": "sources/ui/view/PageScreenshot.xaml.cs",
    "content": "﻿using System.Windows.Controls;\nusing System.Windows.Input;\n\nnamespace FFTriadBuddy.UI\n{\n    /// <summary>\n    /// Interaction logic for PageScreenshot.xaml\n    /// </summary>\n    public partial class PageScreenshot : UserControl\n    {\n        public PageScreenshot()\n        {\n            InitializeComponent();\n        }\n\n        private void Border_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)\n        {\n            var param = checkToggleOverlay.CommandParameter;\n            if (checkToggleOverlay.Command.CanExecute(param))\n            {\n                checkToggleOverlay.Command.Execute(param);\n            }\n        }\n\n        private void ListView_ContextMenuOpening(object sender, ContextMenuEventArgs e)\n        {\n            var listView = e.Source as ListView;\n            var pageVM = DataContext as PageScreenshotViewModel;\n\n            if (pageVM.CommandBuildContextActions.CanExecute(listView.SelectedItem))\n            {\n                pageVM.CommandBuildContextActions.Execute(listView.SelectedItem);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/PageSetup.xaml",
    "content": "﻿<UserControl\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \n             xmlns:local=\"clr-namespace:FFTriadBuddy.UI\"\n             x:Class=\"FFTriadBuddy.UI.PageSetup\"\n             mc:Ignorable=\"d\" \n             d:DataContext=\"{d:DesignInstance Type=local:PageSetupViewModel, IsDesignTimeCreatable=True}\" d:DesignWidth=\"601\" d:DesignHeight=\"239\"\n             Background=\"{StaticResource BrushWindowBackground}\">\n    <Grid Margin=\"4\">\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition/>\n            <RowDefinition Height=\"Auto\"/>\n        </Grid.RowDefinitions>\n        <Grid.ColumnDefinitions>\n            <ColumnDefinition Width=\"Auto\"/>\n            <ColumnDefinition Width=\"3*\"/>\n            <ColumnDefinition Width=\"10\"/>\n            <ColumnDefinition Width=\"2*\"/>\n        </Grid.ColumnDefinitions>\n        <TextBlock x:Name=\"textNpc\" Text=\"{Binding MainForm_Setup_NPC, FallbackValue=aaa}\" Grid.Column=\"0\" Grid.Row=\"0\" VerticalAlignment=\"Center\" Padding=\"0,0,5,0\" Margin=\"0,3\"/>\n        <local:SearchableComboBox Grid.Column=\"1\" Grid.Row=\"0\" Grid.ColumnSpan=\"5\" ItemsSource=\"{Binding Npcs}\" DisplayMemberPath=\"NameLocalized\" SelectedItem=\"{Binding ActiveNpc, Mode=OneWay}\" SelectionEffectivelyChanged=\"SearchableComboBox_SelectionEffectivelyChanged\" IsEnabled=\"{Binding IsDeckOptimizerStopped}\"/>\n        <Grid Grid.Column=\"0\" Grid.Row=\"1\" Grid.ColumnSpan=\"2\" Margin=\"0,10,0,0\">\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition Width=\"{Binding ActualWidth, ElementName=textNpc, Mode=OneWay}\"/>\n                <ColumnDefinition Width=\"Auto\"/>\n                <ColumnDefinition Width=\"5\"/>\n                <ColumnDefinition Width=\"1*\"/>\n            </Grid.ColumnDefinitions>\n            <Grid.RowDefinitions>\n                <RowDefinition Height=\"Auto\"/>\n                <RowDefinition Height=\"Auto\"/>\n                <RowDefinition Height=\"Auto\"/>\n                <RowDefinition Height=\"Auto\"/>\n            </Grid.RowDefinitions>\n            <TextBlock Grid.Column=\"1\" Grid.Row=\"0\" HorizontalAlignment=\"Left\" Text=\"{Binding MainForm_Setup_NPC_Location, FallbackValue=aaa}\" VerticalAlignment=\"Top\"/>\n            <TextBlock Grid.Column=\"1\" Grid.Row=\"1\" HorizontalAlignment=\"Left\" Text=\"{Binding MainForm_Setup_NPC_DeckPower, FallbackValue=aaa}\" VerticalAlignment=\"Top\"/>\n            <TextBlock Grid.Column=\"1\" Grid.Row=\"2\" HorizontalAlignment=\"Left\" Text=\"{Binding MainForm_Setup_NPC_Rules, FallbackValue=aaa}\" VerticalAlignment=\"Top\"/>\n            <TextBlock Grid.Column=\"1\" Grid.Row=\"3\" HorizontalAlignment=\"Left\" Text=\"{Binding MainForm_Setup_NPC_WinChance, FallbackValue=aaa}\" VerticalAlignment=\"Top\"/>\n            <TextBlock Grid.Column=\"3\" Grid.Row=\"0\" HorizontalAlignment=\"Left\" Text=\"{Binding ActiveNpc.LocationLocalized}\" VerticalAlignment=\"Top\"/>\n            <TextBlock Grid.Column=\"3\" Grid.Row=\"1\" HorizontalAlignment=\"Left\" Text=\"{Binding ActiveNpc.DescPower}\" VerticalAlignment=\"Top\"/>\n            <TextBlock Grid.Column=\"3\" Grid.Row=\"2\" HorizontalAlignment=\"Left\" Text=\"{Binding ActiveNpc.DescRules}\" VerticalAlignment=\"Top\"/>\n            <TextBlock x:Name=\"textWinChance\" Grid.Column=\"3\" Grid.Row=\"3\" HorizontalAlignment=\"Left\" Text=\"{Binding DeckSolver.DescProbability}\" VerticalAlignment=\"Top\"/>\n            <ProgressBar Grid.Column=\"3\" Grid.Row=\"3\"  Height=\"{Binding ActualHeight, ElementName=textWinChance, Mode=OneWay}\" Value=\"{Binding DeckSolver.Progress, Mode=OneWay}\" Visibility=\"{Binding DeckSolver.IsSolverRunning, Converter={StaticResource convVis}}\"/>\n            <Viewbox Height=\"{Binding Path=ActualHeight, ElementName=textNpc}\" Grid.Column=\"0\" Grid.Row=\"2\">\n                <CheckBox  IsChecked=\"{Binding IsRegionMode, Mode=OneWay}\" Command=\"{Binding CommandToggleTournament}\" CommandParameter=\"{Binding IsChecked, RelativeSource={RelativeSource Self}}\" HorizontalAlignment=\"Right\" ToolTip=\"{Binding MainForm_Setup_RulesToggle}\" IsEnabled=\"{Binding IsDeckOptimizerStopped}\" Margin=\"0,0,5,0\"/>\n            </Viewbox>\n        </Grid>\n        <Grid Grid.Column=\"3\" Grid.Row=\"1\" Margin=\"0,10,0,0\">\n            <Grid.RowDefinitions>\n                <RowDefinition Height=\"Auto\"/>\n                <RowDefinition Height=\"5\"/>\n                <RowDefinition Height=\"Auto\"/>\n                <RowDefinition Height=\"1*\" MinHeight=\"5\"/>\n                <RowDefinition Height=\"Auto\"/>\n            </Grid.RowDefinitions>\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition Width=\"Auto\"/>\n                <ColumnDefinition Width=\"5\"/>\n                <ColumnDefinition/>\n            </Grid.ColumnDefinitions>\n\n            <TextBlock Grid.Column=\"0\" Grid.Row=\"0\" HorizontalAlignment=\"Left\" Text=\"{Binding MainForm_Setup_Rules_Region1, FallbackValue=aaa}\" VerticalAlignment=\"Center\" Visibility=\"{Binding IsRegionMode, Converter={StaticResource convVis}}\"/>\n            <TextBlock Grid.Column=\"0\" Grid.Row=\"2\" HorizontalAlignment=\"Left\" Text=\"{Binding MainForm_Setup_Rules_Region2, FallbackValue=aaa}\" VerticalAlignment=\"Center\" Visibility=\"{Binding IsRegionMode, Converter={StaticResource convVis}}\"/>\n            <ComboBox Grid.Column=\"2\" Grid.Row=\"0\" VerticalAlignment=\"Top\" ItemsSource=\"{Binding Rules}\" SelectedItem=\"{Binding ActiveRegionRule1}\" DisplayMemberPath=\"NameLocalized\" IsEnabled=\"{Binding IsDeckOptimizerStopped}\" Visibility=\"{Binding IsRegionMode, Converter={StaticResource convVis}}\"/>\n            <ComboBox Grid.Column=\"2\" Grid.Row=\"2\" VerticalAlignment=\"Top\" ItemsSource=\"{Binding Rules}\" SelectedItem=\"{Binding ActiveRegionRule2}\" DisplayMemberPath=\"NameLocalized\" IsEnabled=\"{Binding IsDeckOptimizerStopped}\" Visibility=\"{Binding IsRegionMode, Converter={StaticResource convVis}}\"/>\n\n            <TextBlock Grid.Column=\"0\" Grid.Row=\"0\" HorizontalAlignment=\"Left\" Text=\"{Binding MainForm_Setup_Rules_Tournament}\" VerticalAlignment=\"Center\" Visibility=\"{Binding IsRegionMode, Converter={StaticResource convInvis}}\"/>\n            <TextBlock Grid.Column=\"0\" Grid.Row=\"2\" HorizontalAlignment=\"Left\" Text=\"{Binding MainForm_Setup_Rules_TournamentRules}\" VerticalAlignment=\"Center\" Visibility=\"{Binding IsRegionMode, Converter={StaticResource convInvis}}\" Height=\"{Binding ActualHeight, ElementName=comboTournament}\"/>\n            <ComboBox x:Name=\"comboTournament\" Grid.Column=\"2\" Grid.Row=\"0\" VerticalAlignment=\"Top\" ItemsSource=\"{Binding Tournaments}\" SelectedItem=\"{Binding ActiveTournament}\" DisplayMemberPath=\"NameLocalized\" IsEnabled=\"{Binding IsDeckOptimizerStopped}\" Visibility=\"{Binding IsRegionMode, Converter={StaticResource convInvis}}\"/>\n            <TextBlock Grid.Column=\"2\" Grid.Row=\"2\" Text=\"{Binding ActiveTournament.DescRules}\" VerticalAlignment=\"Center\" Visibility=\"{Binding IsRegionMode, Converter={StaticResource convInvis}}\" Height=\"{Binding ActualHeight, ElementName=comboTournament}\"/>\n\n            <Button x:Name=\"buttonAuth\" Content=\"{Binding MainForm_Setup_Cloud_AuthButton, FallbackValue=aaa}\" Command=\"{Binding CommandCloudAuth}\" Visibility=\"{Binding IsCloudSaveButtonActive, Converter={StaticResource convVis}}\" Grid.Row=\"4\" VerticalAlignment=\"Top\"/>\n            <Border BorderThickness=\"0\" VerticalAlignment=\"Top\" Grid.Column=\"2\" Grid.Row=\"4\" Background=\"#dddddd\">\n                <StackPanel x:Name=\"panelCloudSettings\"  Orientation=\"Horizontal\" Margin=\"2,2,2,2\" VerticalAlignment=\"Center\">\n                    <Viewbox Height=\"{Binding Path=ActualHeight, ElementName=panelCloudSettings}\">\n                        <CheckBox IsChecked=\"{Binding IsCloudSaveEnabled, Mode=OneWay}\" Command=\"{Binding CommandToggleCloudSaves}\" CommandParameter=\"{Binding IsChecked, RelativeSource={RelativeSource Self}}\"/>\n                    </Viewbox>\n                    <TextBlock Text=\"{Binding MainForm_Setup_Cloud_Desc, FallbackValue=aaa}\" Margin=\"5,-1,0,0\"/>\n                    <TextBlock Text=\"{Binding CloudSaveStatus, FallbackValue=aaa}\" Margin=\"5,-1,0,0\"/>\n                </StackPanel>\n            </Border>\n        </Grid>\n\n        <StackPanel Orientation=\"Horizontal\" Grid.Row=\"2\" Grid.ColumnSpan=\"2\" Margin=\"0,10,0,0\" VerticalAlignment=\"Top\">\n            <TextBlock HorizontalAlignment=\"Left\" TextWrapping=\"Wrap\" Text=\"{Binding MainForm_Setup_Deck_Title, FallbackValue=aaa}\"/>\n            <TextBlock HorizontalAlignment=\"Left\" TextWrapping=\"Wrap\" Text=\"{Binding DescDeckState}\" Margin=\"5,0,0,0\" Background=\"#FFFFC0C8\" FontWeight=\"Bold\"/>\n        </StackPanel>\n        <local:DeckView Margin=\"0,9,0,0\" Grid.Row=\"3\" Grid.Column=\"1\" CanReorderCards=\"True\" CanPickCards=\"True\" Width=\"Auto\" Height=\"Auto\" DataContext=\"{Binding ActiveDeck}\" IsEnabled=\"{Binding DataContext.IsDeckOptimizerRunning, Converter={StaticResource convInverse}, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:PageSetup}}}\"/>\n        <Grid Grid.Row=\"4\" Grid.Column=\"1\" Margin=\"0,5,0,10\">\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition/>\n                <ColumnDefinition Width=\"20\"/>\n                <ColumnDefinition Width=\"Auto\"/>\n            </Grid.ColumnDefinitions>\n            <Button Content=\"{Binding MainForm_Setup_Deck_OptimizeStartButton}\" Command=\"{Binding CommandDeckOptimizerStart}\" Visibility=\"{Binding IsDeckOptimizerRunning, Converter={StaticResource convInvis}}\" Grid.Column=\"0\" Grid.ColumnSpan=\"3\"/>\n            <Button Content=\"{Binding MainForm_Setup_Deck_OptimizeAbortButton}\" Command=\"{Binding CommandDeckOptimizerAbort}\" Visibility=\"{Binding IsDeckOptimizerRunning, Converter={StaticResource convVis}}\" Grid.Column=\"2\" Padding=\"10,0,10,0\" HorizontalAlignment=\"Right\"/>\n            <ProgressBar Grid.Column=\"0\" Visibility=\"{Binding IsDeckOptimizerRunning, Converter={StaticResource convVis}}\" Value=\"{Binding OptimizerProgress}\"/>\n        </Grid>\n        <ScrollViewer Grid.Column=\"4\" Grid.Row=\"3\" VerticalScrollBarVisibility=\"Auto\">\n            <ItemsControl ItemsSource=\"{Binding FavDeckVM.FavDecks}\" x:Name=\"favDeckControl\">\n                <ItemsControl.ItemsPanel>\n                    <ItemsPanelTemplate>\n                        <StackPanel Orientation=\"Vertical\"/>\n                    </ItemsPanelTemplate>\n                </ItemsControl.ItemsPanel>\n                <ItemsControl.ItemTemplate>\n                    <DataTemplate>\n                        <Border BorderThickness=\"1\" Background=\"#FFDDDDDD\" BorderBrush=\"#FFA0A0A0\" Margin=\"0,2\" Padding=\"1\">\n                            <Grid>\n                                <Grid.RowDefinitions>\n                                    <RowDefinition/>\n                                    <RowDefinition Height=\"1\"/>\n                                    <RowDefinition/>\n                                </Grid.RowDefinitions>\n                                <Grid.ColumnDefinitions>\n                                    <ColumnDefinition Width=\"Auto\"/>\n                                    <ColumnDefinition Width=\"5\"/>\n                                    <ColumnDefinition Width=\"Auto\"/>\n                                    <ColumnDefinition Width=\"5\"/>\n                                    <ColumnDefinition Width=\"1*\"/>\n                                    <ColumnDefinition Width=\"5\"/>\n                                    <ColumnDefinition Width=\"Auto\"/>\n                                </Grid.ColumnDefinitions>\n                                <Button Content=\"&lt;&lt;\" Grid.Column=\"0\" Grid.Row=\"2\" ToolTip=\"{Binding DataContext.FavDeckForm_Dynamic_UpdateButton, ElementName=favDeckControl}\" Command=\"{Binding DataContext.FavDeckVM.CommandFavUse, ElementName=favDeckControl}\" CommandParameter=\"{Binding}\"/>\n                                <Button Content=\"&gt;&gt;\" Grid.Column=\"0\" Grid.Row=\"0\" ToolTip=\"{Binding DataContext.FavDeckForm_Dynamic_UpdateButton, ElementName=favDeckControl}\" Command=\"{Binding DataContext.FavDeckVM.CommandFavAssign, ElementName=favDeckControl}\" CommandParameter=\"{Binding}\"/>\n                                <TextBlock Text=\"{Binding Deck.Name}\" Grid.Column=\"2\" Grid.Row=\"0\" VerticalAlignment=\"Center\"/>\n                                <TextBlock Text=\"{Binding DescProbability}\" Grid.Column=\"2\" Grid.Row=\"2\" VerticalAlignment=\"Center\"/>\n                                <local:FavDeckPreview DataContext=\"{Binding Deck}\" Grid.Column=\"4\" Grid.Row=\"0\" Grid.RowSpan=\"3\"/>\n                                <Button Content=\"{Binding DataContext.FavDeckCtrl_Edit, ElementName=favDeckControl}\" Grid.Column=\"6\" Grid.Row=\"0\" ToolTip=\"{Binding Content, RelativeSource={RelativeSource Self}}\" Command=\"{Binding DataContext.FavDeckVM.CommandFavEdit, ElementName=favDeckControl}\" CommandParameter=\"{Binding}\"/>\n                                <Button Content=\"x\" Grid.Column=\"6\" Grid.Row=\"2\" ToolTip=\"{Binding DataContext.FavDeckForm_RemoveButton, ElementName=favDeckControl}\" Command=\"{Binding DataContext.FavDeckVM.CommandFavRemove, ElementName=favDeckControl}\" CommandParameter=\"{Binding}\"/>\n                            </Grid>\n                        </Border>\n                    </DataTemplate>\n                </ItemsControl.ItemTemplate>\n            </ItemsControl>\n        </ScrollViewer>\n        <Button Grid.Column=\"4\" Grid.Row=\"4\" Margin=\"0,5,10,10\" Content=\"{Binding MainForm_Setup_Fav_AddSlotButton, FallbackValue=aaa}\" HorizontalAlignment=\"Right\" Command=\"{Binding FavDeckVM.CommandFavCreate}\" Padding=\"10,0,10,0\"/>\n        <GroupBox Grid.Column=\"3\" Grid.Row=\"2\" Grid.RowSpan=\"3\" Margin=\"0,10,0,0\" Header=\"{Binding MainForm_Setup_OptimizeStats_Title}\" Background=\"{StaticResource BrushWindowBackground}\">\n            <GroupBox.Style>\n                <Style TargetType=\"{x:Type GroupBox}\">\n                    <Setter Property=\"Visibility\" Value=\"Collapsed\"/>\n                    <Style.Triggers>\n                        <DataTrigger Binding=\"{Binding IsDeckOptimizerRunning}\" Value=\"True\">\n                            <Setter Property=\"Visibility\" Value=\"Visible\"/>\n                            <DataTrigger.EnterActions>\n                                <StopStoryboard BeginStoryboardName=\"delaydSwitch\"/>\n                            </DataTrigger.EnterActions>\n                            <DataTrigger.ExitActions>\n                                <BeginStoryboard Name=\"delaydSwitch\" >\n                                    <BeginStoryboard.Storyboard>\n                                        <Storyboard>\n                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty=\"Visibility\" Duration=\"0:0:5\">\n                                                <DiscreteObjectKeyFrame KeyTime=\"0%\" Value=\"{x:Static Visibility.Visible}\"/>\n                                                <DiscreteObjectKeyFrame KeyTime=\"100%\" Value=\"{x:Static Visibility.Collapsed}\"/>\n                                            </ObjectAnimationUsingKeyFrames>\n                                        </Storyboard>\n                                    </BeginStoryboard.Storyboard>\n                                </BeginStoryboard>\n                            </DataTrigger.ExitActions>\n                        </DataTrigger>\n                    </Style.Triggers>\n                </Style>\n            </GroupBox.Style>\n            <Grid>\n                <Grid.RowDefinitions>\n                    <RowDefinition Height=\"Auto\"/>\n                    <RowDefinition Height=\"Auto\"/>\n                    <RowDefinition Height=\"Auto\"/>\n                    <RowDefinition Height=\"Auto\"/>\n                    <RowDefinition Height=\"Auto\"/>\n                    <RowDefinition Height=\"Auto\"/>\n                </Grid.RowDefinitions>\n                <Grid.ColumnDefinitions>\n                    <ColumnDefinition Width=\"Auto\"/>\n                    <ColumnDefinition Width=\"5\"/>\n                    <ColumnDefinition/>\n                </Grid.ColumnDefinitions>\n                <TextBlock Text=\"{Binding MainForm_Setup_OptimizerStats_NumOwned, FallbackValue=aaa}\" HorizontalAlignment=\"Left\" VerticalAlignment=\"Top\"/>\n                <TextBlock Text=\"{Binding MainForm_Setup_OptimizerStats_NumPossible, FallbackValue=aaa}\" HorizontalAlignment=\"Left\" VerticalAlignment=\"Top\" Grid.Row=\"1\"/>\n                <TextBlock Text=\"{Binding MainForm_Setup_OptimizerStats_NumTested, FallbackValue=aaa}\" HorizontalAlignment=\"Left\" VerticalAlignment=\"Top\" Grid.Row=\"2\"/>\n                <TextBlock Text=\"{Binding MainForm_Setup_OptimizerStats_Progress, FallbackValue=aaa}\" HorizontalAlignment=\"Left\" VerticalAlignment=\"Top\" Grid.Row=\"4\"/>\n                <TextBlock Text=\"{Binding MainForm_Setup_OptimizerStats_TimeLeft, FallbackValue=aaa}\" HorizontalAlignment=\"Left\" VerticalAlignment=\"Top\" Grid.Row=\"5\"/>\n                <TextBlock Text=\"{Binding OptimizerNumOwned}\" Grid.Column=\"2\" HorizontalAlignment=\"Left\" VerticalAlignment=\"Top\"/>\n                <TextBlock Text=\"{Binding OptimizerNumPossibleDesc}\" Grid.Column=\"2\" HorizontalAlignment=\"Left\" VerticalAlignment=\"Top\" Grid.Row=\"1\"/>\n                <TextBlock Text=\"{Binding OptimizerNumTestedDesc}\" Grid.Column=\"2\" HorizontalAlignment=\"Left\" VerticalAlignment=\"Top\" Grid.Row=\"2\"/>\n                <TextBlock Text=\"{Binding OptimizerProgressDesc}\" Grid.Column=\"2\" HorizontalAlignment=\"Left\" VerticalAlignment=\"Top\" Grid.Row=\"4\"/>\n                <TextBlock Text=\"{Binding OptimizerTimeLeftDesc}\" Grid.Column=\"2\" HorizontalAlignment=\"Left\" VerticalAlignment=\"Top\" Grid.Row=\"5\"/>\n                <TextBlock Text=\" \" Grid.Column=\"0\" Grid.Row=\"3\" />\n            </Grid>\n        </GroupBox>\n    </Grid>\n</UserControl>\n"
  },
  {
    "path": "sources/ui/view/PageSetup.xaml.cs",
    "content": "﻿using System.Windows.Controls;\n\nnamespace FFTriadBuddy.UI\n{\n    /// <summary>\n    /// Interaction logic for PageSetup.xaml\n    /// </summary>\n    public partial class PageSetup : UserControl\n    {\n        public PageSetup()\n        {\n            InitializeComponent();\n        }\n\n        private void SearchableComboBox_SelectionEffectivelyChanged(object sender, object obj)\n        {\n            var pageVM = DataContext as PageSetupViewModel;\n            var npcProxy = obj as NpcModelProxy;\n\n            if (pageVM.CommandPickNpc.CanExecute(npcProxy))\n            {\n                pageVM.CommandPickNpc.Execute(npcProxy);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/PageSimulate.xaml",
    "content": "﻿<UserControl x:Class=\"FFTriadBuddy.UI.PageSimulate\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \n             xmlns:local=\"clr-namespace:FFTriadBuddy.UI\"\n             mc:Ignorable=\"d\" \n             d:DataContext=\"{d:DesignInstance Type=local:PageSimulateViewModel, IsDesignTimeCreatable=True}\"\n             d:DesignHeight=\"215\" d:DesignWidth=\"525\">\n    <UserControl.Resources>\n        <SolidColorBrush x:Key=\"brushBoard\" Color=\"#FFFAEBD7\"/>\n    </UserControl.Resources>\n    <Grid>\n        <Grid>\n            <Grid.RowDefinitions>\n                <RowDefinition Height=\"1*\"/>\n                <RowDefinition Height=\"Auto\"/>\n                <RowDefinition Height=\"Auto\"/>\n            </Grid.RowDefinitions>\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition Width=\"1*\"/>\n                <ColumnDefinition Width=\"1*\"/>\n                <ColumnDefinition Width=\"1*\"/>\n            </Grid.ColumnDefinitions>\n            <StackPanel Margin=\"10,0,0,0\" Grid.Row=\"1\" VerticalAlignment=\"Top\" Orientation=\"Horizontal\" Grid.Column=\"1\" Grid.ColumnSpan=\"2\">\n                <TextBlock TextWrapping=\"Wrap\" Text=\"{Binding MainForm_Simulate_RuleList}\"/>\n                <TextBlock TextWrapping=\"Wrap\" Text=\"{Binding DescRules}\" Margin=\"5,0,0,0\"/>\n            </StackPanel>\n            <Button Content=\"{Binding DescUndoButton}\" Command=\"{Binding CommandRedUndo}\" Margin=\"5,10,10,10\" Grid.Row=\"2\" Grid.Column=\"2\"/>\n            <Button Content=\"{Binding MainForm_Simulate_ResetButton}\" Command=\"{Binding CommandReset}\" Visibility=\"{Binding CanStartWithBlue, Converter={StaticResource convInvis}}\" Margin=\"10,10,5,10\" Grid.Row=\"2\" Grid.Column=\"1\"/>\n            <Button Content=\"{Binding MainForm_Dynamic_Simulate_BlueStartButton}\" Command=\"{Binding CommandBlueFirst}\" Visibility=\"{Binding CanStartWithBlue, Converter={StaticResource convVis}}\" Margin=\"10,10,5,10\" Grid.Row=\"2\" Grid.Column=\"1\"/>\n            <TabControl Margin=\"10\" Style=\"{StaticResource tabControlSwitcher}\" Background=\"{StaticResource brushBoard}\" BorderThickness=\"1\" BorderBrush=\"{StaticResource brushBorder}\" SelectedIndex=\"{Binding SpecialRuleSwitcherBoardIdx, Mode=OneWay}\">\n                <TabItem>\n                    <Viewbox>\n                        <ItemsControl ItemsSource=\"{Binding BoardCards}\">\n                            <ItemsControl.ItemsPanel>\n                                <ItemsPanelTemplate>\n                                    <Grid>\n                                        <Grid.ColumnDefinitions>\n                                            <ColumnDefinition/>\n                                            <ColumnDefinition/>\n                                            <ColumnDefinition/>\n                                        </Grid.ColumnDefinitions>\n                                        <Grid.RowDefinitions>\n                                            <RowDefinition/>\n                                            <RowDefinition/>\n                                            <RowDefinition/>\n                                        </Grid.RowDefinitions>\n                                    </Grid>\n                                </ItemsPanelTemplate>\n                            </ItemsControl.ItemsPanel>\n                            <ItemsControl.ItemContainerStyle>\n                                <Style>\n                                    <Setter Property=\"Grid.Column\" Value=\"{Binding BoardX}\"/>\n                                    <Setter Property=\"Grid.Row\" Value=\"{Binding BoardY}\"/>\n                                    <Setter Property=\"local:CardDragDropExtension.IsDragDestination\" Value=\"True\"/>\n                                    <Setter Property=\"local:CardDragDropExtension.CardContainer\" Value=\"{Binding Card.OwnerObject}\"/>\n                                    <Setter Property=\"local:CardDragDropExtension.CardContext\" Value=\"{Binding Card}\"/>\n                                </Style>\n                            </ItemsControl.ItemContainerStyle>\n                            <ItemsControl.ItemTemplate>\n                                <DataTemplate>\n                                    <Border Margin=\"2\" BorderBrush=\"{StaticResource brushBorder}\" BorderThickness=\"1\" Background=\"WhiteSmoke\" MinHeight=\"100\" MinWidth=\"100\">\n                                        <local:CardView DataContext=\"{Binding Card}\" IsBlinkingOnHighlight=\"True\"/>\n                                    </Border>\n                                </DataTemplate>\n                            </ItemsControl.ItemTemplate>\n                        </ItemsControl>\n                    </Viewbox>\n                </TabItem>\n                <TabItem>\n                    <TextBlock Text=\"{Binding MainForm_Simulate_Game_SpecialRule}\" HorizontalAlignment=\"Center\" VerticalAlignment=\"Center\" FontSize=\"14\" FontWeight=\"Bold\"/>\n                </TabItem>\n            </TabControl>\n            <TabControl Margin=\"10\" Style=\"{StaticResource tabControlSwitcher}\" Grid.Column=\"1\" Grid.ColumnSpan=\"2\" SelectedIndex=\"{Binding SpecialRuleSwitcherIdx, Mode=OneWay}\">\n                <TabItem>\n                    <Border Background=\"#FFFFE4E1\" BorderBrush=\"{StaticResource brushBorder}\" BorderThickness=\"1\">\n                        <Grid>\n                            <Grid.RowDefinitions>\n                                <RowDefinition Height=\"Auto\"/>\n                                <RowDefinition Height=\"1*\"/>\n                                <RowDefinition Height=\"Auto\"/>\n                                <RowDefinition Height=\"1*\"/>\n                                <RowDefinition Height=\"Auto\"/>\n                            </Grid.RowDefinitions>\n                            <TextBlock Text=\"{Binding MainForm_Simulate_Game_KnownCards}\" VerticalAlignment=\"Top\" Margin=\"2\"/>\n                            <TextBlock Text=\"{Binding DescUnknownCards}\" Grid.Row=\"2\" VerticalAlignment=\"Top\" Margin=\"2\" Visibility=\"{Binding HasUnknownCards, Converter={StaticResource convVis}}\"/>\n                            <Border Grid.Row=\"4\" Margin=\"0,2,0,0\">\n                                <Border.Resources>\n                                    <Style TargetType=\"{x:Type Border}\">\n                                        <Setter Property=\"Background\" Value=\"#FFFFC1BA\"/>\n                                        <Style.Triggers>\n                                            <DataTrigger Binding=\"{Binding HasGameHint}\" Value=\"True\">\n                                                <Setter Property=\"Background\" Value=\"#FFCE1500\"/>\n                                            </DataTrigger>\n                                        </Style.Triggers>\n                                    </Style>\n                                    <Style TargetType=\"{x:Type TextBlock}\">\n                                        <Style.Triggers>\n                                            <DataTrigger Binding=\"{Binding HasGameHint}\" Value=\"True\">\n                                                <Setter Property=\"Foreground\" Value=\"White\"/>\n                                            </DataTrigger>\n                                        </Style.Triggers>\n                                    </Style>\n                                </Border.Resources>\n                                <TextBlock Text=\"{Binding DescGameHint}\" HorizontalAlignment=\"Center\"/>\n                            </Border>\n                            <local:DeckView DataContext=\"{Binding RedKnownCards}\" Grid.Row=\"1\" Width=\"Auto\" Height=\"Auto\" CanReorderCards=\"True\"/>\n                            <local:DeckView DataContext=\"{Binding RedUnknownCards}\" Grid.Row=\"3\" Width=\"Auto\" Height=\"Auto\" CanReorderCards=\"True\" Visibility=\"{Binding DataContext.HasUnknownCards, Converter={StaticResource convVis}, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:PageSimulate}}}\"/>\n                        </Grid>\n                    </Border>\n                </TabItem>\n                <TabItem>\n                    <Grid>\n                        <Grid.Resources>\n                            <Style x:Key=\"show2\" TargetType=\"{x:Type FrameworkElement}\">\n                                <Style.Triggers>\n                                    <DataTrigger Binding=\"{Binding SpecialRules.NumRouletteChoices}\" Value=\"1\">\n                                        <Setter Property=\"Visibility\" Value=\"Hidden\"/>\n                                    </DataTrigger>\n                                </Style.Triggers>\n                            </Style>\n                            <Style x:Key=\"show3\" TargetType=\"{x:Type FrameworkElement}\" BasedOn=\"{StaticResource show2}\">\n                                <Style.Triggers>\n                                    <DataTrigger Binding=\"{Binding SpecialRules.NumRouletteChoices}\" Value=\"2\">\n                                        <Setter Property=\"Visibility\" Value=\"Hidden\"/>\n                                    </DataTrigger>\n                                </Style.Triggers>\n                            </Style>\n                            <Style x:Key=\"show4\" TargetType=\"{x:Type FrameworkElement}\" BasedOn=\"{StaticResource show3}\">\n                                <Style.Triggers>\n                                    <DataTrigger Binding=\"{Binding SpecialRules.NumRouletteChoices}\" Value=\"3\">\n                                        <Setter Property=\"Visibility\" Value=\"Hidden\"/>\n                                    </DataTrigger>\n                                </Style.Triggers>\n                            </Style>\n                        </Grid.Resources>\n                        \n                        <Grid.RowDefinitions>\n                            <RowDefinition Height=\"Auto\"/>\n                            <RowDefinition Height=\"Auto\"/>\n                            <RowDefinition Height=\"Auto\"/>\n                            <RowDefinition Height=\"Auto\"/>\n                            <RowDefinition/>\n                            <RowDefinition Height=\"Auto\"/>\n                        </Grid.RowDefinitions>\n                        <Grid.ColumnDefinitions>\n                            <ColumnDefinition Width=\"Auto\"/>\n                            <ColumnDefinition/>\n                        </Grid.ColumnDefinitions>\n                        <TextBlock Grid.Row=\"0\" Text=\"{Binding MainForm_Simulate_Roulette_Rule1}\" VerticalAlignment=\"Center\"/>\n                        <TextBlock Grid.Row=\"1\" Text=\"{Binding MainForm_Simulate_Roulette_Rule2}\" VerticalAlignment=\"Center\" Style=\"{StaticResource show2}\"/>\n                        <TextBlock Grid.Row=\"2\" Text=\"{Binding MainForm_Simulate_Roulette_Rule3}\" VerticalAlignment=\"Center\" Style=\"{StaticResource show3}\"/>\n                        <TextBlock Grid.Row=\"3\" Text=\"{Binding MainForm_Simulate_Roulette_Rule4}\" VerticalAlignment=\"Center\" Style=\"{StaticResource show4}\"/>\n                        <ComboBox Grid.Column=\"1\" Grid.Row=\"0\" ItemsSource=\"{Binding SpecialRules.Rules}\" DisplayMemberPath=\"NameLocalized\" SelectedItem=\"{Binding SpecialRules.Roulette1}\" Margin=\"5,2,0,2\"/>\n                        <ComboBox Grid.Column=\"1\" Grid.Row=\"1\" ItemsSource=\"{Binding SpecialRules.Rules}\" DisplayMemberPath=\"NameLocalized\" SelectedItem=\"{Binding SpecialRules.Roulette2}\" Margin=\"5,2,0,2\" Style=\"{StaticResource show2}\"/>\n                        <ComboBox Grid.Column=\"1\" Grid.Row=\"2\" ItemsSource=\"{Binding SpecialRules.Rules}\" DisplayMemberPath=\"NameLocalized\" SelectedItem=\"{Binding SpecialRules.Roulette3}\" Margin=\"5,2,0,2\" Style=\"{StaticResource show3}\"/>\n                        <ComboBox Grid.Column=\"1\" Grid.Row=\"3\" ItemsSource=\"{Binding SpecialRules.Rules}\" DisplayMemberPath=\"NameLocalized\" SelectedItem=\"{Binding SpecialRules.Roulette4}\" Margin=\"5,2,0,2\" Style=\"{StaticResource show4}\"/>\n                        <Button Grid.Row=\"5\" Grid.ColumnSpan=\"2\" Content=\"{Binding MainForm_Simulate_Game_ApplyRuleButton}\" Background=\"{StaticResource brushBoard}\" Command=\"{Binding SpecialRules.CommandApply}\"/>\n                    </Grid>\n                </TabItem>\n                <TabItem>\n                    <Grid>\n                        <Grid.RowDefinitions>\n                            <RowDefinition Height=\"Auto\"/>\n                            <RowDefinition/>\n                            <RowDefinition Height=\"Auto\"/>\n                        </Grid.RowDefinitions>\n                        <TextBlock Text=\"{Binding MainForm_Simulate_Random_Info}\"/>\n                        <local:DeckView DataContext=\"{Binding SpecialRules.OverrideBlueDeck}\" Grid.Row=\"1\" Margin=\"10,10,10,10\" Width=\"Auto\" Height=\"Auto\" CanPickCards=\"True\" CanReorderCards=\"True\" />\n                        <Button Grid.Row=\"2\" Content=\"{Binding MainForm_Simulate_Game_ApplyRuleButton}\" Background=\"{StaticResource brushBoard}\" Command=\"{Binding SpecialRules.CommandApply}\"/>\n                    </Grid>\n                </TabItem>\n                <TabItem>\n                    <Grid>\n                        <Grid.RowDefinitions>\n                            <RowDefinition Height=\"Auto\"/>\n                            <RowDefinition/>\n                            <RowDefinition/>\n                            <RowDefinition Height=\"Auto\"/>\n                        </Grid.RowDefinitions>\n                        <TextBlock Text=\"{Binding MainForm_Dynamic_Simulate_SwapRuleButton}\"/>\n                        <local:DeckView DataContext=\"{Binding SpecialRules.OverrideBlueDeck}\" Grid.Row=\"1\" Margin=\"10,10,10,5\" Width=\"Auto\" Height=\"Auto\" />\n                        <local:DeckView DataContext=\"{Binding SpecialRules.OverrideRedDeck}\" Grid.Row=\"2\" Margin=\"10,5,10,10\" Width=\"Auto\" Height=\"Auto\" />\n                        <Button Grid.Row=\"3\" Content=\"{Binding MainForm_Simulate_Game_ApplyRuleButton}\" Background=\"{StaticResource brushBoard}\" Command=\"{Binding SpecialRules.CommandApply}\"/>\n                    </Grid>\n                </TabItem>\n                <TabItem>\n                    <Grid>\n                        <Grid.RowDefinitions>\n                            <RowDefinition Height=\"Auto\"/>\n                            <RowDefinition/>\n                            <RowDefinition Height=\"Auto\"/>\n                        </Grid.RowDefinitions>\n                        <Grid.ColumnDefinitions>\n                            <ColumnDefinition Width=\"1*\"/>\n                            <ColumnDefinition Width=\"10\"/>\n                            <ColumnDefinition Width=\"4*\"/>\n                        </Grid.ColumnDefinitions>\n                        <TextBlock Grid.ColumnSpan=\"3\" Text=\"{Binding MainForm_Simulate_Open_Hint}\"/>\n                        <local:DeckView DataContext=\"{Binding SpecialRules.OverrideRedDeck}\"  Grid.Row=\"1\" Grid.ColumnSpan=\"3\" Margin=\"10,10,10,5\" Width=\"Auto\" Height=\"Auto\" />\n                        <Button Grid.Row=\"2\" Content=\"{Binding MainForm_Simulate_Game_SkipRuleButton}\" Background=\"{StaticResource brushBoard}\" Command=\"{Binding SpecialRules.CommandSkip}\"/>\n                        <Button Grid.Column=\"2\" Grid.Row=\"2\" Content=\"{Binding MainForm_Simulate_Game_ApplyRuleButton}\" Background=\"{StaticResource brushBoard}\" Command=\"{Binding SpecialRules.CommandApply}\"/>\n                    </Grid>\n                </TabItem>\n                <TabItem>\n                    <Grid>\n                        <Grid.RowDefinitions>\n                            <RowDefinition Height=\"Auto\"/>\n                            <RowDefinition Height=\"10\"/>\n                            <RowDefinition Height=\"Auto\"/>\n                            <RowDefinition/>\n                            <RowDefinition Height=\"Auto\"/>\n                        </Grid.RowDefinitions>\n                        <TextBlock Text=\"{Binding MainForm_Simulate_Debug_Info}\"/>\n                        <CheckBox Grid.Row=\"2\" Content=\"{Binding MainForm_Simulate_Debug_ForceCached}\" IsChecked=\"{Binding SpecialRules.DebugForceCached}\"/>\n                        <Button Grid.Row=\"4\" Content=\"{Binding MainForm_Simulate_Game_ApplyRuleButton}\" Background=\"{StaticResource brushBoard}\" Command=\"{Binding SpecialRules.CommandApply}\"/>\n                    </Grid>\n                </TabItem>\n            </TabControl>\n            <Grid Grid.Row=\"1\" Grid.RowSpan=\"2\">\n                <Grid.ColumnDefinitions>\n                    <ColumnDefinition Width=\"Auto\"/>\n                    <ColumnDefinition/>\n                </Grid.ColumnDefinitions>\n                <Grid.RowDefinitions>\n                    <RowDefinition Height=\"Auto\"/>\n                    <RowDefinition/>\n                </Grid.RowDefinitions>\n                <StackPanel Margin=\"10,0,0,0\" Grid.Row=\"0\" VerticalAlignment=\"Top\" Orientation=\"Horizontal\">\n                    <TextBlock Text=\"{Binding MainForm_Simulate_WinChance}\"/>\n                    <TextBlock Text=\"{Binding DescChanceAndResult}\" Margin=\"5,0,0,0\"/>\n                </StackPanel>\n                <TextBlock Text=\"{Binding MainForm_Dynamic_Simulate_ChangeBlueHint}\" Margin=\"10,0,0,10\" Grid.Row=\"1\" VerticalAlignment=\"Bottom\" HorizontalAlignment=\"Left\" Visibility=\"{Binding CanSelectBlue, Converter={StaticResource convVis}}\"/>\n                <Rectangle x:Name=\"rectPlayerDeckSlot\" Fill=\"Transparent\" Margin=\"10,0,10,10\" Grid.RowSpan=\"2\" Grid.Column=\"1\" IsHitTestVisible=\"False\" LayoutUpdated=\"rectPlayerDeckSlot_LayoutUpdated\"/>\n            </Grid>\n        </Grid>\n        <local:PlayerDeckPreview x:Name=\"playerDeck\" DataContext=\"{Binding PlayerDeck}\" Background=\"#FFE0FCFF\" BorderBrush=\"{StaticResource brushBorder}\" BorderThickness=\"1\" Width=\"10\" Height=\"10\" VerticalAlignment=\"Top\" HorizontalAlignment=\"Left\"/>\n    </Grid>\n</UserControl>\n"
  },
  {
    "path": "sources/ui/view/PageSimulate.xaml.cs",
    "content": "﻿using System;\nusing System.Windows;\nusing System.Windows.Controls;\n\nnamespace FFTriadBuddy.UI\n{\n    /// <summary>\n    /// Interaction logic for PageSimulate.xaml\n    /// </summary>\n    public partial class PageSimulate : UserControl\n    {\n        public PageSimulate()\n        {\n            InitializeComponent();\n        }\n\n        private void rectPlayerDeckSlot_LayoutUpdated(object sender, EventArgs e)\n        {\n            // position playerDeck to fit inside rectPlayerDeckSlot, anchored with upper right corner\n            // ...and keeping 3x2 aspect ratio\n\n            var cellSize = Math.Min(rectPlayerDeckSlot.ActualWidth / 3, rectPlayerDeckSlot.ActualHeight / 2);\n            playerDeck.Width = cellSize * 3;\n            playerDeck.Height = cellSize * 2;\n\n            var anchorPoint = rectPlayerDeckSlot.TranslatePoint(new Point(rectPlayerDeckSlot.ActualWidth, 0), this);\n            playerDeck.Margin = new Thickness(anchorPoint.X - playerDeck.Width, anchorPoint.Y, 0, 0);\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/controls/NumTextBox.xaml",
    "content": "﻿<UserControl x:Class=\"FFTriadBuddy.UI.NumTextBox\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \n             xmlns:local=\"clr-namespace:FFTriadBuddy.UI\"\n             mc:Ignorable=\"d\" \n             d:DesignHeight=\"40\" d:DesignWidth=\"100\" MouseWheel=\"UserControl_MouseWheel\">\n    <Grid>\n        <Grid.RowDefinitions>\n            <RowDefinition/>\n            <RowDefinition/>\n        </Grid.RowDefinitions>\n        <Grid.ColumnDefinitions>\n            <ColumnDefinition/>\n            <ColumnDefinition Width=\"Auto\"/>\n        </Grid.ColumnDefinitions>\n        <TextBlock x:Name=\"textMarker\" Grid.RowSpan=\"2\" Text=\"888\" Foreground=\"{x:Null}\" />\n        <TextBox x:Name=\"textNum\" Grid.RowSpan=\"2\" HorizontalContentAlignment=\"Right\" VerticalContentAlignment=\"Center\" Width=\"{Binding ActualWidth, Mode=OneWay, ElementName=textMarker}\"/>\n        <RepeatButton BorderThickness=\"0\" Grid.Column=\"1\" Grid.Row=\"0\" Width=\"13\" Background=\"Transparent\" Click=\"RepeatButtonUp_Click\">\n            <Path Fill=\"Black\" Data=\"M 0 3 L 6 3 L 3 0 Z\"/>\n        </RepeatButton>\n        <RepeatButton BorderThickness=\"0\" Grid.Column=\"1\" Grid.Row=\"1\" Width=\"13\" Background=\"Transparent\" Click=\"RepeatButtonDown_Click\">\n            <Path Fill=\"Black\" Data=\"M 0 0 L 3 3 L 6 0 Z\"/>\n        </RepeatButton>\n    </Grid>\n</UserControl>\n"
  },
  {
    "path": "sources/ui/view/controls/NumTextBox.xaml.cs",
    "content": "﻿using System.Linq;\nusing System.Windows;\nusing System.Windows.Controls;\nusing System.Windows.Input;\n\nnamespace FFTriadBuddy.UI\n{\n    /// <summary>\n    /// Interaction logic for NumTextBox.xaml\n    /// </summary>\n    public partial class NumTextBox : UserControl\n    {\n        public static readonly DependencyProperty MinValueProperty = DependencyProperty.RegisterAttached(\"MinValue\", typeof(int), typeof(NumTextBox),\n            new PropertyMetadata(1, (d, e) => (d as NumTextBox)?.OnMinValueChanged((int)e.NewValue)));\n\n        public int MinValue\n        {\n            get { return (int)GetValue(MinValueProperty); }\n            set { SetValue(MinValueProperty, value); }\n        }\n\n        public static readonly DependencyProperty MaxValueProperty = DependencyProperty.RegisterAttached(\"MaxValue\", typeof(int), typeof(NumTextBox),\n            new PropertyMetadata(10, (d, e) => (d as NumTextBox)?.OnMaxValueChanged((int)e.NewValue)));\n\n        public int MaxValue\n        {\n            get { return (int)GetValue(MaxValueProperty); }\n            set { SetValue(MaxValueProperty, value); }\n        }\n\n        public static readonly DependencyProperty ValueProperty = DependencyProperty.RegisterAttached(\"Value\", typeof(int), typeof(NumTextBox),\n            new PropertyMetadata(1, (d, e) => (d as NumTextBox)?.OnValueChanged((int)e.NewValue)));\n\n        public int Value\n        {\n            get { return (int)GetValue(ValueProperty); }\n            set { SetValue(ValueProperty, value); }\n        }\n\n        private bool lockTextUpdate = false;\n\n        public NumTextBox()\n        {\n            InitializeComponent();\n\n            textNum.PreviewTextInput += TextNum_PreviewTextInput;\n            textNum.TextChanged += TextNum_TextChanged;\n\n            textNum.PreviewMouseLeftButtonDown += TextNum_PreviewMouseLeftButtonDown;\n            textNum.GotKeyboardFocus += TextNum_GotKeyboardFocus;\n            textNum.MouseDoubleClick += TextNum_MouseDoubleClick;\n\n            UpdateMask();\n            UpdateText();\n        }\n\n        private void TextNum_MouseDoubleClick(object sender, MouseButtonEventArgs e)\n        {\n            textNum.SelectAll();\n        }\n\n        private void TextNum_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)\n        {\n            textNum.SelectAll();\n        }\n\n        private void TextNum_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)\n        {\n            var textBox = ViewUtils.FindVisualParent(e.OriginalSource as UIElement, x => x is TextBox) as TextBox;\n            if (textBox == textNum && !textNum.IsKeyboardFocusWithin)\n            {\n                textBox.Focus();\n                e.Handled = true;\n            }\n        }\n\n        private void TextNum_TextChanged(object sender, TextChangedEventArgs e)\n        {\n            bool isValid = int.TryParse(textNum.Text, out int newValue);\n            if (isValid)\n            {\n                isValid = (newValue >= MinValue) && (newValue <= MaxValue);\n            }\n\n            if (!isValid)\n            {\n                textNum.Text = Value.ToString();\n                textNum.SelectAll();\n            }\n            else\n            {\n                lockTextUpdate = true;\n                Value = newValue;\n                lockTextUpdate = false;\n            }\n        }\n\n        private void TextNum_PreviewTextInput(object sender, TextCompositionEventArgs e)\n        {\n            if (e.Text == null || !e.Text.All(char.IsDigit))\n            {\n                e.Handled = true;\n            }\n        }\n\n        public void OnMinValueChanged(int minValue)\n        {\n            if (Value < minValue)\n            {\n                Value = minValue;\n            }\n        }\n\n        public void OnMaxValueChanged(int maxValue)\n        {\n            if (Value > maxValue)\n            {\n                Value = maxValue;\n                UpdateMask();\n            }\n        }\n\n        public void OnValueChanged(int newValue)\n        {\n            UpdateText();\n        }\n\n        private void UpdateMask()\n        {\n            string newMask = \"##\"; // 1 more for nicer presentation\n            int testV = MaxValue;\n            while (testV >= 10)\n            {\n                testV /= 10;\n                newMask += '#';\n            }\n\n            textMarker.Text = newMask;\n        }\n\n        private void UpdateText()\n        {\n            if (!lockTextUpdate)\n            {\n                textNum.Text = Value.ToString();\n            }\n        }\n\n        private void UserControl_MouseWheel(object sender, MouseWheelEventArgs e)\n        {\n            if (e.Delta > 0)\n            {\n                RepeatButtonUp_Click(null, null);\n            }\n            else if (e.Delta < 0)\n            {\n                RepeatButtonDown_Click(null, null);\n            }\n        }\n\n        private void RepeatButtonUp_Click(object sender, RoutedEventArgs e)\n        {\n            if (Value < MaxValue)\n            {\n                Value++;\n            }\n        }\n\n        private void RepeatButtonDown_Click(object sender, RoutedEventArgs e)\n        {\n            if (Value > MinValue)\n            {\n                Value--;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/controls/OutlinedTextBlock.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.Globalization;\nusing System.Windows;\nusing System.Windows.Documents;\nusing System.Windows.Markup;\nusing System.Windows.Media;\n\n// Copy pasta:\n// https://stackoverflow.com/a/35262509\n//\n\nnamespace FFTriadBuddy.UI\n{\n    [ContentProperty(\"Text\")]\n    public class OutlinedTextBlock : FrameworkElement\n    {\n        private void UpdatePen()\n        {\n            _Pen = new Pen(Stroke, StrokeThickness)\n            {\n                DashCap = PenLineCap.Round,\n                EndLineCap = PenLineCap.Round,\n                LineJoin = PenLineJoin.Round,\n                StartLineCap = PenLineCap.Round\n            };\n\n            InvalidateVisual();\n        }\n\n        public static readonly DependencyProperty FillProperty = DependencyProperty.Register(\n          \"Fill\",\n          typeof(Brush),\n          typeof(OutlinedTextBlock),\n          new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.AffectsRender));\n\n        public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register(\n          \"Stroke\",\n          typeof(Brush),\n          typeof(OutlinedTextBlock),\n          new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.AffectsRender, StrokePropertyChangedCallback));\n\n        private static void StrokePropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)\n        {\n            (dependencyObject as OutlinedTextBlock)?.UpdatePen();\n        }\n\n        public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register(\n          \"StrokeThickness\",\n          typeof(double),\n          typeof(OutlinedTextBlock),\n          new FrameworkPropertyMetadata(1d, FrameworkPropertyMetadataOptions.AffectsRender, StrokePropertyChangedCallback));\n\n        public static readonly DependencyProperty FontFamilyProperty = TextElement.FontFamilyProperty.AddOwner(\n          typeof(OutlinedTextBlock),\n          new FrameworkPropertyMetadata(OnFormattedTextUpdated));\n\n        public static readonly DependencyProperty FontSizeProperty = TextElement.FontSizeProperty.AddOwner(\n          typeof(OutlinedTextBlock),\n          new FrameworkPropertyMetadata(OnFormattedTextUpdated));\n\n        public static readonly DependencyProperty FontStretchProperty = TextElement.FontStretchProperty.AddOwner(\n          typeof(OutlinedTextBlock),\n          new FrameworkPropertyMetadata(OnFormattedTextUpdated));\n\n        public static readonly DependencyProperty FontStyleProperty = TextElement.FontStyleProperty.AddOwner(\n          typeof(OutlinedTextBlock),\n          new FrameworkPropertyMetadata(OnFormattedTextUpdated));\n\n        public static readonly DependencyProperty FontWeightProperty = TextElement.FontWeightProperty.AddOwner(\n          typeof(OutlinedTextBlock),\n          new FrameworkPropertyMetadata(OnFormattedTextUpdated));\n\n        public static readonly DependencyProperty TextProperty = DependencyProperty.Register(\n          \"Text\",\n          typeof(string),\n          typeof(OutlinedTextBlock),\n          new FrameworkPropertyMetadata(OnFormattedTextInvalidated));\n\n        public static readonly DependencyProperty TextAlignmentProperty = DependencyProperty.Register(\n          \"TextAlignment\",\n          typeof(TextAlignment),\n          typeof(OutlinedTextBlock),\n          new FrameworkPropertyMetadata(OnFormattedTextUpdated));\n\n        public static readonly DependencyProperty TextDecorationsProperty = DependencyProperty.Register(\n          \"TextDecorations\",\n          typeof(TextDecorationCollection),\n          typeof(OutlinedTextBlock),\n          new FrameworkPropertyMetadata(OnFormattedTextUpdated));\n\n        public static readonly DependencyProperty TextTrimmingProperty = DependencyProperty.Register(\n          \"TextTrimming\",\n          typeof(TextTrimming),\n          typeof(OutlinedTextBlock),\n          new FrameworkPropertyMetadata(OnFormattedTextUpdated));\n\n        public static readonly DependencyProperty TextWrappingProperty = DependencyProperty.Register(\n          \"TextWrapping\",\n          typeof(TextWrapping),\n          typeof(OutlinedTextBlock),\n          new FrameworkPropertyMetadata(TextWrapping.NoWrap, OnFormattedTextUpdated));\n\n        private FormattedText _FormattedText;\n        private Geometry _TextGeometry;\n        private Pen _Pen;\n\n        public Brush Fill\n        {\n            get { return (Brush)GetValue(FillProperty); }\n            set { SetValue(FillProperty, value); }\n        }\n\n        public FontFamily FontFamily\n        {\n            get { return (FontFamily)GetValue(FontFamilyProperty); }\n            set { SetValue(FontFamilyProperty, value); }\n        }\n\n        [TypeConverter(typeof(FontSizeConverter))]\n        public double FontSize\n        {\n            get { return (double)GetValue(FontSizeProperty); }\n            set { SetValue(FontSizeProperty, value); }\n        }\n\n        public FontStretch FontStretch\n        {\n            get { return (FontStretch)GetValue(FontStretchProperty); }\n            set { SetValue(FontStretchProperty, value); }\n        }\n\n        public FontStyle FontStyle\n        {\n            get { return (FontStyle)GetValue(FontStyleProperty); }\n            set { SetValue(FontStyleProperty, value); }\n        }\n\n        public FontWeight FontWeight\n        {\n            get { return (FontWeight)GetValue(FontWeightProperty); }\n            set { SetValue(FontWeightProperty, value); }\n        }\n\n        public Brush Stroke\n        {\n            get { return (Brush)GetValue(StrokeProperty); }\n            set { SetValue(StrokeProperty, value); }\n        }\n\n        public double StrokeThickness\n        {\n            get { return (double)GetValue(StrokeThicknessProperty); }\n            set { SetValue(StrokeThicknessProperty, value); }\n        }\n\n        public string Text\n        {\n            get { return (string)GetValue(TextProperty); }\n            set { SetValue(TextProperty, value); }\n        }\n\n        public TextAlignment TextAlignment\n        {\n            get { return (TextAlignment)GetValue(TextAlignmentProperty); }\n            set { SetValue(TextAlignmentProperty, value); }\n        }\n\n        public TextDecorationCollection TextDecorations\n        {\n            get { return (TextDecorationCollection)GetValue(TextDecorationsProperty); }\n            set { SetValue(TextDecorationsProperty, value); }\n        }\n\n        public TextTrimming TextTrimming\n        {\n            get { return (TextTrimming)GetValue(TextTrimmingProperty); }\n            set { SetValue(TextTrimmingProperty, value); }\n        }\n\n        public TextWrapping TextWrapping\n        {\n            get { return (TextWrapping)GetValue(TextWrappingProperty); }\n            set { SetValue(TextWrappingProperty, value); }\n        }\n\n        public OutlinedTextBlock()\n        {\n            UpdatePen();\n            TextDecorations = new TextDecorationCollection();\n        }\n\n        protected override void OnRender(DrawingContext drawingContext)\n        {\n            EnsureGeometry();\n\n            drawingContext.DrawGeometry(null, _Pen, _TextGeometry);\n            drawingContext.DrawGeometry(Fill, null, _TextGeometry);\n        }\n\n        protected override Size MeasureOverride(Size availableSize)\n        {\n            EnsureFormattedText();\n\n            // constrain the formatted text according to the available size\n\n            double w = availableSize.Width;\n            double h = availableSize.Height;\n\n            // the Math.Min call is important - without this constraint (which seems arbitrary, but is the maximum allowable text width), things blow up when availableSize is infinite in both directions\n            // the Math.Max call is to ensure we don't hit zero, which will cause MaxTextHeight to throw\n            _FormattedText.MaxTextWidth = Math.Min(3579139, w);\n            _FormattedText.MaxTextHeight = Math.Max(0.0001d, h);\n\n            // return the desired size\n            return new Size(Math.Ceiling(_FormattedText.Width), Math.Ceiling(_FormattedText.Height));\n        }\n\n        protected override Size ArrangeOverride(Size finalSize)\n        {\n            EnsureFormattedText();\n\n            // update the formatted text with the final size\n            _FormattedText.MaxTextWidth = finalSize.Width;\n            _FormattedText.MaxTextHeight = Math.Max(0.0001d, finalSize.Height);\n\n            // need to re-generate the geometry now that the dimensions have changed\n            _TextGeometry = null;\n\n            return finalSize;\n        }\n\n        private static void OnFormattedTextInvalidated(DependencyObject dependencyObject,\n          DependencyPropertyChangedEventArgs e)\n        {\n            var outlinedTextBlock = (OutlinedTextBlock)dependencyObject;\n            outlinedTextBlock._FormattedText = null;\n            outlinedTextBlock._TextGeometry = null;\n\n            outlinedTextBlock.InvalidateMeasure();\n            outlinedTextBlock.InvalidateVisual();\n        }\n\n        private static void OnFormattedTextUpdated(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)\n        {\n            var outlinedTextBlock = (OutlinedTextBlock)dependencyObject;\n            outlinedTextBlock.UpdateFormattedText();\n            outlinedTextBlock._TextGeometry = null;\n\n            outlinedTextBlock.InvalidateMeasure();\n            outlinedTextBlock.InvalidateVisual();\n        }\n\n        private void EnsureFormattedText()\n        {\n            if (_FormattedText != null)\n            {\n                return;\n            }\n\n            _FormattedText = new FormattedText(\n              Text ?? \"\",\n              CultureInfo.CurrentUICulture,\n              FlowDirection,\n              new Typeface(FontFamily, FontStyle, FontWeight, FontStretch),\n              FontSize,\n              Brushes.Black,\n              VisualTreeHelper.GetDpi(this).PixelsPerDip);\n\n            UpdateFormattedText();\n        }\n\n        private void UpdateFormattedText()\n        {\n            if (_FormattedText == null)\n            {\n                return;\n            }\n\n            _FormattedText.MaxLineCount = TextWrapping == TextWrapping.NoWrap ? 1 : int.MaxValue;\n            _FormattedText.TextAlignment = TextAlignment;\n            _FormattedText.Trimming = TextTrimming;\n\n            _FormattedText.SetFontSize(FontSize);\n            _FormattedText.SetFontStyle(FontStyle);\n            _FormattedText.SetFontWeight(FontWeight);\n            _FormattedText.SetFontFamily(FontFamily);\n            _FormattedText.SetFontStretch(FontStretch);\n            _FormattedText.SetTextDecorations(TextDecorations);\n        }\n\n        private void EnsureGeometry()\n        {\n            if (_TextGeometry != null)\n            {\n                return;\n            }\n\n            EnsureFormattedText();\n            _TextGeometry = _FormattedText.BuildGeometry(new Point(0, 0));\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/controls/SearchableComboBox.xaml",
    "content": "﻿<ComboBox x:Class=\"FFTriadBuddy.UI.SearchableComboBox\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \n             xmlns:local=\"clr-namespace:FFTriadBuddy.UI\"\n             mc:Ignorable=\"d\" \n             d:DesignHeight=\"32\" d:DesignWidth=\"200\" IsEditable=\"True\" IsTextSearchEnabled=\"False\" StaysOpenOnEdit=\"True\">\n</ComboBox>\n"
  },
  {
    "path": "sources/ui/view/controls/SearchableComboBox.xaml.cs",
    "content": "﻿using System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Windows;\nusing System.Windows.Controls;\nusing System.Windows.Controls.Primitives;\nusing System.Windows.Data;\nusing System.Windows.Input;\n\nnamespace FFTriadBuddy.UI\n{\n    // Modified class from:\n    // https://stackoverflow.com/a/58066259\n\n    public partial class SearchableComboBox : ComboBox\n    {\n        public event Action<object, object> SelectionEffectivelyChanged;\n\n        private object effectivelySelectedItem = null;\n        public object EffectivelySelectedItem\n        {\n            get { return effectivelySelectedItem; }\n            set { effectivelySelectedItem = value; }\n        }\n\n        public SortDescription sorter = new SortDescription();\n\n        private TextBox EditableTextBox => GetTemplateChild(\"PART_EditableTextBox\") as TextBox;\n        private bool isTextBoxFreezed = false;\n        private bool lockFinalSelection = false;\n        private string currentFilter = \"\";\n        private UserChange<bool> IsDropDownOpenUC;\n\n        private Predicate<object> orgViewFilter = null;\n\n        public static readonly DependencyProperty ItemDescEvaluatorProperty = DependencyProperty.RegisterAttached(\"ItemDescEvaluator\", typeof(string), typeof(SearchableComboBox));\n        public string ItemDescEvaluator => GetValue(ItemDescEvaluatorProperty).ToString();\n\n        public SearchableComboBox()\n        {\n            InitializeComponent();\n\n            IsDropDownOpenUC = new UserChange<bool>(v => IsDropDownOpen = v);\n            DropDownOpened += FilteredComboBox_DropDownOpened;\n\n            IsEditable = true;\n            IsTextSearchEnabled = true;\n            StaysOpenOnEdit = true;\n            IsReadOnly = false;\n\n            Loaded += (s, e) =>\n            {\n                if (EditableTextBox != null)\n                {\n                    new TextBoxBaseUserChangeTracker(EditableTextBox).UserTextChanged += FilteredComboBox_UserTextChange;\n                }\n            };\n\n            SelectionChanged += (_, __) =>\n            {\n                shouldTriggerSelectedItemChanged = true;\n                if (!isTextBoxFreezed && !lockFinalSelection)\n                {\n                    TriggerSelectedItemChanged();\n                }\n            };\n            SelectionEffectivelyChanged += (_, o) => EffectivelySelectedItem = o;\n        }\n\n        protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)\n        {\n            if (oldValue != null)\n            {\n                var view = CollectionViewSource.GetDefaultView(oldValue);\n                view.Filter = orgViewFilter;\n                view.SortDescriptions.Remove(sorter);\n                view.CollectionChanged -= View_CollectionChanged;\n            }\n\n            if (newValue != null)\n            {\n                var view = CollectionViewSource.GetDefaultView(newValue);\n                orgViewFilter = view.Filter;\n                view.Filter = FilterItem;\n                view.SortDescriptions.Add(sorter);\n                view.CollectionChanged += View_CollectionChanged;\n            }\n\n            lockFinalSelection = true;\n            base.OnItemsSourceChanged(oldValue, newValue);\n            lockFinalSelection = false;\n        }\n\n        private void View_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)\n        {\n            CheckSelectedItem();\n        }\n\n        protected override void OnPreviewKeyDown(KeyEventArgs e)\n        {\n            base.OnPreviewKeyDown(e);\n            if (e.Key == Key.Down && !IsDropDownOpen)\n            {\n                IsDropDownOpen = true;\n                e.Handled = true;\n            }\n            else if (e.Key == Key.Escape)\n            {\n                ClearFilter();\n                Text = \"\";\n                IsDropDownOpen = true;\n            }\n            else if (e.Key == Key.Enter || e.Key == Key.Tab)\n            {\n                CheckSelectedItem();\n                TriggerSelectedItemChanged();\n            }\n        }\n\n        protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e)\n        {\n            base.OnPreviewLostKeyboardFocus(e);\n            CheckSelectedItem();\n            if ((e.OldFocus == this || e.OldFocus == EditableTextBox) && e.NewFocus != this && e.NewFocus != EditableTextBox)\n                TriggerSelectedItemChanged();\n        }\n\n        private string GetItemDescription(object item)\n        {\n            if (!string.IsNullOrEmpty(DisplayMemberPath) && item != null)\n            {\n                var itemDescBinding = new Binding\n                {\n                    Source = item,\n                    Path = new PropertyPath(DisplayMemberPath),\n                    Mode = BindingMode.OneWay\n                };\n                BindingOperations.SetBinding(this, ItemDescEvaluatorProperty, itemDescBinding);\n\n                string descValue = ItemDescEvaluator;\n                return descValue;\n            }\n\n            return item?.ToString();\n        }\n\n        private void CheckSelectedItem()\n        {\n            Text = GetItemDescription(SelectedItem);\n        }\n\n        private bool shouldTriggerSelectedItemChanged = false;\n        private void TriggerSelectedItemChanged()\n        {\n            if (shouldTriggerSelectedItemChanged)\n            {\n                SelectionEffectivelyChanged?.Invoke(this, SelectedItem);\n                shouldTriggerSelectedItemChanged = false;\n            }\n        }\n\n        public void ClearFilter()\n        {\n            if (string.IsNullOrEmpty(currentFilter)) return;\n            currentFilter = \"\";\n            CollectionViewSource.GetDefaultView(ItemsSource).Refresh();\n        }\n\n        private void FilteredComboBox_DropDownOpened(object sender, EventArgs e)\n        {\n            if (IsDropDownOpenUC.IsUserChange)\n                ClearFilter();\n        }\n\n        private void FilteredComboBox_UserTextChange(object sender, EventArgs e)\n        {\n            if (isTextBoxFreezed) return;\n            var tb = EditableTextBox;\n            if (tb.SelectionStart + tb.SelectionLength == tb.Text.Length)\n                currentFilter = tb.Text.Substring(0, tb.SelectionStart).ToLower();\n            else\n                currentFilter = tb.Text.ToLower();\n            RefreshFilter();\n        }\n\n        private void RefreshFilter()\n        {\n            if (ItemsSource == null) return;\n\n            var view = CollectionViewSource.GetDefaultView(ItemsSource);\n            FreezTextBoxState(() =>\n            {\n                var isDropDownOpen = IsDropDownOpen;\n                //always hide because showing it enables the user to pick with up and down keys, otherwise it's not working because of the glitch in view.Refresh()\n                IsDropDownOpenUC.Set(false);\n                view.Refresh();\n\n                if (!string.IsNullOrEmpty(currentFilter) || isDropDownOpen)\n                    IsDropDownOpenUC.Set(true);\n\n                if (SelectedItem == null)\n                {\n                    foreach (var itm in ItemsSource)\n                        if (GetItemDescription(itm) == Text)\n                        {\n                            SelectedItem = itm;\n                            break;\n                        }\n                }\n            });\n        }\n\n        private void FreezTextBoxState(Action action)\n        {\n            isTextBoxFreezed = true;\n            var tb = EditableTextBox;\n            var text = Text;\n            var selStart = tb.SelectionStart;\n            var selLen = tb.SelectionLength;\n            action();\n            Text = text;\n            tb.SelectionStart = selStart;\n            tb.SelectionLength = selLen;\n            isTextBoxFreezed = false;\n        }\n\n        private bool FilterItem(object value)\n        {\n            if (orgViewFilter != null && !orgViewFilter(value)) return false;\n            if (value == null) return false;\n            if (currentFilter.Length == 0) return true;\n\n            return GetItemDescription(value).ToLower().Contains(currentFilter);\n        }\n\n        private class TextBoxBaseUserChangeTracker\n        {\n            private bool IsTextInput { get; set; }\n\n            public TextBoxBase TextBoxBase { get; set; }\n            private List<Key> PressedKeys = new List<Key>();\n            public event EventHandler UserTextChanged;\n            private string LastText;\n\n            public TextBoxBaseUserChangeTracker(TextBoxBase textBoxBase)\n            {\n                TextBoxBase = textBoxBase;\n                LastText = TextBoxBase.ToString();\n\n                textBoxBase.PreviewTextInput += (s, e) =>\n                {\n                    IsTextInput = true;\n                };\n\n                textBoxBase.TextChanged += (s, e) =>\n                {\n                    var isUserChange = PressedKeys.Count > 0 || IsTextInput || LastText == TextBoxBase.ToString();\n                    IsTextInput = false;\n                    LastText = TextBoxBase.ToString();\n                    if (isUserChange)\n                        UserTextChanged?.Invoke(this, e);\n                };\n\n                textBoxBase.PreviewKeyDown += (s, e) =>\n                {\n                    switch (e.Key)\n                    {\n                        case Key.Back:\n                        case Key.Space:\n                            if (!PressedKeys.Contains(e.Key))\n                                PressedKeys.Add(e.Key);\n                            break;\n                    }\n                    if (e.Key == Key.Back)\n                    {\n                        var textBox = textBoxBase as TextBox;\n                        if (textBox.SelectionStart > 0 && textBox.SelectionLength > 0 && (textBox.SelectionStart + textBox.SelectionLength) == textBox.Text.Length)\n                        {\n                            textBox.SelectionStart--;\n                            textBox.SelectionLength++;\n                            e.Handled = true;\n                            UserTextChanged?.Invoke(this, e);\n                        }\n                    }\n                };\n\n                textBoxBase.PreviewKeyUp += (s, e) =>\n                {\n                    if (PressedKeys.Contains(e.Key))\n                        PressedKeys.Remove(e.Key);\n                };\n\n                textBoxBase.LostFocus += (s, e) =>\n                {\n                    PressedKeys.Clear();\n                    IsTextInput = false;\n                };\n            }\n        }\n\n        private class UserChange<T>\n        {\n            private Action<T> action;\n\n            public bool IsUserChange { get; private set; } = true;\n\n            public UserChange(Action<T> action)\n            {\n                this.action = action;\n            }\n\n            public void Set(T val)\n            {\n                try\n                {\n                    IsUserChange = false;\n                    action(val);\n                }\n                finally\n                {\n                    IsUserChange = true;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/controls-triad/CardGridView.xaml",
    "content": "﻿<UserControl x:Class=\"FFTriadBuddy.UI.CardGridView\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \n             xmlns:local=\"clr-namespace:FFTriadBuddy.UI\"\n             mc:Ignorable=\"d\" \n             d:DataContext=\"{d:DesignInstance Type=local:CardCollectionViewModel, IsDesignTimeCreatable=True}\"\n             d:DesignHeight=\"600\" d:DesignWidth=\"500\" SizeChanged=\"UserControl_SizeChanged\">\n    <UserControl.Resources>\n        <SolidColorBrush x:Key=\"BrushMaskDisabled\" Color=\"#a0FF0000\"/>\n        <SolidColorBrush x:Key=\"BrushDarkBorder\" Color=\"DarkGray\"/>\n    </UserControl.Resources>\n    <StackPanel>\n        <Border Background=\"{StaticResource BrushDarkBorder}\" BorderThickness=\"0\">\n            <TextBlock x:Name=\"gridName\" Text=\"{Binding Name}\" FontSize=\"10px\" Padding=\"2,0,0,0\" HorizontalAlignment=\"Center\"/>\n        </Border>\n        <Border BorderBrush=\"{StaticResource BrushDarkBorder}\" BorderThickness=\"1\">\n            <ItemsControl ItemsSource=\"{Binding Cards}\">\n                <ItemsControl.ItemsPanel>\n                    <ItemsPanelTemplate>\n                        <UniformGrid x:Name=\"cardGrid\" Columns=\"5\" Rows=\"6\"/>\n                    </ItemsPanelTemplate>\n                </ItemsControl.ItemsPanel>\n                <ItemsControl.ItemTemplate>\n                    <DataTemplate>\n                        <Grid Margin=\"1\" MouseDown=\"CardView_MouseDown\" ToolTip=\"{Binding CardModel.NameLocalized}\">\n                            <Image Source=\"{Binding CardImage}\"/>\n                            <Rectangle Name=\"mask\" Fill=\"{StaticResource BrushMaskDisabled}\" Visibility=\"Hidden\"/>\n                        </Grid>\n                        <DataTemplate.Triggers>\n                            <DataTrigger Binding=\"{Binding CardModel.IsOwned}\" Value=\"False\">\n                                <Setter Property=\"Visibility\" Value=\"Visible\" TargetName=\"mask\"/>\n                            </DataTrigger>\n                        </DataTemplate.Triggers>\n                    </DataTemplate>\n                </ItemsControl.ItemTemplate>\n            </ItemsControl>\n        </Border>\n    </StackPanel>\n</UserControl>\n"
  },
  {
    "path": "sources/ui/view/controls-triad/CardGridView.xaml.cs",
    "content": "﻿using System;\nusing System.Windows;\nusing System.Windows.Controls;\nusing System.Windows.Input;\n\nnamespace FFTriadBuddy.UI\n{\n    /// <summary>\n    /// Interaction logic for CardGridView.xaml\n    /// </summary>\n    public partial class CardGridView : UserControl\n    {\n        public CardGridView()\n        {\n            InitializeComponent();\n        }\n\n        private void CardView_MouseDown(object sender, MouseButtonEventArgs e)\n        {\n            var controlOb = sender as FrameworkElement;\n            var cardVM = controlOb.DataContext as CardViewModel;\n            var gridVM = DataContext as CardCollectionViewModel;\n\n            if (gridVM.CommandSelect.CanExecute(cardVM))\n            {\n                gridVM.CommandSelect.Execute(cardVM);\n            }\n        }\n\n        private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)\n        {\n            // maintain aspect ratio\n            // TODO: read those from cardGrid item?\n            int numRows = 6;\n            int numColumns = 5;\n\n            Width = numColumns * Math.Max(0, ActualHeight - gridName.ActualHeight) / numRows;\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/controls-triad/CardView.xaml",
    "content": "﻿<UserControl x:Class=\"FFTriadBuddy.UI.CardView\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \n             xmlns:local=\"clr-namespace:FFTriadBuddy.UI\"\n             mc:Ignorable=\"d\" \n             d:DataContext=\"{d:DesignInstance Type=local:CardViewModel, IsDesignTimeCreatable=True}\"\n             d:DesignHeight=\"52\" d:DesignWidth=\"52\">\n    <UserControl.Resources>\n        <Color x:Key=\"ColorBorderBlue\">#87cefa</Color>\n        <Color x:Key=\"ColorBorderBlueHL\">#054f7d</Color>\n        <Color x:Key=\"ColorBorderRed\">#fa8795</Color>\n        <Color x:Key=\"ColorBorderRedHL\">#7d0513</Color>\n        <SolidColorBrush x:Key=\"BrushBorderBlue\" Color=\"{StaticResource ColorBorderBlue}\"/>\n        <SolidColorBrush x:Key=\"BrushBorderBlueHL\" Color=\"{StaticResource ColorBorderBlueHL}\"/>\n        <SolidColorBrush x:Key=\"BrushBorderRed\" Color=\"{StaticResource ColorBorderRed}\"/>\n        <SolidColorBrush x:Key=\"BrushBorderRedHL\" Color=\"{StaticResource ColorBorderRedHL}\"/>\n        <SolidColorBrush x:Key=\"BrushBorderLocked\" Color=\"Gray\"/>\n        <SolidColorBrush x:Key=\"BrushNumSide\" Color=\"White\"/>\n        <SolidColorBrush x:Key=\"BrushNumOutline\" Color=\"Black\"/>\n        <SolidColorBrush x:Key=\"BrushNumModPositive\" Color=\"#3bff3b\"/>\n        <SolidColorBrush x:Key=\"BrushNumModNegative\" Color=\"#ff3b3b\"/>\n        <SolidColorBrush x:Key=\"BrushMaskHidden\" Color=\"#B8860B\"/>\n        <SolidColorBrush x:Key=\"BrushMaskHiddenDisabled\" Color=\"#80B8860B\"/>\n        <SolidColorBrush x:Key=\"BrushMaskDisabled\" Color=\"#66FF0000\"/>\n        <Thickness x:Key=\"BorderMargin\" Left=\"3\" Top=\"3\" Right=\"3\" Bottom=\"3\"/>\n\n        <Style TargetType=\"{x:Type local:OutlinedTextBlock}\" x:Key=\"textStyle\">\n            <Setter Property=\"FontSize\" Value=\"15\"/>\n            <Setter Property=\"IsHitTestVisible\" Value=\"False\"/>\n            <Setter Property=\"StrokeThickness\" Value=\"3\"/>\n            <Setter Property=\"Stroke\" Value=\"#cc000000\"/>\n            <Style.Triggers>\n                <DataTrigger Binding=\"{Binding IsUsingImageBig}\" Value=\"True\">\n                    <Setter Property=\"FontSize\" Value=\"28\"/>\n                    <Setter Property=\"StrokeThickness\" Value=\"5\"/>\n                </DataTrigger>\n            </Style.Triggers>\n        </Style>\n        <Style TargetType=\"{x:Type FrameworkElement}\" x:Key=\"detailsStyle\">\n            <Setter Property=\"Visibility\" Value=\"Visible\"/>\n            <Setter Property=\"IsHitTestVisible\" Value=\"False\"/>\n            <Style.Triggers>\n                <DataTrigger Binding=\"{Binding IsShowingDetails}\" Value=\"False\">\n                    <Setter Property=\"Visibility\" Value=\"Hidden\"/>\n                </DataTrigger>\n                <DataTrigger Binding=\"{Binding CardDragMode}\" Value=\"DragOut\">\n                    <Setter Property=\"Visibility\" Value=\"Hidden\"/>\n                </DataTrigger>\n                <DataTrigger Binding=\"{Binding CardDragMode}\" Value=\"DragIn\">\n                    <Setter Property=\"Visibility\" Value=\"Hidden\"/>\n                </DataTrigger>\n            </Style.Triggers>\n        </Style>\n        <Style TargetType=\"{x:Type FrameworkElement}\" BasedOn=\"{StaticResource detailsStyle}\" x:Key=\"detailsStyleSmall\">\n            <Style.Triggers>\n                <DataTrigger Binding=\"{Binding IsUsingImageBig}\" Value=\"True\">\n                    <Setter Property=\"Visibility\" Value=\"Hidden\"/>\n                </DataTrigger>\n            </Style.Triggers>\n        </Style>\n        <Style TargetType=\"{x:Type FrameworkElement}\" BasedOn=\"{StaticResource detailsStyle}\" x:Key=\"detailsStyleBig\">\n            <Style.Triggers>\n                <DataTrigger Binding=\"{Binding IsUsingImageBig}\" Value=\"False\">\n                    <Setter Property=\"Visibility\" Value=\"Collapsed\"/>\n                </DataTrigger>\n            </Style.Triggers>\n        </Style>\n        <Style TargetType=\"{x:Type local:OutlinedTextBlock}\" BasedOn=\"{StaticResource textStyle}\"/>\n    </UserControl.Resources>\n    <Viewbox ToolTip=\"{Binding Tooltip}\" Margin=\"0\" RenderTransformOrigin=\"0.5,0.5\">\n        <Grid>\n            <Rectangle x:Name=\"rectBackground\">\n                <Rectangle.Style>\n                    <Style TargetType=\"{x:Type Rectangle}\">\n                        <Setter Property=\"Visibility\" Value=\"Hidden\"/>\n                        <Setter Property=\"IsHitTestVisible\" Value=\"False\"/>\n                        <Setter Property=\"Fill\" Value=\"Transparent\"/>\n                        <Style.Triggers>\n                            <DataTrigger Binding=\"{Binding IsUsingImageBig, FallbackValue=False}\" Value=\"True\">\n                                <Setter Property=\"Visibility\" Value=\"Visible\"/>\n                            </DataTrigger>\n                            <DataTrigger Binding=\"{Binding CardOwner}\" Value=\"Blue\">\n                                <Setter Property=\"Fill\" Value=\"{StaticResource BrushBorderBlue}\"/>\n                            </DataTrigger>\n                            <DataTrigger Binding=\"{Binding CardOwner}\" Value=\"Red\">\n                                <Setter Property=\"Fill\" Value=\"{StaticResource BrushBorderRed}\"/>\n                            </DataTrigger>\n                            <DataTrigger Binding=\"{Binding IsShowingLock}\" Value=\"True\">\n                                <Setter Property=\"Fill\" Value=\"{StaticResource BrushBorderLocked}\"/>\n                            </DataTrigger>\n                        </Style.Triggers>\n                    </Style>\n                </Rectangle.Style>\n            </Rectangle>\n            <Image x:Name=\"imageCard\" Source=\"{Binding CardImage}\"/>\n            <Image Source=\"{Binding DragImage}\" IsHitTestVisible=\"False\">\n                <Image.RenderTransform>\n                    <TranslateTransform/>\n                </Image.RenderTransform>\n                <Image.Resources>\n                    <Storyboard x:Key=\"animDragIn\" >\n                        <DoubleAnimation Storyboard.TargetProperty=\"Opacity\" From=\"0.75\" To=\"1\" AutoReverse=\"False\" Duration=\"0:0:0.25\" RepeatBehavior=\"1x\" />\n                        <DoubleAnimation Storyboard.TargetProperty=\"(UIElement.RenderTransform).(TranslateTransform.Y)\" From=\"-10\" To=\"0\" AutoReverse=\"False\" Duration=\"0:0:0.25\" RepeatBehavior=\"1x\"/>\n                    </Storyboard>\n                    <Storyboard x:Key=\"animDragOut\" >\n                        <DoubleAnimation Storyboard.TargetProperty=\"Opacity\" From=\"0.75\" To=\"1\" AutoReverse=\"False\" Duration=\"0:0:0.25\" RepeatBehavior=\"1x\" />\n                        <DoubleAnimation Storyboard.TargetProperty=\"(UIElement.RenderTransform).(TranslateTransform.Y)\" From=\"10\" To=\"0\" AutoReverse=\"False\" Duration=\"0:0:0.25\" RepeatBehavior=\"1x\"/>\n                    </Storyboard>\n                </Image.Resources>\n                <Image.Style>\n                    <Style TargetType=\"{x:Type Image}\">\n                        <Setter Property=\"Visibility\" Value=\"Hidden\"/>\n                        <Style.Triggers>\n                            <DataTrigger Binding=\"{Binding CardDragMode}\" Value=\"DragIn\">\n                                <DataTrigger.EnterActions>\n                                    <BeginStoryboard Name=\"animDragIn\" Storyboard=\"{StaticResource animDragIn}\" />\n                                </DataTrigger.EnterActions>\n                                <DataTrigger.ExitActions>\n                                    <StopStoryboard BeginStoryboardName=\"animDragIn\"/>\n                                </DataTrigger.ExitActions>\n                                <Setter Property=\"Visibility\" Value=\"Visible\"/>\n                            </DataTrigger>\n                            <DataTrigger Binding=\"{Binding CardDragMode}\" Value=\"DragOut\">\n                                <DataTrigger.EnterActions>\n                                    <BeginStoryboard Name=\"animDragOut\" Storyboard=\"{StaticResource animDragOut}\" />\n                                </DataTrigger.EnterActions>\n                                <DataTrigger.ExitActions>\n                                    <StopStoryboard BeginStoryboardName=\"animDragOut\"/>\n                                </DataTrigger.ExitActions>\n                                <Setter Property=\"Visibility\" Value=\"Visible\"/>\n                            </DataTrigger>\n                        </Style.Triggers>\n                    </Style>\n                </Image.Style>\n            </Image>\n            <Rectangle x:Name=\"rectMask\">\n                <Rectangle.Style>\n                    <Style TargetType=\"{x:Type Rectangle}\">\n                        <Setter Property=\"Visibility\" Value=\"Hidden\"/>\n                        <Setter Property=\"IsHitTestVisible\" Value=\"False\"/>\n                        <Style.Triggers>\n                            <DataTrigger Binding=\"{Binding HasCardImage, FallbackValue=False}\" Value=\"False\">\n                                <Setter Property=\"Visibility\" Value=\"Visible\"/>\n                                <Setter Property=\"Fill\" Value=\"{StaticResource BrushMaskDisabled}\"/>\n                            </DataTrigger>\n                            <DataTrigger Binding=\"{Binding HasCardImage}\" Value=\"False\">\n                                <Setter Property=\"IsHitTestVisible\" Value=\"True\"/>\n                            </DataTrigger>\n                            <MultiDataTrigger>\n                                <MultiDataTrigger.Conditions>\n                                    <Condition Binding=\"{Binding HasCardImage}\" Value=\"False\"/>\n                                    <Condition Binding=\"{Binding IsHighlighted}\" Value=\"True\"/>\n                                </MultiDataTrigger.Conditions>\n                                <Setter Property=\"Visibility\" Value=\"Visible\"/>\n                                <Setter Property=\"Fill\" Value=\"{StaticResource BrushMaskHidden}\"/>\n                            </MultiDataTrigger>\n                            <MultiDataTrigger>\n                                <MultiDataTrigger.Conditions>\n                                    <Condition Binding=\"{Binding HasCardImage}\" Value=\"False\"/>\n                                    <Condition Binding=\"{Binding IsHighlighted}\" Value=\"False\"/>\n                                </MultiDataTrigger.Conditions>\n                                <Setter Property=\"Visibility\" Value=\"Visible\"/>\n                                <Setter Property=\"Fill\" Value=\"{StaticResource BrushMaskHiddenDisabled}\"/>\n                            </MultiDataTrigger>\n                            <MultiDataTrigger>\n                                <MultiDataTrigger.Conditions>\n                                    <Condition Binding=\"{Binding HasCardImage}\" Value=\"False\"/>\n                                    <Condition Binding=\"{Binding IsShowingDetails}\" Value=\"False\"/>\n                                    <Condition Binding=\"{Binding IsHidden}\" Value=\"False\"/>\n                                </MultiDataTrigger.Conditions>\n                                <Setter Property=\"Visibility\" Value=\"Hidden\"/>\n                            </MultiDataTrigger>\n                        </Style.Triggers>\n                    </Style>\n                </Rectangle.Style>\n            </Rectangle>\n            <Grid x:Name=\"gridSmall\">\n                <Grid.Style>\n                    <Style TargetType=\"{x:Type Grid}\" BasedOn=\"{StaticResource detailsStyleSmall}\">\n                        <Style.Triggers>\n                            <DataTrigger Binding=\"{Binding CardOwner}\" Value=\"Blue\">\n                                <Setter Property=\"Margin\" Value=\"{StaticResource BorderMargin}\"/>\n                            </DataTrigger>\n                            <DataTrigger Binding=\"{Binding CardOwner}\" Value=\"Red\">\n                                <Setter Property=\"Margin\" Value=\"{StaticResource BorderMargin}\"/>\n                            </DataTrigger>\n                        </Style.Triggers>\n                    </Style>\n                </Grid.Style>\n                <Image x:Name=\"imageRarity\" HorizontalAlignment=\"Left\" VerticalAlignment=\"Top\" Source=\"{Binding RarityImage}\" Height=\"17\" Width=\"18\" Opacity=\"0.75\"/>\n                <Image x:Name=\"imageType\" HorizontalAlignment=\"Right\" VerticalAlignment=\"Top\" Source=\"{Binding TypeImage}\" Height=\"20\" Width=\"20\" Opacity=\"0.75\"/>\n                <Polygon Points=\"0, 13, 10, 19, 10, 33, 0, 39\" Fill=\"Black\" Opacity=\"0.3\"/>\n                <Polygon Points=\"52, 13, 42, 19, 42, 33, 52, 39\" Fill=\"Black\" Opacity=\"0.3\"/>\n                <Polygon Points=\"17, 0, 21, 16, 31, 16, 35, 0\" Fill=\"Black\" Opacity=\"0.3\"/>\n                <Polygon Points=\"17, 52, 21, 36, 31, 36, 35, 52\" Fill=\"Black\" Opacity=\"0.3\"/>\n                <local:OutlinedTextBlock x:Name=\"textNumU\" Text=\"{Binding NumUp, StringFormat=\\{0:X\\}}\" Fill=\"{StaticResource BrushNumSide}\" Margin=\"0,-2,0,0\" HorizontalAlignment=\"Center\" VerticalAlignment=\"Top\"/>\n                <local:OutlinedTextBlock x:Name=\"textNumL\" Text=\"{Binding NumLeft, StringFormat=\\{0:X\\}}\" Fill=\"{StaticResource BrushNumSide}\" Margin=\"0,0,0,0\" HorizontalAlignment=\"Right\" VerticalAlignment=\"Center\"/>\n                <local:OutlinedTextBlock x:Name=\"textNumD\" Text=\"{Binding NumDown, StringFormat=\\{0:X\\}}\" Fill=\"{StaticResource BrushNumSide}\" Margin=\"0,0,0,-1\" VerticalAlignment=\"Bottom\" HorizontalAlignment=\"Center\"/>\n                <local:OutlinedTextBlock x:Name=\"textNumR\" Text=\"{Binding NumRight, StringFormat=\\{0:X\\}}\"  Fill=\"{StaticResource BrushNumSide}\" Margin=\"1,0,0,0\" HorizontalAlignment=\"Left\" VerticalAlignment=\"Center\"/>\n                <local:OutlinedTextBlock x:Name=\"textMod\" Text=\"{Binding NumMod}\" HorizontalAlignment=\"Center\" VerticalAlignment=\"Center\" Stroke=\"#80000000\">\n                    <local:OutlinedTextBlock.Style>\n                        <Style TargetType=\"{x:Type local:OutlinedTextBlock}\" BasedOn=\"{StaticResource textStyle}\">\n                            <Setter Property=\"Fill\" Value=\"{StaticResource BrushNumModPositive}\"/>\n                            <Style.Triggers>\n                                <DataTrigger Binding=\"{Binding IsModNegative}\" Value=\"True\">\n                                    <Setter Property=\"Fill\" Value=\"{StaticResource BrushNumModNegative}\"/>\n                                </DataTrigger>\n                                <DataTrigger Binding=\"{Binding NumMod}\" Value=\"0\">\n                                    <Setter Property=\"Visibility\" Value=\"Hidden\"/>\n                                </DataTrigger>\n                            </Style.Triggers>\n                        </Style>\n                    </local:OutlinedTextBlock.Style>\n                </local:OutlinedTextBlock>\n            </Grid>\n            <Grid x:Name=\"gridBig\" Style=\"{StaticResource detailsStyleBig}\">\n                <Image x:Name=\"imageRarityB\" HorizontalAlignment=\"Left\" VerticalAlignment=\"Top\" Source=\"{Binding RarityImage}\" Height=\"25.5\" Width=\"27\" Opacity=\"0.75\"/>\n                <Image x:Name=\"imageTypeB\" HorizontalAlignment=\"Right\" VerticalAlignment=\"Top\" Source=\"{Binding TypeImage}\" Height=\"30\" Width=\"30\" Opacity=\"0.75\"/>\n                <Polygon Points=\"0, 48, 18, 54, 18, 76, 0, 82\" Fill=\"Black\" Opacity=\"0.3\"/>\n                <Polygon Points=\"104, 48, 86, 54, 86, 76, 104, 82\" Fill=\"Black\" Opacity=\"0.3\"/>\n                <Polygon Points=\"40, 0, 43, 32, 60, 32, 63, 0\" Fill=\"Black\" Opacity=\"0.3\"/>\n                <Polygon Points=\"40, 128, 43, 96, 60, 96, 63, 128\" Fill=\"Black\" Opacity=\"0.3\"/>\n                <local:OutlinedTextBlock x:Name=\"textNumUB\" Text=\"{Binding NumUp, StringFormat=\\{0:X\\}}\" Fill=\"{StaticResource BrushNumSide}\" Margin=\"0,-2,0,0\" HorizontalAlignment=\"Center\" VerticalAlignment=\"Top\"/>\n                <local:OutlinedTextBlock x:Name=\"textNumLB\" Text=\"{Binding NumLeft, StringFormat=\\{0:X\\}}\" Fill=\"{StaticResource BrushNumSide}\" Margin=\"0,0,0,0\" HorizontalAlignment=\"Right\" VerticalAlignment=\"Center\"/>\n                <local:OutlinedTextBlock x:Name=\"textNumDB\" Text=\"{Binding NumDown, StringFormat=\\{0:X\\}}\" Fill=\"{StaticResource BrushNumSide}\" Margin=\"0,0,0,-1\" VerticalAlignment=\"Bottom\" HorizontalAlignment=\"Center\"/>\n                <local:OutlinedTextBlock x:Name=\"textNumRB\" Text=\"{Binding NumRight, StringFormat=\\{0:X\\}}\"  Fill=\"{StaticResource BrushNumSide}\" Margin=\"1,0,0,0\" HorizontalAlignment=\"Left\" VerticalAlignment=\"Center\"/>\n                <local:OutlinedTextBlock x:Name=\"textModB\" Text=\"{Binding NumMod}\" HorizontalAlignment=\"Center\" VerticalAlignment=\"Center\" Stroke=\"#80000000\">\n                    <local:OutlinedTextBlock.Style>\n                        <Style TargetType=\"{x:Type local:OutlinedTextBlock}\" BasedOn=\"{StaticResource textStyle}\">\n                            <Setter Property=\"Fill\" Value=\"{StaticResource BrushNumModPositive}\"/>\n                            <Style.Triggers>\n                                <DataTrigger Binding=\"{Binding IsModNegative}\" Value=\"True\">\n                                    <Setter Property=\"Fill\" Value=\"{StaticResource BrushNumModNegative}\"/>\n                                </DataTrigger>\n                                <DataTrigger Binding=\"{Binding NumMod}\" Value=\"0\">\n                                    <Setter Property=\"Visibility\" Value=\"Hidden\"/>\n                                </DataTrigger>\n                            </Style.Triggers>\n                        </Style>\n                    </local:OutlinedTextBlock.Style>\n                </local:OutlinedTextBlock>\n            </Grid>\n            <Rectangle x:Name=\"rectBorder\" StrokeThickness=\"5\" IsHitTestVisible=\"False\">\n                <Rectangle.Style>\n                    <Style TargetType=\"{x:Type Rectangle}\">\n                        <Style.Resources>\n                            <Storyboard x:Key=\"animBlinkBlue\" >\n                                <ColorAnimationUsingKeyFrames Storyboard.TargetProperty=\"Stroke.(SolidColorBrush.Color)\" AutoReverse=\"False\" Duration=\"0:0:1.5\" RepeatBehavior=\"Forever\">\n                                    <ColorAnimationUsingKeyFrames.KeyFrames>\n                                        <DiscreteColorKeyFrame Value=\"{StaticResource ColorBorderBlueHL}\" KeyTime=\"0%\"/>\n                                        <DiscreteColorKeyFrame Value=\"Transparent\" KeyTime=\"50%\"/>\n                                    </ColorAnimationUsingKeyFrames.KeyFrames>\n                                </ColorAnimationUsingKeyFrames>\n                            </Storyboard>\n                            <Storyboard x:Key=\"animBlinkRed\" >\n                                <ColorAnimationUsingKeyFrames Storyboard.TargetProperty=\"Stroke.(SolidColorBrush.Color)\" AutoReverse=\"False\" Duration=\"0:0:1.5\" RepeatBehavior=\"Forever\">\n                                    <ColorAnimationUsingKeyFrames.KeyFrames>\n                                        <DiscreteColorKeyFrame Value=\"{StaticResource ColorBorderRedHL}\" KeyTime=\"0%\"/>\n                                        <DiscreteColorKeyFrame Value=\"Transparent\" KeyTime=\"50%\"/>\n                                    </ColorAnimationUsingKeyFrames.KeyFrames>\n                                </ColorAnimationUsingKeyFrames>\n                            </Storyboard>\n                        </Style.Resources>\n\n                        <Setter Property=\"Stroke\" Value=\"Transparent\"/>\n                        <Style.Triggers>\n                            <MultiDataTrigger>\n                                <MultiDataTrigger.Conditions>\n                                    <Condition Binding=\"{Binding CardOwner}\" Value=\"Blue\"/>\n                                    <Condition Binding=\"{Binding IsUsingImageBig}\" Value=\"False\"/>\n                                </MultiDataTrigger.Conditions>\n                                <Setter Property=\"Stroke\" Value=\"{StaticResource BrushBorderBlue}\"/>\n                            </MultiDataTrigger>\n                            <MultiDataTrigger>\n                                <MultiDataTrigger.Conditions>\n                                    <Condition Binding=\"{Binding CardOwner}\" Value=\"Blue\"/>\n                                    <Condition Binding=\"{Binding IsHighlighted}\" Value=\"True\"/>\n                                </MultiDataTrigger.Conditions>\n                                <Setter Property=\"Stroke\" Value=\"{StaticResource BrushBorderBlueHL}\"/>\n                            </MultiDataTrigger>\n                            <MultiDataTrigger>\n                                <MultiDataTrigger.Conditions>\n                                    <Condition Binding=\"{Binding CardOwner}\" Value=\"Blue\"/>\n                                    <Condition Binding=\"{Binding IsHighlighted}\" Value=\"True\"/>\n                                    <Condition Binding=\"{Binding IsBlinkingOnHighlight, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:CardView}}}\" Value=\"True\"/>\n                                </MultiDataTrigger.Conditions>\n                                <MultiDataTrigger.EnterActions>\n                                    <BeginStoryboard Name=\"blinkBlue\" Storyboard=\"{StaticResource animBlinkBlue}\" />\n                                </MultiDataTrigger.EnterActions>\n                                <MultiDataTrigger.ExitActions>\n                                    <StopStoryboard BeginStoryboardName=\"blinkBlue\"/>\n                                </MultiDataTrigger.ExitActions>\n                                <Setter Property=\"Stroke\" Value=\"{StaticResource BrushBorderBlueHL}\"/>\n                            </MultiDataTrigger>\n                            <MultiDataTrigger>\n                                <MultiDataTrigger.Conditions>\n                                    <Condition Binding=\"{Binding CardOwner}\" Value=\"Red\"/>\n                                    <Condition Binding=\"{Binding IsUsingImageBig}\" Value=\"False\"/>\n                                </MultiDataTrigger.Conditions>\n                                <Setter Property=\"Stroke\" Value=\"{StaticResource BrushBorderRed}\"/>\n                            </MultiDataTrigger>\n                            <MultiDataTrigger>\n                                <MultiDataTrigger.Conditions>\n                                    <Condition Binding=\"{Binding CardOwner}\" Value=\"Red\"/>\n                                    <Condition Binding=\"{Binding IsHighlighted}\" Value=\"True\"/>\n                                </MultiDataTrigger.Conditions>\n                                <Setter Property=\"Stroke\" Value=\"{StaticResource BrushBorderRedHL}\"/>\n                            </MultiDataTrigger>\n                            <MultiDataTrigger>\n                                <MultiDataTrigger.Conditions>\n                                    <Condition Binding=\"{Binding CardOwner}\" Value=\"Red\"/>\n                                    <Condition Binding=\"{Binding IsHighlighted}\" Value=\"True\"/>\n                                    <Condition Binding=\"{Binding IsBlinkingOnHighlight, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:CardView}}}\" Value=\"True\"/>\n                                </MultiDataTrigger.Conditions>\n                                <MultiDataTrigger.EnterActions>\n                                    <BeginStoryboard Name=\"blinkRed\" Storyboard=\"{StaticResource animBlinkRed}\" />\n                                </MultiDataTrigger.EnterActions>\n                                <MultiDataTrigger.ExitActions>\n                                    <StopStoryboard BeginStoryboardName=\"blinkRed\"/>\n                                </MultiDataTrigger.ExitActions>\n                                <Setter Property=\"Stroke\" Value=\"{StaticResource BrushBorderRedHL}\"/>\n                            </MultiDataTrigger>\n                            <MultiDataTrigger>\n                                <MultiDataTrigger.Conditions>\n                                    <Condition Binding=\"{Binding IsShowingLock}\" Value=\"True\"/>\n                                    <Condition Binding=\"{Binding IsUsingImageBig}\" Value=\"False\"/>\n                                </MultiDataTrigger.Conditions>\n                                <Setter Property=\"Stroke\" Value=\"{StaticResource BrushBorderLocked}\"/>\n                            </MultiDataTrigger>\n                        </Style.Triggers>\n                    </Style>\n                </Rectangle.Style>\n            </Rectangle>\n        </Grid>\n    </Viewbox>\n</UserControl>\n"
  },
  {
    "path": "sources/ui/view/controls-triad/CardView.xaml.cs",
    "content": "﻿using System.Windows;\nusing System.Windows.Controls;\n\nnamespace FFTriadBuddy.UI\n{\n    /// <summary>\n    /// Interaction logic for CardView.xaml\n    /// </summary>\n    public partial class CardView : UserControl\n    {\n        public static readonly DependencyProperty IsBlinkingOnHighlightProperty = DependencyProperty.Register(\"IsBlinkingOnHighlight\", typeof(bool), typeof(DeckView));\n\n        public bool IsBlinkingOnHighlight\n        {\n            get { return (bool)GetValue(IsBlinkingOnHighlightProperty); }\n            set { SetValue(IsBlinkingOnHighlightProperty, value); }\n        }\n\n        public CardView()\n        {\n            InitializeComponent();\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/controls-triad/DeckView.xaml",
    "content": "﻿<UserControl x:Class=\"FFTriadBuddy.UI.DeckView\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \n             xmlns:local=\"clr-namespace:FFTriadBuddy.UI\"\n             mc:Ignorable=\"d\" \n             d:DataContext=\"{d:DesignInstance Type=local:DeckViewModel, IsDesignTimeCreatable=True}\"\n             d:DesignHeight=\"50\" d:DesignWidth=\"280\">\n    <UserControl.Resources>\n        <ContextMenu x:Key=\"menuPickCard\" Opened=\"ContextMenu_Opened\">\n            <MenuItem Header=\"{Binding OwnerObject.DeckCtrl_CtxMenu_PickCard}\" IsEnabled=\"False\"/>\n            <MenuItem Header=\"{Binding OwnerObject.DeckCtrl_CtxMenu_UseOnlyOwned}\" IsCheckable=\"True\" StaysOpenOnClick=\"True\" IsChecked=\"{Binding OwnerObject.IsUsingOnlyOwnedCards}\"/>\n            <MenuItem Header=\"{Binding OwnerObject.DeckCtrl_CtxMenu_LockForOptimization}\"\n                      IsCheckable=\"True\" StaysOpenOnClick=\"True\" \n                      IsChecked=\"{Binding IsShowingLock, Mode=OneWay}\" \n                      Command=\"{Binding OwnerObject.CommandToggleLock}\" CommandParameter=\"{Binding Path=.}\"/>\n            <MenuItem StaysOpenOnClick=\"True\">\n                <MenuItem.Header>\n                    <local:SearchableComboBox Width=\"200\" \n                              ItemsSource=\"{Binding OwnerObject.CardPickerItems}\"\n                              DisplayMemberPath=\"DescDeckPicker\"\n                              SelectedItem=\"{Binding CardModel, Mode=OneWay}\"\n                              SelectionEffectivelyChanged=\"SearchableComboBox_SelectionEffectivelyChanged\"/>\n                </MenuItem.Header>\n            </MenuItem>\n        </ContextMenu>\n    </UserControl.Resources>\n    <UserControl.Style>\n        <Style TargetType=\"UserControl\">\n            <Style.Triggers>\n                <!-- fake binding for design time background -->\n                <DataTrigger Binding=\"{Binding NumToSelect, FallbackValue=-10}\" Value=\"-10\">\n                    <Setter Property=\"Background\" Value=\"Gray\"/>\n                </DataTrigger>\n            </Style.Triggers>\n        </Style>\n    </UserControl.Style>\n    <Viewbox HorizontalAlignment=\"Left\" VerticalAlignment=\"Top\">\n        <ItemsControl Name=\"cards\" ItemsSource=\"{Binding Cards}\">\n            <ItemsControl.Resources>\n                <Thickness x:Key=\"marginDefault\" Top=\"0\"/>\n                <Thickness x:Key=\"marginSelectable\" Top=\"6\"/>\n\n                <Style TargetType=\"{x:Type local:CardView}\">\n                    <Style.Triggers>\n                        <DataTrigger Binding=\"{Binding CanReorderCards, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:DeckView}}}\" Value=\"True\">\n                            <Setter Property=\"local:CardDragDropExtension.IsDragSource\" Value=\"True\"/>\n                            <Setter Property=\"local:CardDragDropExtension.IsDragDestination\" Value=\"True\"/>\n                            <Setter Property=\"local:CardDragDropExtension.CardContext\" Value=\"{Binding}\"/>\n                            <Setter Property=\"local:CardDragDropExtension.CardContainer\" Value=\"{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:DeckView}}}\"/>\n                        </DataTrigger>\n                        <DataTrigger Binding=\"{Binding IsHighlighted}\" Value=\"True\">\n                            <DataTrigger.EnterActions>\n                                <BeginStoryboard>\n                                    <Storyboard>\n                                        <DoubleAnimation Storyboard.TargetProperty=\"(UIElement.RenderTransform).(TranslateTransform.Y)\" From=\"0\" To=\"-6\" AutoReverse=\"False\" Duration=\"0:0:0.1\" RepeatBehavior=\"1x\"/>\n                                    </Storyboard>\n                                </BeginStoryboard>\n                            </DataTrigger.EnterActions>\n                            <DataTrigger.ExitActions>\n                                <BeginStoryboard>\n                                    <Storyboard>\n                                        <DoubleAnimation Storyboard.TargetProperty=\"(UIElement.RenderTransform).(TranslateTransform.Y)\" From=\"-3\" To=\"0\" AutoReverse=\"False\" Duration=\"0:0:0.3\" RepeatBehavior=\"1x\"/>\n                                    </Storyboard>\n                                </BeginStoryboard>\n                            </DataTrigger.ExitActions>\n                        </DataTrigger>\n                    </Style.Triggers>\n                </Style>\n            </ItemsControl.Resources>\n            <ItemsControl.ItemsPanel>\n                <ItemsPanelTemplate>\n                    <StackPanel Orientation=\"Horizontal\">\n                        <StackPanel.Style>\n                            <Style TargetType=\"{x:Type StackPanel}\">\n                                <Setter Property=\"Margin\" Value=\"{StaticResource marginSelectable}\"/>\n                                <Style.Triggers>\n                                    <DataTrigger Binding=\"{Binding NumToSelect}\" Value=\"0\">\n                                        <Setter Property=\"Margin\" Value=\"{StaticResource marginDefault}\"/>\n                                    </DataTrigger>\n                                </Style.Triggers>\n                            </Style>\n                        </StackPanel.Style>\n                    </StackPanel>\n                </ItemsPanelTemplate>\n            </ItemsControl.ItemsPanel>\n            <ItemsControl.ItemTemplate>\n                <DataTemplate>\n                    <Grid Margin=\"0, 0, 5, 0\">\n                        <local:CardView ContextMenu=\"{StaticResource menuPickCard}\"\n                                        ContextMenuService.IsEnabled=\"{Binding CanPickCards, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:DeckView}}}\"\n                                        ContextMenuService.Placement=\"Top\"\n                                        PreviewMouseLeftButtonDown=\"Card_MouseDown\" PreviewMouseLeftButtonUp=\"Card_MouseUp\"\n                                        IsEnabled=\"{Binding IsEnabled, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:DeckView}}}\">\n                            <local:CardView.RenderTransform>\n                                <TranslateTransform/>\n                            </local:CardView.RenderTransform>\n                        </local:CardView>\n                    </Grid>\n                </DataTemplate>\n            </ItemsControl.ItemTemplate>\n        </ItemsControl>\n    </Viewbox>\n</UserControl>\n"
  },
  {
    "path": "sources/ui/view/controls-triad/DeckView.xaml.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System.Windows;\nusing System.Windows.Controls;\nusing System.Windows.Controls.Primitives;\nusing System.Windows.Input;\n\nnamespace FFTriadBuddy.UI\n{\n    /// <summary>\n    /// Interaction logic for DeckView.xaml\n    /// </summary>\n    public partial class DeckView : UserControl\n    {\n        public static readonly DependencyProperty CanReorderCardsProperty = DependencyProperty.Register(\"CanReorderCards\", typeof(bool), typeof(DeckView));\n        public static readonly DependencyProperty CanPickCardsProperty = DependencyProperty.Register(\"CanPickCards\", typeof(bool), typeof(DeckView));\n\n        public bool CanReorderCards\n        {\n            get { return (bool)GetValue(CanReorderCardsProperty); }\n            set { SetValue(CanReorderCardsProperty, value); }\n        }\n        public bool CanPickCards\n        {\n            get { return (bool)GetValue(CanPickCardsProperty); }\n            set { SetValue(CanPickCardsProperty, value); }\n        }\n\n        private static bool hasMetadataOverride = false;\n\n        public DeckView()\n        {\n            InitializeComponent();\n\n            // force default size for instances\n            if (!hasMetadataOverride)\n            {\n                hasMetadataOverride = true;\n                WidthProperty.OverrideMetadata(typeof(DeckView), new FrameworkPropertyMetadata((double)280));\n                HeightProperty.OverrideMetadata(typeof(DeckView), new FrameworkPropertyMetadata((double)50));\n            }\n        }\n\n        private void Card_MouseDown(object sender, MouseButtonEventArgs e)\n        {\n            var controlOb = sender as Control;\n            var cardVM = controlOb.DataContext as CardViewModel;\n            var deckVM = DataContext as DeckViewModel;\n\n            if (IsEnabled && deckVM.CommandSelect.CanExecute(cardVM))\n            {\n                deckVM.CommandSelect.Execute(cardVM);\n            }\n        }\n\n        private void Card_MouseUp(object sender, MouseButtonEventArgs e)\n        {\n            if (IsEnabled && CanPickCards)\n            {\n                var controlOb = sender as Control;\n                var cardVM = controlOb.DataContext as CardViewModel;\n\n                if (cardVM.CardDragMode == ECardDragMode.None)\n                {\n                    var controlCtxMenu = ContextMenuService.GetContextMenu(controlOb);\n                    if (controlCtxMenu != null)\n                    {\n                        controlCtxMenu.Placement = PlacementMode.Top;\n                        controlCtxMenu.PlacementTarget = controlOb;\n                        controlCtxMenu.IsOpen = true;\n                    }\n\n                    CardDragDropExtension.CardDragReset();\n                }\n            }\n        }\n\n        private void SearchableComboBox_SelectionEffectivelyChanged(object sender, object obj)\n        {\n            var controlOb = sender as Control;\n            var deckVM = DataContext as DeckViewModel;\n\n            var commandParam = new DeckViewModel.CardPickerParams() { cardVM = controlOb.DataContext as CardViewModel, cardModel = obj as CardModelProxy };\n            if (IsEnabled && deckVM.CommandPickCard.CanExecute(commandParam))\n            {\n                deckVM.CommandPickCard.Execute(commandParam);\n            }\n        }\n\n        private void ContextMenu_Opened(object sender, RoutedEventArgs e)\n        {\n            // validate, there are reports of default selection not matching actual card\n\n            var ctxMenu = sender as ContextMenu;\n            var comboBox = ViewUtils.FindVisualChildRecursive(ctxMenu, x => x is SearchableComboBox) as SearchableComboBox;\n            if (comboBox != null)\n            {\n                var selectedEntry = comboBox.SelectedItem as CardModelProxy;\n                var expectedEntry = (ctxMenu.DataContext as CardViewModel)?.CardModel;\n\n                if (selectedEntry != expectedEntry)\n                {\n                    comboBox.SelectedItem = expectedEntry;\n\n                    if (Logger.IsSuperVerbose())\n                    {\n                        var selectedEntry2 = comboBox.SelectedItem as CardModelProxy;\n\n                        Logger.WriteLine(\"Card context selection failed! got:{0}, expected:{1}, after forcing:{2}\",\n                            selectedEntry?.DescDeckPicker ?? \"--\",\n                            expectedEntry?.DescDeckPicker ?? \"--\",\n                            selectedEntry2?.DescDeckPicker ?? \"--\");\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/controls-triad/PlayerDeckPreview.xaml",
    "content": "﻿<UserControl\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \n             xmlns:local=\"clr-namespace:FFTriadBuddy.UI\"\n             xmlns:UI=\"clr-namespace:FFTriadBuddy.UI\" x:Class=\"FFTriadBuddy.UI.PlayerDeckPreview\"\n             mc:Ignorable=\"d\" \n             d:DataContext=\"{d:DesignInstance Type=local:DeckViewModel, IsDesignTimeCreatable=True}\"\n             d:DesignHeight=\"400\" d:DesignWidth=\"600\">\n    <Grid x:Name=\"gridName\">\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"1*\"/>\n            <RowDefinition Height=\"1*\"/>\n        </Grid.RowDefinitions>\n        <Grid.ColumnDefinitions>\n            <ColumnDefinition Width=\"1*\"/>\n            <ColumnDefinition Width=\"1*\"/>\n            <ColumnDefinition Width=\"1*\"/>\n            <ColumnDefinition Width=\"1*\"/>\n            <ColumnDefinition Width=\"1*\"/>\n            <ColumnDefinition Width=\"1*\"/>\n        </Grid.ColumnDefinitions>\n        <Grid.Resources>\n            <SolidColorBrush x:Key=\"BrushEmpty\" Color=\"#40000000\"/>\n            <SolidColorBrush x:Key=\"BrushBorder\" Color=\"Black\"/>\n            <SolidColorBrush x:Key=\"BrushHighlighted\" Color=\"#80054f7d\"/>\n            <Color x:Key=\"ColorPreview\">#FF054F7D</Color>\n\n            <Style TargetType=\"{x:Type Border}\">\n                <EventSetter Event=\"MouseLeftButtonDown\" Handler=\"Card_MouseDown\"/>\n                <Setter Property=\"BorderBrush\" Value=\"{StaticResource BrushBorder}\"/>\n                <Setter Property=\"BorderThickness\" Value=\"1\"/>\n                <Style.Triggers>\n                    <DataTrigger Binding=\"{Binding Path=.}\" Value=\"{x:Null}\">\n                        <Setter Property=\"Background\" Value=\"{StaticResource BrushEmpty}\"/>\n                    </DataTrigger>\n                </Style.Triggers>\n            </Style>\n\n            <Style TargetType=\"{x:Type Rectangle}\">\n                <Setter Property=\"Visibility\" Value=\"Hidden\"/>\n                <Setter Property=\"IsHitTestVisible\" Value=\"False\"/>\n                <Style.Triggers>\n                    <DataTrigger Binding=\"{Binding DataContext.NumSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:PlayerDeckPreview}}}\" Value=\"1\">\n                        <Setter Property=\"Fill\" Value=\"{StaticResource BrushHighlighted}\"/>\n                        <Setter Property=\"Visibility\" Value=\"Visible\"/>\n                    </DataTrigger>\n                    <DataTrigger Binding=\"{Binding IsHighlighted}\" Value=\"True\">\n                        <Setter Property=\"Visibility\" Value=\"Hidden\"/>\n                    </DataTrigger>\n                    <DataTrigger Binding=\"{Binding IsPreview}\" Value=\"True\">\n                        <DataTrigger.EnterActions>\n                            <BeginStoryboard Name=\"blinkBlue\" >\n                                <BeginStoryboard.Storyboard>\n                                    <Storyboard>\n                                        <ColorAnimationUsingKeyFrames Storyboard.TargetProperty=\"Fill.(SolidColorBrush.Color)\" AutoReverse=\"False\" Duration=\"0:0:1.5\" RepeatBehavior=\"Forever\">\n                                            <ColorAnimationUsingKeyFrames.KeyFrames>\n                                                <DiscreteColorKeyFrame Value=\"{StaticResource ColorPreview}\" KeyTime=\"0%\"/>\n                                                <DiscreteColorKeyFrame Value=\"Transparent\" KeyTime=\"50%\"/>\n                                            </ColorAnimationUsingKeyFrames.KeyFrames>\n                                        </ColorAnimationUsingKeyFrames>\n                                    </Storyboard>\n                                </BeginStoryboard.Storyboard>\n                            </BeginStoryboard>\n                        </DataTrigger.EnterActions>\n                        <DataTrigger.ExitActions>\n                            <StopStoryboard BeginStoryboardName=\"blinkBlue\"/>\n                        </DataTrigger.ExitActions>\n                        <Setter Property=\"Fill\" Value=\"{StaticResource BrushBorder}\"/>\n                        <Setter Property=\"Visibility\" Value=\"Visible\"/>\n                    </DataTrigger>\n                </Style.Triggers>\n            </Style>\n        </Grid.Resources>\n        <Border Grid.ColumnSpan=\"2\" Margin=\"1\" DataContext=\"{Binding Cards[0]}\">\n            <Grid>\n                <Image Source=\"{Binding CardImage}\" ToolTip=\"{Binding Tooltip}\"/>\n                <Rectangle/>\n            </Grid>\n        </Border>\n        <Border Grid.Column=\"2\" Grid.ColumnSpan=\"2\" Margin=\"1\" DataContext=\"{Binding Cards[1]}\">\n            <Grid>\n                <Image Source=\"{Binding CardImage}\" ToolTip=\"{Binding Tooltip}\"/>\n                <Rectangle/>\n            </Grid>\n        </Border>\n        <Border Grid.Column=\"4\" Grid.ColumnSpan=\"2\" Margin=\"1\" DataContext=\"{Binding Cards[2]}\">\n            <Grid>\n                <Image Source=\"{Binding CardImage}\" ToolTip=\"{Binding Tooltip}\"/>\n                <Rectangle/>\n            </Grid>\n        </Border>\n        <Border Grid.Column=\"1\" Grid.Row=\"1\" Grid.ColumnSpan=\"2\" Margin=\"1\" DataContext=\"{Binding Cards[3]}\">\n            <Grid>\n                <Image Source=\"{Binding CardImage}\" ToolTip=\"{Binding Tooltip}\"/>\n                <Rectangle/>\n            </Grid>\n        </Border>\n        <Border Grid.Column=\"3\" Grid.Row=\"1\" Grid.ColumnSpan=\"2\" Margin=\"1\" DataContext=\"{Binding Cards[4]}\">\n            <Grid>\n                <Image Source=\"{Binding CardImage}\" ToolTip=\"{Binding Tooltip}\"/>\n                <Rectangle/>\n            </Grid>\n        </Border>\n    </Grid>\n</UserControl>\n"
  },
  {
    "path": "sources/ui/view/controls-triad/PlayerDeckPreview.xaml.cs",
    "content": "﻿using System.Windows;\nusing System.Windows.Controls;\nusing System.Windows.Input;\n\nnamespace FFTriadBuddy.UI\n{\n    /// <summary>\n    /// Interaction logic for PlayerDeckPreview.xaml\n    /// </summary>\n    public partial class PlayerDeckPreview : UserControl\n    {\n        public PlayerDeckPreview()\n        {\n            InitializeComponent();\n        }\n\n        private void Card_MouseDown(object sender, MouseButtonEventArgs e)\n        {\n            var controlOb = sender as FrameworkElement;\n            var cardVM = controlOb.DataContext as CardViewModel;\n            var deckVM = DataContext as DeckViewModel;\n\n            if (deckVM.CommandSelect.CanExecute(cardVM))\n            {\n                deckVM.CommandSelect.Execute(cardVM);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/utils/CanvasExtensions.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Windows;\nusing System.Windows.Controls;\nusing System.Windows.Threading;\n\nnamespace FFTriadBuddy.UI\n{\n    public class CanvasExtensions\n    {\n        public static DependencyProperty AutoHideProperty = DependencyProperty.RegisterAttached(\"AutoHide\", typeof(bool), typeof(CanvasExtensions),\n            new PropertyMetadata(false, new PropertyChangedCallback(OnAutoHideChanged)));\n\n        public static bool GetAutoHide(FrameworkElement owner)\n        {\n            return (bool)owner.GetValue(AutoHideProperty);\n        }\n\n        public static void SetAutoHide(FrameworkElement owner, bool value)\n        {\n            owner.SetValue(AutoHideProperty, value);\n        }\n\n        public static void OnAutoHideChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)\n        {\n            var owner = sender as FrameworkElement;\n            if (owner != null && !owner.IsLoaded)\n            {\n                bool designMode = DesignerProperties.GetIsInDesignMode(sender);\n                if (!designMode)\n                {\n                    owner.Visibility = Visibility.Hidden;\n                }\n            }\n        }\n\n        public static DependencyProperty ScreenBoundsProperty = DependencyProperty.RegisterAttached(\"ScreenBounds\", typeof(OverlayWindowViewModel.ScreenCoordVM), typeof(CanvasExtensions),\n            new PropertyMetadata(null, new PropertyChangedCallback(OnScreenBoundsChanged)));\n\n        public static OverlayWindowViewModel.ScreenCoordVM GetScreenBounds(FrameworkElement owner)\n        {\n            return (OverlayWindowViewModel.ScreenCoordVM)owner.GetValue(ScreenBoundsProperty);\n        }\n\n        public static void SetScreenBounds(FrameworkElement owner, OverlayWindowViewModel.ScreenCoordVM value)\n        {\n            owner.SetValue(ScreenBoundsProperty, value);\n        }\n\n        public static void OnScreenBoundsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)\n        {\n            var owner = sender as FrameworkElement;\n            if (owner != null)\n            {\n                if (!owner.IsLoaded)\n                {\n                    owner.Loaded += UpdateScreenBoundsOnLoaded;\n                }\n                else\n                {\n                    UpdateScreenBounds(owner, e.NewValue as OverlayWindowViewModel.ScreenCoordVM);\n                }\n            }\n        }\n\n        private static void UpdateScreenBoundsOnLoaded(object sender, RoutedEventArgs e)\n        {\n            var owner = sender as FrameworkElement;\n            owner.Loaded -= UpdateScreenBoundsOnLoaded;\n\n            var bounds = GetScreenBounds(owner);\n            UpdateScreenBounds(owner, bounds);\n        }\n\n        private static Dictionary<object, DispatcherTimer> mapTimers = new Dictionary<object, DispatcherTimer>();\n        private static void UpdateScreenBounds(FrameworkElement owner, OverlayWindowViewModel.ScreenCoordVM screenCoordVM)\n        {\n            if (mapTimers.TryGetValue(owner, out var existingTimer))\n            {\n                if (existingTimer.IsEnabled)\n                {\n                    existingTimer.Stop();\n                }\n\n                mapTimers.Remove(owner);\n            }\n\n            if (screenCoordVM != null)\n            {\n                var canvasOwner = ViewUtils.FindVisualParent(owner, x => x is Canvas) as Canvas;\n                var overlayOwner = ViewUtils.FindVisualParent(owner, x => x is OverlayWindowInteractive) as OverlayWindowInteractive;\n                var localPos = canvasOwner.PointFromScreen(new Point(screenCoordVM.ScreenCoords.X, screenCoordVM.ScreenCoords.Y));\n\n                switch (screenCoordVM.DrawMode)\n                {\n                    case OverlayWindowViewModel.ScreenCoordVM.Mode.CapturePanel:\n                        // coord only, anchor: center + bottom (with margin)\n                        if (overlayOwner != null)\n                        {\n                            overlayOwner.SetPanelCanvasPos(localPos.X - (owner.ActualWidth / 2), localPos.Y - owner.ActualHeight - 10);\n                        }\n                        else\n                        {\n                            Canvas.SetLeft(owner, localPos.X - (owner.ActualWidth / 2));\n                            Canvas.SetTop(owner, localPos.Y - owner.ActualHeight - 10);\n                        }\n                        break;\n\n                    case OverlayWindowViewModel.ScreenCoordVM.Mode.AdjustedCapturePanel:\n                        // coord only, anchor: center + top (with margin + coord.height)\n                        if (overlayOwner != null)\n                        {\n                            overlayOwner.SetPanelCanvasPos(localPos.X + ((screenCoordVM.ScreenSize.Width - owner.ActualWidth) / 2), localPos.Y + screenCoordVM.ScreenSize.Height + 50);\n                        }\n                        else\n                        {\n                            Canvas.SetLeft(owner, localPos.X + ((screenCoordVM.ScreenSize.Width - owner.ActualWidth) / 2));\n                            Canvas.SetTop(owner, localPos.Y + screenCoordVM.ScreenSize.Height + 50);\n                        }\n                        break;\n\n                    case OverlayWindowViewModel.ScreenCoordVM.Mode.SwapWarning:\n                        // coord only, anchor: left + bottom (with margin)\n                        Canvas.SetLeft(owner, localPos.X);\n                        Canvas.SetTop(owner, localPos.Y - owner.ActualHeight - 10);\n                        break;\n\n                    default:\n                        Canvas.SetLeft(owner, localPos.X);\n                        Canvas.SetTop(owner, localPos.Y);\n                        owner.Width = screenCoordVM.ScreenSize.Width;\n                        owner.Height = screenCoordVM.ScreenSize.Height;\n                        break;\n                }\n\n                owner.Visibility = Visibility.Visible;\n                if (screenCoordVM.Duration > 0)\n                {\n                    var timer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(screenCoordVM.Duration) };\n                    timer.Tick += (s, e) =>\n                    {\n                        owner.Visibility = Visibility.Hidden;\n                        mapTimers.Remove(owner);\n                        ((DispatcherTimer)s).Stop();\n                    };\n\n                    timer.Start();\n                    mapTimers.Add(owner, timer);\n                }\n            }\n            else\n            {\n                owner.Visibility = Visibility.Hidden;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/utils/CardDragDropExtension.cs",
    "content": "﻿using System;\nusing System.Windows;\nusing System.Windows.Controls;\nusing System.Windows.Input;\n\nnamespace FFTriadBuddy.UI\n{\n    public interface ICardDragDropSource\n    {\n        void OnCardDrop(CardViewModel sourceCard, object destContainer);\n    }\n\n    public interface ICardDragDropTarget\n    {\n        bool IsCardDropAllowed(CardViewModel sourceCard, object sourceContainer);\n        void OnCardDragEnter(CardViewModel sourceCard, CardViewModel destCard);\n        void OnCardDragLeave(CardViewModel sourceCard, CardViewModel destCard);\n        void OnCardDrop(CardViewModel sourceCard, CardViewModel destCard, object sourceContainer);\n    }\n\n    public class CardDragDropExtension\n    {\n        private class CardDragData\n        {\n            public CardViewModel srcCard;\n            public object srcControl;\n            public object srcContainerOb;\n        }\n\n        private static Point mouseDownPos;\n        private static Control mouseDownCtrl;\n        private static bool isDropAllowed;\n\n        public static readonly DependencyProperty IsDragSourceProperty =\n            DependencyProperty.RegisterAttached(\"IsDragSource\", typeof(bool), typeof(CardDragDropExtension), new PropertyMetadata(false, IsDragSourceChanged));\n\n        public static readonly DependencyProperty IsDragDestinationProperty =\n            DependencyProperty.RegisterAttached(\"IsDragDestination\", typeof(bool), typeof(CardDragDropExtension), new PropertyMetadata(false, IsDragDestinationChanged));\n\n        public static readonly DependencyProperty CardContextProperty =\n            DependencyProperty.RegisterAttached(\"CardContext\", typeof(CardViewModel), typeof(CardDragDropExtension));\n\n        public static readonly DependencyProperty CardContainerProperty =\n            DependencyProperty.RegisterAttached(\"CardContainer\", typeof(object), typeof(CardDragDropExtension));\n\n        public static bool GetIsDragSource(DependencyObject obj)\n        {\n            return (bool)obj.GetValue(IsDragSourceProperty);\n        }\n\n        public static void SetIsDragSource(DependencyObject obj, bool value)\n        {\n            obj.SetValue(IsDragSourceProperty, value);\n        }\n\n        public static bool GetIsDragDestination(DependencyObject obj)\n        {\n            return (bool)obj.GetValue(IsDragDestinationProperty);\n        }\n\n        public static void SetIsDragDestination(DependencyObject obj, bool value)\n        {\n            obj.SetValue(IsDragDestinationProperty, value);\n        }\n\n        public static CardViewModel GetCardContext(DependencyObject obj)\n        {\n            return (CardViewModel)obj.GetValue(CardContextProperty);\n        }\n\n        public static void SetCardContext(DependencyObject obj, CardViewModel value)\n        {\n            obj.SetValue(CardContextProperty, value);\n        }\n\n        public static object GetCardContainer(DependencyObject obj)\n        {\n            return obj.GetValue(CardContainerProperty);\n        }\n\n        public static void SetCardContainer(DependencyObject obj, object value)\n        {\n            obj.SetValue(CardContainerProperty, value);\n        }\n\n        private static void IsDragSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)\n        {\n            var control = obj as Control;\n            if (control != null && e.NewValue != e.OldValue)\n            {\n                if ((bool)e.NewValue)\n                {\n                    control.PreviewMouseMove += CardDrag_MouseMove;\n                    control.PreviewMouseLeftButtonDown += CardDrag_LeftButtonDown;\n                    control.PreviewMouseLeftButtonUp += CardDrag_LeftButtonUp;\n                }\n                else\n                {\n                    control.PreviewMouseMove -= CardDrag_MouseMove;\n                    control.PreviewMouseLeftButtonDown -= CardDrag_LeftButtonDown;\n                    control.PreviewMouseLeftButtonUp += CardDrag_LeftButtonUp;\n                }\n            }\n        }\n\n        public static void CardDragReset()\n        {\n            mouseDownCtrl = null;\n        }\n\n        private static void CardDrag_LeftButtonDown(object sender, MouseButtonEventArgs e)\n        {\n            mouseDownPos = e.GetPosition(null);\n            mouseDownCtrl = sender as Control;\n        }\n\n        private static void CardDrag_LeftButtonUp(object sender, MouseButtonEventArgs e)\n        {\n            mouseDownCtrl = null;\n        }\n\n        private static void CardDrag_MouseMove(object sender, MouseEventArgs e)\n        {\n            if (e.LeftButton == MouseButtonState.Pressed && mouseDownCtrl != null && mouseDownCtrl.IsEnabled)\n            {\n                var pos = e.GetPosition(null);\n                bool canStartDrag =\n                    Math.Abs(pos.X - mouseDownPos.X) >= SystemParameters.MinimumHorizontalDragDistance ||\n                    Math.Abs(pos.Y - mouseDownPos.Y) >= SystemParameters.MinimumHorizontalDragDistance;\n\n                if (canStartDrag)\n                {\n                    var cardVM = GetCardContext(mouseDownCtrl);\n                    if (cardVM != null)\n                    {\n                        CardDragData payload = new CardDragData()\n                        {\n                            srcCard = cardVM,\n                            srcContainerOb = GetCardContainer(mouseDownCtrl),\n                            srcControl = mouseDownCtrl\n                        };\n\n                        DataObject dragData = new DataObject();\n                        dragData.SetData(payload);\n\n                        DragDrop.DoDragDrop(mouseDownCtrl, dragData, DragDropEffects.Move);\n                    }\n\n                    mouseDownCtrl = null;\n                }\n            }\n        }\n\n        private static void IsDragDestinationChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)\n        {\n            var control = obj as UIElement;\n            if (control != null && e.NewValue != e.OldValue)\n            {\n                if ((bool)e.NewValue)\n                {\n                    control.AllowDrop = true;\n                    control.PreviewDragEnter += CardDrag_DragEnter;\n                    control.PreviewDragLeave += CardDrag_DragLeave;\n                    control.PreviewDragOver += CardDrag_DragOver;\n                    control.PreviewDrop += CardDrag_Drop;\n                }\n                else\n                {\n                    control.AllowDrop = false;\n                    control.PreviewDragEnter -= CardDrag_DragEnter;\n                    control.PreviewDragLeave -= CardDrag_DragLeave;\n                    control.PreviewDragOver -= CardDrag_DragOver;\n                    control.PreviewDrop -= CardDrag_Drop;\n                }\n            }\n        }\n\n        private static void CardDrag_Drop(object sender, DragEventArgs e)\n        {\n            var control = sender as UIElement;\n            if (control != null && e.Data.GetDataPresent(typeof(CardDragData)))\n            {\n                var dragPayload = (CardDragData)e.Data.GetData(typeof(CardDragData));\n                var targetContainerOb = GetCardContainer(control);\n                var targetContainer = targetContainerOb as ICardDragDropTarget;\n\n                if (targetContainer != null && targetContainer.IsCardDropAllowed(dragPayload.srcCard, dragPayload.srcContainerOb))\n                {\n                    targetContainer.OnCardDrop(dragPayload.srcCard, GetCardContext(control), dragPayload.srcContainerOb);\n\n                    var srcContainer = dragPayload.srcContainerOb as ICardDragDropSource;\n                    if (srcContainer != null)\n                    {\n                        srcContainer.OnCardDrop(dragPayload.srcCard, targetContainerOb);\n                    }\n                }\n            }\n        }\n\n        private static void CardDrag_DragLeave(object sender, DragEventArgs e)\n        {\n            if (!isDropAllowed) { return; }\n\n            var control = sender as UIElement;\n            if (control != null && e.Data.GetDataPresent(typeof(CardDragData)))\n            {\n                var dragPayload = (CardDragData)e.Data.GetData(typeof(CardDragData));\n                var targetContainerOb = GetCardContainer(control);\n                var targetContainer = targetContainerOb as ICardDragDropTarget;\n\n                if (targetContainer != null)\n                {\n                    targetContainer.OnCardDragLeave(dragPayload.srcCard, GetCardContext(control));\n                }\n\n                e.Handled = true;\n            }\n        }\n\n        private static void CardDrag_DragEnter(object sender, DragEventArgs e)\n        {\n            isDropAllowed = false;\n\n            var control = sender as UIElement;\n            if (control != null && e.Data.GetDataPresent(typeof(CardDragData)))\n            {\n                var dragPayload = (CardDragData)e.Data.GetData(typeof(CardDragData));\n                if (dragPayload.srcControl != control)\n                {\n                    var targetContainerOb = GetCardContainer(control);\n                    var targetContainer = targetContainerOb as ICardDragDropTarget;\n\n                    if (targetContainer != null && targetContainer.IsCardDropAllowed(dragPayload.srcCard, dragPayload.srcContainerOb))\n                    {\n                        targetContainer.OnCardDragEnter(dragPayload.srcCard, GetCardContext(control));\n                        isDropAllowed = true;\n                    }\n                }\n\n                e.Effects = isDropAllowed ? DragDropEffects.Move : DragDropEffects.None;\n                e.Handled = true;\n            }\n        }\n\n        private static void CardDrag_DragOver(object sender, DragEventArgs e)\n        {\n            e.Effects = isDropAllowed ? DragDropEffects.Move : DragDropEffects.None;\n            e.Handled = true;\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/utils/Converters.cs",
    "content": "﻿using System;\nusing System.Drawing;\nusing System.Globalization;\nusing System.Reflection;\nusing System.Windows;\nusing System.Windows.Data;\nusing System.Windows.Interop;\nusing System.Windows.Media.Imaging;\n\nnamespace FFTriadBuddy.UI\n{\n    public class BoolToInvisibilityConverter : IValueConverter\n    {\n        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            return (bool)value ? Visibility.Hidden : Visibility.Visible;\n        }\n\n        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            throw new NotImplementedException();\n        }\n    }\n\n    public class InversedBoolConverter : IValueConverter\n    {\n        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            return !((bool)value);\n        }\n\n        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            throw new NotImplementedException();\n        }\n    }\n\n    public class SystemIconConverter : IValueConverter\n    {\n        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            var icon = (Icon)typeof(SystemIcons).GetProperty(parameter.ToString(), BindingFlags.Public | BindingFlags.Static).GetValue(null, null);\n            var bitmap = Imaging.CreateBitmapSourceFromHIcon(icon.Handle, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());\n            return bitmap;\n        }\n\n        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            throw new NotImplementedException();\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/utils/ListViewExtensions.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.Reflection;\nusing System.Windows;\nusing System.Windows.Controls;\nusing System.Windows.Data;\nusing System.Windows.Media;\n\nnamespace FFTriadBuddy.UI\n{\n    public class ListViewExtensions\n    {\n        public class SortInfo\n        {\n            public GridViewColumn column;\n            public ListSortDirection direction;\n        }\n\n        private static readonly PropertyInfo InheritanceContextProp = typeof(DependencyObject).GetProperty(\"InheritanceContext\", BindingFlags.NonPublic | BindingFlags.Instance);\n\n\n        public static DependencyProperty SortInfoProperty = DependencyProperty.RegisterAttached(\"SortInfo\", typeof(SortInfo), typeof(ListViewExtensions));\n\n        public static SortInfo GetSortInfo(ListView owner)\n        {\n            return (SortInfo)owner.GetValue(SortInfoProperty);\n        }\n\n        public static void SetSortInfo(ListView owner, SortInfo value)\n        {\n            owner.SetValue(SortInfoProperty, value);\n        }\n\n\n        public static DependencyProperty ColumnSortPropertyProperty = DependencyProperty.RegisterAttached(\"ColumnSortProperty\", typeof(string), typeof(ListViewExtensions));\n\n        public static string GetColumnSortProperty(GridViewColumn owner)\n        {\n            return (string)owner.GetValue(ColumnSortPropertyProperty);\n        }\n\n        public static void SetColumnSortProperty(GridViewColumn owner, string value)\n        {\n            owner.SetValue(ColumnSortPropertyProperty, value);\n        }\n\n        public static DependencyProperty InitSortingProperty = DependencyProperty.RegisterAttached(\"InitSorting\", typeof(string), typeof(ListViewExtensions));\n\n        public static string GetInitSorting(GridViewColumn owner)\n        {\n            return (string)owner.GetValue(InitSortingProperty);\n        }\n\n        public static void SetInitSorting(GridViewColumn owner, string value)\n        {\n            owner.SetValue(InitSortingProperty, value);\n        }\n\n        public static DependencyProperty EnableSortOnClickProperty = DependencyProperty.RegisterAttached(\"EnableSortOnClick\", typeof(bool), typeof(ListViewExtensions),\n            new PropertyMetadata(false, new PropertyChangedCallback(EnableSortOnClickChanged)));\n\n        public static bool GetEnableSortOnClick(ListView owner)\n        {\n            return (bool)owner.GetValue(EnableSortOnClickProperty);\n        }\n\n        public static void SetEnableSortOnClick(ListView owner, bool value)\n        {\n            owner.SetValue(EnableSortOnClickProperty, value);\n        }\n\n        public static void EnableSortOnClickChanged(object sender, DependencyPropertyChangedEventArgs e)\n        {\n            var listView = sender as ListView;\n            if (listView == null || e.NewValue == e.OldValue)\n            {\n                return;\n            }\n\n            bool enableSorting = (bool)e.NewValue;\n            if (enableSorting)\n            {\n                listView.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(HandlerColumnClicked));\n                if (listView.View == null)\n                {\n                    listView.Loaded += new RoutedEventHandler(HandlerViewLoaded);\n                }\n                else\n                {\n                    InitializeViewSorting(listView);\n                }\n            }\n            else\n            {\n                listView.RemoveHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(HandlerColumnClicked));\n            }\n        }\n\n        public static void HandlerViewLoaded(object sender, RoutedEventArgs e)\n        {\n            var listView = e.Source as ListView;\n            InitializeViewSorting(listView);\n            listView.Loaded -= new RoutedEventHandler(HandlerViewLoaded);\n        }\n\n        public static void InitializeViewSorting(ListView listView)\n        {\n            GridView gridView = (listView != null) ? (listView.View as GridView) : null;\n            if (gridView != null)\n            {\n                bool hasSorted = false;\n                foreach (var column in gridView.Columns)\n                {\n                    var mode = GetInitSorting(column);\n                    if (mode != null)\n                    {\n                        var sortInfo = new SortInfo() { column = column, direction = (mode == \"ASC\" ? ListSortDirection.Descending : ListSortDirection.Ascending) };\n                        SetSortInfo(listView, sortInfo);\n\n                        ApplyViewSorting(listView, column);\n                        hasSorted = true;\n                        break;\n                    }\n                }\n\n                if (!hasSorted)\n                {\n                    foreach (var column in gridView.Columns)\n                    {\n                        if (column.DisplayMemberBinding != null)\n                        {\n                            ApplyViewSorting(listView, column);\n                            break;\n                        }\n                    }\n                }\n            }\n        }\n\n        public static void HandlerColumnClicked(object sender, RoutedEventArgs e)\n        {\n            var columnHeader = e.OriginalSource as GridViewColumnHeader;\n            var column = columnHeader != null ? columnHeader.Column : null;\n            var listView = FindObjectParent<ListView>(column);\n\n            if (column != null && listView != null)\n            {\n                ApplyViewSorting(listView, column);\n            }\n        }\n\n        public static void ApplyViewSorting(ListView listView, GridViewColumn column)\n        {\n            if (listView == null && listView.Items == null)\n            {\n                return;\n            }\n\n            SortInfo sortInfo = GetSortInfo(listView);\n            if (sortInfo == null)\n            {\n                sortInfo = new SortInfo();\n            }\n\n            if (sortInfo.column != column)\n            {\n                sortInfo.column = column;\n                sortInfo.direction = ListSortDirection.Ascending;\n            }\n            else\n            {\n                sortInfo.direction = (sortInfo.direction == ListSortDirection.Ascending) ? ListSortDirection.Descending : ListSortDirection.Ascending;\n            }\n\n            var sortProp = (column.DisplayMemberBinding is Binding) ? (column.DisplayMemberBinding as Binding).Path.Path : GetColumnSortProperty(column);\n            if (!string.IsNullOrEmpty(sortProp))\n            {\n                listView.Items.SortDescriptions.Clear();\n                listView.Items.SortDescriptions.Add(new SortDescription(sortProp, sortInfo.direction));\n            }\n\n            SetSortInfo(listView, sortInfo);\n        }\n\n        public static DependencyObject FindObjectParent(DependencyObject source, Predicate<DependencyObject> isTypeMatching)\n        {\n            DependencyObject testOb = source;\n            while (testOb != null && !isTypeMatching(testOb))\n            {\n                var nextParentOb = LogicalTreeHelper.GetParent(testOb);\n                if (nextParentOb == null)\n                {\n                    if (testOb is FrameworkElement)\n                    {\n                        nextParentOb = VisualTreeHelper.GetParent(testOb);\n                    }\n\n                    if (nextParentOb == null && testOb is ContentElement)\n                    {\n                        nextParentOb = ContentOperations.GetParent((ContentElement)testOb);\n                    }\n\n                    if (nextParentOb == null)\n                    {\n                        nextParentOb = InheritanceContextProp.GetValue(testOb, null) as DependencyObject;\n                    }\n                }\n\n                testOb = nextParentOb;\n            }\n\n            return testOb;\n        }\n\n        public static T FindObjectParent<T>(DependencyObject source) where T : DependencyObject\n        {\n            return FindObjectParent(source, x => x is T) as T;\n        }\n\n        public static DependencyProperty SyncScrollItemProperty = DependencyProperty.RegisterAttached(\"SyncScrollItem\", typeof(object), typeof(ListViewExtensions),\n            new PropertyMetadata(null, new PropertyChangedCallback(SyncScrollItemChanged)));\n\n        public static object GetSyncScrollItem(ListView owner)\n        {\n            return (object)owner.GetValue(SyncScrollItemProperty);\n        }\n\n        public static void SetSyncScrollItem(ListView owner, object value)\n        {\n            owner.SetValue(SyncScrollItemProperty, value);\n        }\n\n        public static void SyncScrollItemChanged(object sender, DependencyPropertyChangedEventArgs e)\n        {\n            var listView = sender as ListView;\n            if (listView == null || e.NewValue == e.OldValue)\n            {\n                return;\n            }\n\n            listView.SelectedItem = e.NewValue;\n            listView.ScrollIntoView(e.NewValue);\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/utils/OverlayWindowService.cs",
    "content": "﻿using System.Windows;\n\nnamespace FFTriadBuddy.UI\n{\n    public class OverlayWindowService : IOverlayWindowService\n    {\n        private OverlayWindowInteractive overlayWindowI;\n        private OverlayWindowTransparent overlayWindowT;\n\n        public void SetOverlayActive(IOverlayWindowViewModel viewModel, bool wantsActive)\n        {\n            if (overlayWindowI == null)\n            {\n                overlayWindowI = new OverlayWindowInteractive() { Owner = App.Current.MainWindow, FontSize = App.Current.MainWindow.FontSize };\n            }\n\n            if (overlayWindowT == null)\n            {\n                overlayWindowT = new OverlayWindowTransparent() { Owner = App.Current.MainWindow, FontSize = App.Current.MainWindow.FontSize };\n                overlayWindowT.panelCapture = overlayWindowI.panelCapture;\n                overlayWindowI.OnPanelMoved += (x, y) => overlayWindowT.SetDetailsCanvasPos(x, y);\n            }\n\n            overlayWindowI.DataContext = viewModel;\n            overlayWindowT.DataContext = viewModel;\n\n            overlayWindowI.SetOverlayActive(wantsActive);\n            overlayWindowT.SetOverlayActive(wantsActive);\n        }\n\n        public bool IsCursorInside(System.Drawing.Rectangle screenBounds)\n        {\n            if (overlayWindowI != null)\n            {\n                var screenPos = System.Windows.Forms.Cursor.Position;\n                bool isInside = screenBounds.Contains(screenPos.X, screenPos.Y);\n\n                if (isInside)\n                {\n                    return true;\n                }\n            }\n\n            return false;\n        }\n\n        private void MoveOverlayToScreen(System.Drawing.Rectangle screenBounds)\n        {\n            if (overlayWindowI != null)\n            {\n                overlayWindowI.Left = screenBounds.X;\n                overlayWindowI.Top = screenBounds.Y;\n                overlayWindowI.Width = screenBounds.Width;\n                overlayWindowI.Height = screenBounds.Height;\n            }\n\n            if (overlayWindowT != null)\n            {\n                overlayWindowT.Left = screenBounds.X;\n                overlayWindowT.Top = screenBounds.Y;\n                overlayWindowT.Width = screenBounds.Width;\n                overlayWindowT.Height = screenBounds.Height;\n            }\n        }\n\n        public void OnProcessingGameWindow(System.Drawing.Rectangle gameWindowBounds, out bool invalidatedPosition)\n        {\n            invalidatedPosition = false;\n            if (!gameWindowBounds.IsEmpty && (overlayWindowI != null))\n            {\n                var screenBounds = System.Windows.Forms.Screen.GetBounds(gameWindowBounds);\n                var centerPt = new Point(overlayWindowI.Left + (overlayWindowI.ActualWidth * 0.5), overlayWindowI.Top + (overlayWindowI.ActualHeight * 0.5));\n                if (!screenBounds.Contains((int)centerPt.X, (int)centerPt.Y))\n                {\n                    MoveOverlayToScreen(screenBounds);\n                    invalidatedPosition = true;\n                }\n            }\n        }\n\n        public System.Drawing.Rectangle GetScreenBounds(System.Drawing.Rectangle gameWindowBounds)\n        {\n            OnProcessingGameWindow(gameWindowBounds, out bool _);\n            if (gameWindowBounds.IsEmpty)\n            {\n                var mainWindow = App.Current.MainWindow;\n                gameWindowBounds = new System.Drawing.Rectangle((int)mainWindow.Left, (int)mainWindow.Top, (int)mainWindow.ActualWidth, (int)mainWindow.ActualHeight);\n            }\n\n            var screenBounds = System.Windows.Forms.Screen.GetBounds(gameWindowBounds);\n            MoveOverlayToScreen(screenBounds);\n\n            return screenBounds;\n        }\n\n        public static void Initialize()\n        {\n            ViewModelServices.OverlayWindow = new OverlayWindowService();\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/view/utils/ViewUtils.cs",
    "content": "﻿using System;\nusing System.Windows;\nusing System.Windows.Controls;\nusing System.Windows.Media;\n\nnamespace FFTriadBuddy.UI\n{\n    public class ViewUtils\n    {\n        public static DependencyObject FindVisualChildRecursive(DependencyObject parentOb, Func<DependencyObject, bool> predicate)\n        {\n            int numChildren = parentOb != null ? VisualTreeHelper.GetChildrenCount(parentOb) : 0;\n            for (int idx = 0; idx < numChildren; idx++)\n            {\n                var childOb = VisualTreeHelper.GetChild(parentOb, idx);\n                if (predicate(childOb))\n                {\n                    return childOb;\n                }\n\n                var foundMatch = FindVisualChildRecursive(childOb, predicate);\n                if (foundMatch != null)\n                {\n                    return foundMatch;\n                }\n            }\n\n            return null;\n        }\n\n        public static DependencyObject FindVisualParent(DependencyObject testOb, Func<DependencyObject, bool> predicate)\n        {\n            while (testOb != null)\n            {\n                if (predicate(testOb))\n                {\n                    return testOb;\n                }\n\n                testOb = VisualTreeHelper.GetParent(testOb);\n            }\n\n            return null;\n        }\n    }\n\n    public class NoAutoWidthDecorator : Decorator\n    {\n        protected override Size MeasureOverride(Size constraint)\n        {\n            Child.Measure(constraint);\n            return new Size(0, Child.DesiredSize.Height);\n        }\n\n        protected override Size ArrangeOverride(Size arrangeSize)\n        {\n            var childElem = Child as FrameworkElement;\n            childElem.MaxWidth = arrangeSize.Width;\n            childElem.MinHeight = arrangeSize.Height;\n\n            return base.ArrangeOverride(arrangeSize);\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/viewmodel/AdjustCardViewModel.cs",
    "content": "﻿using System;\nusing System.Drawing.Imaging;\nusing System.IO;\nusing System.Windows.Input;\nusing System.Windows.Media.Imaging;\n\nnamespace FFTriadBuddy.UI\n{\n    public class AdjustCardViewModel : LocalizedViewModel, IDialogWindowViewModel\n    {\n        public ImageCardDataViewModel HashProxy { get; private set; }\n\n        private int sideUp = 0;\n        public int SideUp { get => sideUp; set { PropertySetAndNotify(value, ref sideUp); UpdateMatchingCards(); } }\n\n        private int sideLeft = 0;\n        public int SideLeft { get => sideLeft; set { PropertySetAndNotify(value, ref sideLeft); UpdateMatchingCards(); } }\n\n        private int sideDown = 0;\n        public int SideDown { get => sideDown; set { PropertySetAndNotify(value, ref sideDown); UpdateMatchingCards(); } }\n\n        private int sideRight = 0;\n        public int SideRight { get => sideRight; set { PropertySetAndNotify(value, ref sideRight); UpdateMatchingCards(); } }\n\n        public string DescSideUp { get; private set; }\n        public string DescSideLeft { get; private set; }\n        public string DescSideDown { get; private set; }\n        public string DescSideRight { get; private set; }\n\n        public BitmapImage ImageSideUp { get; } = new BitmapImage();\n        public BitmapImage ImageSideLeft { get; } = new BitmapImage();\n        public BitmapImage ImageSideDown { get; } = new BitmapImage();\n        public BitmapImage ImageSideRight { get; } = new BitmapImage();\n\n        public BitmapImage ImageCard { get; } = new BitmapImage();\n        public string CardDesc => (HashProxy == null) ? null : string.Format(\"{0} [{1}-{2}-{3}-{4}]\",\n            HashProxy.cardState.card == null ? loc.strings.AdjustForm_Dynamic_UnknownOwner : HashProxy.cardState.card.Name.GetLocalized(),\n            HashProxy.cardState.sideNumber[0], HashProxy.cardState.sideNumber[1], HashProxy.cardState.sideNumber[2], HashProxy.cardState.sideNumber[3]);\n\n        public string CardState => (HashProxy == null) ? null : GetLocalizedCardState(HashProxy.cardState.state);\n\n        public BulkObservableCollection<CardModelProxy> MatchingCards { get; } = new BulkObservableCollection<CardModelProxy>();\n        private CardModelProxy selectedMatch;\n        public CardModelProxy SelectedMatch { get => selectedMatch; set => PropertySetAndNotify(value, ref selectedMatch); }\n        public bool HasMultipleMatches => MatchingCards.Count > 1;\n\n        public ICommand CommandSave { get; private set; }\n\n        public event Action<bool?> RequestDialogWindowClose;\n\n        public string AdjustForm_CancelButton => loc.strings.AdjustForm_CancelButton;\n        public string AdjustForm_SaveButton => loc.strings.AdjustForm_SaveButton;\n        public string AdjustForm_CardDown => loc.strings.AdjustForm_CardDown;\n        public string AdjustForm_CardLeft => loc.strings.AdjustForm_CardLeft;\n        public string AdjustForm_CardList => loc.strings.AdjustForm_CardList;\n        public string AdjustForm_CardRight => loc.strings.AdjustForm_CardRight;\n        public string AdjustForm_CardStatus => loc.strings.AdjustForm_CardStatus;\n        public string AdjustForm_CardUp => loc.strings.AdjustForm_CardUp;\n        public string AdjustForm_Current => loc.strings.AdjustForm_Current;\n\n        public AdjustCardViewModel()\n        {\n            // design time only\n        }\n\n        public AdjustCardViewModel(ImageCardDataViewModel cardData)\n        {\n            HashProxy = cardData;\n\n            SideUp = HashProxy.cardState.sideNumber[0];\n            SideLeft = HashProxy.cardState.sideNumber[1];\n            SideDown = HashProxy.cardState.sideNumber[2];\n            SideRight = HashProxy.cardState.sideNumber[3];\n\n            DescSideUp = GenerateSideInfo(ImageSideUp, HashProxy.cardState.sideInfo[0]);\n            DescSideLeft = GenerateSideInfo(ImageSideLeft, HashProxy.cardState.sideInfo[1]);\n            DescSideDown = GenerateSideInfo(ImageSideDown, HashProxy.cardState.sideInfo[2]);\n            DescSideRight = GenerateSideInfo(ImageSideRight, HashProxy.cardState.sideInfo[3]);\n            GenerateCardImage();\n            UpdateMatchingCards();\n\n            CommandSave = new RelayCommand<object>((_) => RequestDialogWindowClose.Invoke(true), (_) => (HashProxy != null) && (selectedMatch != null) && (selectedMatch.cardOb != HashProxy.cardState.card));\n        }\n\n        public string GetDialogWindowTitle()\n        {\n            return loc.strings.AdjustForm_Title;\n        }\n\n        private string GetLocalizedCardState(ScannerTriad.ECardState value)\n        {\n            switch (value)\n            {\n                case ScannerTriad.ECardState.Hidden:\n                    return loc.strings.AdjustForm_Dynamic_CardState_Hidden;\n                case ScannerTriad.ECardState.Locked:\n                    return loc.strings.AdjustForm_Dynamic_CardState_Locked;\n                case ScannerTriad.ECardState.Visible:\n                    return loc.strings.AdjustForm_Dynamic_CardState_Visible;\n                case ScannerTriad.ECardState.PlacedRed:\n                    return loc.strings.AdjustForm_Dynamic_CardState_PlacedRed;\n                case ScannerTriad.ECardState.PlacedBlue:\n                    return loc.strings.AdjustForm_Dynamic_CardState_PlacedBlue;\n            }\n            return loc.strings.AdjustForm_Dynamic_CardState_None;\n        }\n\n        private string GenerateSideInfo(BitmapImage image, ScannerTriad.CardState.SideInfo sideInfo)\n        {\n            var hashSize = ScannerTriad.GetDigitHashSize();\n            var hashPreview = new ImageUtils.HashPreview\n            {\n                bounds = new System.Drawing.Rectangle(0, 0, hashSize.Width, hashSize.Height),\n                hashValues = sideInfo.hashValues\n            };\n\n            var bitmap = new System.Drawing.Bitmap(hashSize.Width, hashSize.Height, PixelFormat.Format32bppArgb);\n            ImageUtils.DrawDebugHash(bitmap, hashPreview, System.Drawing.Color.White);\n\n            WriteBitmapToImage(bitmap, image);\n            bitmap.Dispose();\n\n            return sideInfo.hasOverride ? loc.strings.AdjustForm_Dynamic_Digit_Override :\n                string.Format(\"{0}: {1:P0}\", loc.strings.AdjustForm_Dynamic_Digit_Default, sideInfo.matchPct);\n        }\n\n        private void GenerateCardImage()\n        {\n            var bitmap = ImageUtils.CreatePreviewImage(HashProxy.cardState.sourceImage, HashProxy.cardState.bounds, System.Drawing.Rectangle.Empty);\n            WriteBitmapToImage(bitmap, ImageCard);\n            bitmap.Dispose();\n        }\n\n        private void WriteBitmapToImage(System.Drawing.Bitmap bitmap, BitmapImage image)\n        {\n            using (var memory = new MemoryStream())\n            {\n                bitmap.Save(memory, ImageFormat.Png);\n                memory.Position = 0;\n\n                image.BeginInit();\n                image.StreamSource = memory;\n                image.CacheOption = BitmapCacheOption.OnLoad;\n                image.EndInit();\n                image.Freeze();\n            }\n        }\n\n        private void UpdateMatchingCards()\n        {\n            MatchingCards.SuspendNotifies();\n            MatchingCards.Clear();\n\n            var cardDB = TriadCardDB.Get();\n            TriadCard foundCard = cardDB.Find(SideUp, SideLeft, SideDown, SideRight);\n\n            CardModelProxy newMatch = null;\n            if (foundCard != null)\n            {\n                var modelProxyDB = ModelProxyDB.Get();\n\n                if (foundCard.SameNumberId < 0)\n                {\n                    MatchingCards.Add(modelProxyDB.GetCardProxy(foundCard));\n                }\n                else\n                {\n                    foreach (var card in cardDB.sameNumberMap[foundCard.SameNumberId])\n                    {\n                        MatchingCards.Add(modelProxyDB.GetCardProxy(foundCard));\n                    }\n                }\n\n                newMatch = MatchingCards[0];\n            }\n\n            if (selectedMatch != newMatch)\n            {\n                SelectedMatch = newMatch;\n                CommandManager.InvalidateRequerySuggested();\n            }\n\n            MatchingCards.ResumeNotifies();\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/viewmodel/AdjustHashViewModel.cs",
    "content": "﻿using System;\nusing System.Windows.Input;\n\nnamespace FFTriadBuddy.UI\n{\n    public class AdjustHashViewModel : LocalizedViewModel, IDialogWindowViewModel\n    {\n        public ImageHashDataModelProxy HashProxy { get; set; }\n\n        public int MatchDistance => HashProxy.hashData.matchDistance;\n        public string MatchDistanceInfo =>\n            HashProxy.hashData.isAuto ? loc.strings.AdjustForm_Dynamic_Distance_Classifier :\n            HashProxy.hashData.matchDistance == 0 ? loc.strings.AdjustForm_Dynamic_Distance_Exact :\n            loc.strings.AdjustForm_Dynamic_Distance_DefaultHint;\n\n        private IImageHashMatch selectedMatch;\n        public IImageHashMatch SelectedMatch { get { UpdateSelectedMatch(); return selectedMatch; } set => PropertySetAndNotify(value, ref selectedMatch); }\n\n        public ICommand CommandSave { get; private set; }\n        public event Action<bool?> RequestDialogWindowClose;\n\n        public string AdjustForm_Current => loc.strings.AdjustForm_Current;\n        public string AdjustForm_Distance => loc.strings.AdjustForm_Distance;\n        public string AdjustForm_HashList => loc.strings.AdjustForm_HashList;\n        public string AdjustForm_CancelButton => loc.strings.AdjustForm_CancelButton;\n        public string AdjustForm_SaveButton => loc.strings.AdjustForm_SaveButton;\n\n        public AdjustHashViewModel()\n        {\n            CommandSave = new RelayCommand<object>((_) => RequestDialogWindowClose.Invoke(true), (_) => (HashProxy != null) && (selectedMatch != HashProxy.CurrentMatch));\n        }\n\n        public string GetDialogWindowTitle()\n        {\n            return loc.strings.AdjustForm_Title;\n        }\n\n        private void UpdateSelectedMatch()\n        {\n            if (selectedMatch == null && HashProxy != null)\n            {\n                selectedMatch = HashProxy.CurrentMatch;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/viewmodel/CardCollectionViewModel.cs",
    "content": "﻿using System.Windows.Input;\n\nnamespace FFTriadBuddy.UI\n{\n    public class CardCollectionViewModel : LocalizedViewModel\n    {\n        private BulkObservableCollection<CardViewModel> cards = new BulkObservableCollection<CardViewModel>();\n        public BulkObservableCollection<CardViewModel> Cards => cards;\n\n        private string name;\n        public string Name { get => name; set => PropertySetAndNotify(value, ref name); }\n\n        public ICommand CommandSelect { get; protected set; }\n\n        public CardCollectionViewModel()\n        {\n            CommandSelect = new RelayCommand<CardViewModel>((cardInfo) =>\n            {\n                if (cardInfo.CardModel != null)\n                {\n                    cardInfo.CardModel.IsOwned = !cardInfo.CardModel.IsOwned;\n                }\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/viewmodel/CardViewModel.cs",
    "content": "﻿using System.Windows.Media.Imaging;\n\nnamespace FFTriadBuddy.UI\n{\n    public enum ECardOwner\n    {\n        None,\n        Blue,\n        Red,\n    }\n\n    public enum ECardDragMode\n    {\n        None,\n        DragOut,\n        DragIn,\n    }\n\n    public class CardViewModel : BaseViewModel\n    {\n        private CardModelProxy cardModel = null;\n        public CardModelProxy CardModel\n        {\n            get { return cardModel; }\n            set\n            {\n                if (cardModel == value) { return; }\n                cardModel = value;\n\n                if (cardModel != null)\n                {\n                    IconDB icons = IconDB.Get();\n                    CardImage = isUsingImageBig ? icons.mapCardImagesBig[cardModel.cardOb.Id] : icons.mapCardImages[cardModel.cardOb.Id];\n                    TypeImage = icons.mapCardTypes[cardModel.cardOb.Type];\n                    RarityImage = icons.mapCardRarities[cardModel.cardOb.Rarity];\n                }\n                else\n                {\n                    CardImage = null;\n                    TypeImage = null;\n                    RarityImage = null;\n                }\n\n                // notify all linked properties\n                OnPropertyChanged();\n                OnPropertyChanged(\"CardImage\");\n                OnPropertyChanged(\"RarityImage\");\n                OnPropertyChanged(\"TypeImage\");\n                OnPropertyChanged(\"HasCardImage\");\n                OnPropertyChanged(\"NumUp\");\n                OnPropertyChanged(\"NumLeft\");\n                OnPropertyChanged(\"NumDown\");\n                OnPropertyChanged(\"NumRight\");\n                OnPropertyChanged(\"Tooltip\");\n            }\n        }\n\n        public BitmapImage CardImage { get; private set; }\n        public BitmapImage RarityImage { get; private set; }\n        public BitmapImage TypeImage { get; private set; }\n        public bool HasCardImage => CardImage != null;\n\n        public int NumUp => cardModel?.cardOb.Sides[(int)ETriadGameSide.Up] ?? 0;\n        public int NumLeft => cardModel?.cardOb.Sides[(int)ETriadGameSide.Left] ?? 0;\n        public int NumDown => cardModel?.cardOb.Sides[(int)ETriadGameSide.Down] ?? 0;\n        public int NumRight => cardModel?.cardOb.Sides[(int)ETriadGameSide.Right] ?? 0;\n        public string Tooltip => cardModel?.NameLocalized;\n\n        public object OwnerObject { get; set; }\n\n        private int ownerIndex = -1;\n        public int OwnerIndex { get => ownerIndex; set => PropertySetAndNotify(value, ref ownerIndex); }\n\n        private int numMod = 0;\n        public int NumMod { get => numMod; set { PropertySetAndNotify(value, ref numMod); OnPropertyChanged(\"IsModNegative\"); } }\n        public bool IsModNegative => numMod < 0;\n\n        private BitmapImage dragImage;\n        public BitmapImage DragImage { get => dragImage; set => PropertySetAndNotify(value, ref dragImage); }\n\n        private ECardDragMode cardDragMode;\n        public ECardDragMode CardDragMode { get => cardDragMode; set => PropertySetAndNotify(value, ref cardDragMode); }\n\n        private ECardOwner cardOwner;\n        public ECardOwner CardOwner { get => cardOwner; set => PropertySetAndNotify(value, ref cardOwner); }\n\n        private bool isHidden;\n        public bool IsHidden { get => isHidden; set => PropertySetAndNotify(value, ref isHidden); }\n\n        private bool isHighlighted;\n        public bool IsHighlighted { get => isHighlighted; set => PropertySetAndNotify(value, ref isHighlighted); }\n\n        private bool isPreview;\n        public bool IsPreview { get => isPreview; set => PropertySetAndNotify(value, ref isPreview); }\n\n        private bool isShowingDetails = true;\n        public bool IsShowingDetails { get => isShowingDetails; set => PropertySetAndNotify(value, ref isShowingDetails); }\n\n        private bool isShowingLock = false;\n        public bool IsShowingLock { get => isShowingLock; set => PropertySetAndNotify(value, ref isShowingLock); }\n\n        private bool isUsingImageBig = false;\n        public bool IsUsingImageBig\n        {\n            get => isUsingImageBig;\n            set\n            {\n                PropertySetAndNotify(value, ref isUsingImageBig);\n\n                IconDB icons = IconDB.Get();\n                CardImage =\n                    (cardModel == null) ? null :\n                    isUsingImageBig ? icons.mapCardImagesBig[cardModel.cardOb.Id] :\n                    icons.mapCardImages[CardModel.cardOb.Id];\n                OnPropertyChanged();\n                OnPropertyChanged(\"CardImage\");\n                OnPropertyChanged(\"HasCardImage\");\n            }\n        }\n\n        public void Assign(TriadCardInstance cardData)\n        {\n            CardModel = (cardData == null) ? null : ModelProxyDB.Get().GetCardProxy(cardData.card);\n            NumMod = cardData?.scoreModifier ?? 0;\n            IsShowingDetails = cardData != null;\n            CardOwner = (cardData == null) ? ECardOwner.None :\n                (cardData.owner == ETriadCardOwner.Blue) ? ECardOwner.Blue\n                : ECardOwner.Red;\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/viewmodel/ContextActionViewModel.cs",
    "content": "﻿using System.Windows.Input;\n\nnamespace FFTriadBuddy.UI\n{\n    public class ContextActionViewModel : BaseViewModel\n    {\n        public string Name { get; set; }\n        public ICommand Command { get; set; }\n        public bool IsSeparator { get; set; }\n        public bool IsCheckbox { get; set; }\n        public bool IsChecked { get; set; }\n\n        public override string ToString()\n        {\n            return IsSeparator ? \"<< separator >>\" : Name;\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/viewmodel/DeckViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.Specialized;\nusing System.ComponentModel;\nusing System.Windows.Data;\nusing System.Windows.Input;\n\nnamespace FFTriadBuddy.UI\n{\n    public class DeckViewModel : CardCollectionViewModel, ICardDragDropTarget\n    {\n        public class CardPickerParams\n        {\n            public CardViewModel cardVM;\n            public CardModelProxy cardModel;\n        }\n\n        private CollectionViewSource cardPickerItems = new CollectionViewSource();\n        public ICollectionView CardPickerItems => cardPickerItems.View;\n        public event Action<DeckViewModel> OnCardsChanged;\n\n        public bool CanReceiveDropCards = true;\n        public int NumToSelect { get; set; } = 0;\n\n        private List<int> selectedIndices = new List<int>();\n        public List<int> SelectedIndices => selectedIndices;\n        public int NumSelected => selectedIndices.Count;\n        public event Action<DeckViewModel> OnSelectionChanged;\n\n        private ECardOwner deckOwner = ECardOwner.None;\n        public ECardOwner DeckOwner { get => deckOwner; set => PropertySetAndNotify(value, ref deckOwner); }\n\n        private bool isShowingDetails = true;\n        public bool IsShowingDetails { get => isShowingDetails; set => PropertySetAndNotify(value, ref isShowingDetails); }\n\n        private bool useSmallIcons = false;\n        private bool canUseBigIcons = true;\n        public bool CanUseBigIcons { get => canUseBigIcons; set { PropertySetAndNotify(value, ref canUseBigIcons); UpdateAllCardIcons(); } }\n\n        private bool isUsingOnlyOwnedCards = false;\n        public bool IsUsingOnlyOwnedCards { get => isUsingOnlyOwnedCards; set { PropertySetAndNotify(value, ref isUsingOnlyOwnedCards); CardPickerItems.Refresh(); } }\n\n        private CardViewModel selectedPreview = null;\n        public CardViewModel SelectedPreview { get => selectedPreview; set => PropertySetAndNotify(value, ref selectedPreview); }\n\n        public ICommand CommandToggleLock { get; private set; }\n        public ICommand CommandPickCard { get; private set; }\n\n        public string DeckCtrl_CtxMenu_LockForOptimization => loc.strings.DeckCtrl_CtxMenu_LockForOptimization;\n        public string DeckCtrl_CtxMenu_PickCard => loc.strings.DeckCtrl_CtxMenu_PickCard;\n        public string DeckCtrl_CtxMenu_UseOnlyOwned => loc.strings.DeckCtrl_CtxMenu_UseOnlyOwned;\n\n        public DeckViewModel()\n        {\n            CommandSelect = new RelayCommand<CardViewModel>(CommandSelectFunc, (card) => (NumToSelect > 0) && (card != null));\n            CommandPickCard = new RelayCommand<CardPickerParams>(CommandPickCardFunc);\n            CommandToggleLock = new RelayCommand<CardViewModel>(CommandToggleLockFunc);\n\n            Cards.CollectionChanged += Cards_CollectionChanged;\n\n            cardPickerItems.Source = ModelProxyDB.Get().Cards;\n            cardPickerItems.View.Filter += CardPickerItems_Filter;\n\n            useSmallIcons = PlayerSettingsDB.Get().useSmallIcons;\n            // CanUseBigIcons can be overriden to disable big icons permanently\n            SettingsWeakEventManager.AddHandler(PageInfoViewModel.lastInstance, PageInfo_OnSettingsChanged);\n        }\n\n        private void PageInfo_OnSettingsChanged(object sender, SettingsEventArgs e)\n        {\n            if (e.Type == SettingsEventArgs.Setting.UseSmallIcons)\n            {\n                useSmallIcons = e.BoolValue;\n                UpdateAllCardIcons();\n            }\n        }\n\n        public bool IsCardDropAllowed(CardViewModel sourceCard, object sourceContainer)\n        {\n            return CanReceiveDropCards && (sourceContainer == this);\n        }\n\n        public void OnCardDragEnter(CardViewModel sourceCard, CardViewModel destCard)\n        {\n            sourceCard.DragImage = destCard.CardImage;\n            sourceCard.CardDragMode = ECardDragMode.DragOut;\n\n            destCard.DragImage = sourceCard.CardImage;\n            destCard.CardDragMode = ECardDragMode.DragIn;\n        }\n\n        public void OnCardDragLeave(CardViewModel sourceCard, CardViewModel destCard)\n        {\n            sourceCard.CardDragMode = ECardDragMode.None;\n            destCard.CardDragMode = ECardDragMode.None;\n        }\n\n        public void OnCardDrop(CardViewModel sourceCard, CardViewModel destCard, object sourceContainer)\n        {\n            sourceCard.CardDragMode = ECardDragMode.None;\n            destCard.CardDragMode = ECardDragMode.None;\n\n            int cachedSrcIdx = sourceCard.OwnerIndex;\n            int cachedDestIdx = destCard.OwnerIndex;\n\n            // OwnerIndex will be updated from Cards change notify\n            Cards[cachedSrcIdx] = destCard;\n            Cards[cachedDestIdx] = sourceCard;\n\n            OnCardsChanged?.Invoke(this);\n        }\n\n        public void SetCustomSelect(RelayCommand<CardViewModel> selectCommand)\n        {\n            CommandSelect = selectCommand;\n        }\n\n        private void CommandSelectFunc(CardViewModel cardOb)\n        {\n            int idxToToggle = cardOb.OwnerIndex;\n            if (selectedIndices.Contains(idxToToggle))\n            {\n                selectedIndices.Remove(idxToToggle);\n                Cards[idxToToggle].IsHighlighted = false;\n            }\n            else\n            {\n                while (selectedIndices.Count >= NumToSelect)\n                {\n                    int idxToRemove = selectedIndices[0];\n                    selectedIndices.RemoveAt(0);\n                    Cards[idxToRemove].IsHighlighted = false;\n                }\n\n                selectedIndices.Add(idxToToggle);\n                Cards[idxToToggle].IsHighlighted = true;\n            }\n\n            OnPropertyChanged(\"NumSelected\");\n            OnSelectionChanged?.Invoke(this);\n        }\n\n        private void CommandToggleLockFunc(CardViewModel cardOb)\n        {\n            if (cardOb != null)\n            {\n                cardOb.IsShowingLock = !cardOb.IsShowingLock;\n            }\n        }\n\n        private void CommandPickCardFunc(CardPickerParams param)\n        {\n            if (param.cardVM != null && param.cardModel != null)\n            {\n                if (param.cardVM.CardModel == null || param.cardVM.CardModel.Id != param.cardModel.Id)\n                {\n                    param.cardVM.CardModel = param.cardModel;\n                    OnCardsChanged?.Invoke(this);\n                }\n            }\n        }\n\n        private bool CardPickerItems_Filter(object item)\n        {\n            return !isUsingOnlyOwnedCards || ModelProxyDB.Get().OwnedCards.Contains(item as CardModelProxy);\n        }\n\n        private void Cards_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)\n        {\n            if (e.NewItems != null)\n            {\n                for (int idx = 0; idx < e.NewItems.Count; idx++)\n                {\n                    var cardVM = e.NewItems[idx] as CardViewModel;\n                    if (cardVM != null)\n                    {\n                        cardVM.OwnerObject = this;\n                        cardVM.OwnerIndex = e.NewStartingIndex + idx;\n                        UpdateCardVisuals(cardVM);\n                    }\n                }\n            }\n        }\n\n        private void UpdateCardVisuals(CardViewModel cardVM)\n        {\n            cardVM.CardOwner = DeckOwner;\n            cardVM.IsShowingDetails = IsShowingDetails;\n            cardVM.IsUsingImageBig = CanUseBigIcons && !useSmallIcons;\n            cardVM.IsPreview = SelectedPreview == cardVM;\n        }\n\n        private void UpdateAllCardIcons()\n        {\n            foreach (var cardVM in Cards)\n            {\n                if (cardVM != null)\n                {\n                    cardVM.IsUsingImageBig = CanUseBigIcons && !useSmallIcons;\n                }\n            }\n        }\n\n        public void ForceCardsUpdate()\n        {\n            OnCardsChanged?.Invoke(this);\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/viewmodel/FavDeckEditViewModel.cs",
    "content": "﻿using System;\nusing System.Windows.Input;\n\nnamespace FFTriadBuddy.UI\n{\n    public class FavDeckEditViewModel : LocalizedViewModel, IDialogWindowViewModel\n    {\n        public DeckViewModel FavDeck { get; set; }\n\n        public string FavDeckForm_Info => loc.strings.FavDeckForm_Info;\n        public string FavDeckForm_Name => loc.strings.FavDeckForm_Name;\n        public string AdjustForm_CancelButton => loc.strings.AdjustForm_CancelButton;\n        public string AdjustForm_SaveButton => loc.strings.AdjustForm_SaveButton;\n\n        public ICommand CommandSave { get; private set; }\n        public event Action<bool?> RequestDialogWindowClose;\n\n        public FavDeckEditViewModel()\n        {\n            CommandSave = new RelayCommand<object>((_) => RequestDialogWindowClose.Invoke(true), (_) => FavDeck?.Name.Length > 0);\n        }\n\n        public string GetDialogWindowTitle()\n        {\n            return loc.strings.FavDeckForm_Title;\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/viewmodel/ImageCardDataViewModel.cs",
    "content": "﻿namespace FFTriadBuddy.UI\n{\n    public class ImageCardDataViewModel : LocalizedViewModel\n    {\n        public readonly ScannerTriad.CardState cardState;\n\n        private string cachedType;\n        public string DescType => cachedType;\n\n        private string cachedSides;\n        public string DescSides => cachedSides;\n\n        private string cachedName;\n        public string NameLocalized => cachedName;\n\n        public bool IsDetected => cardState.card != null;\n\n        public ImageCardDataViewModel(ScannerTriad.CardState cardState)\n        {\n            this.cardState = cardState;\n            cachedSides =\n                (cardState.sideNumber == null) ? \"\" :\n                string.Format(\"{0}-{1}-{2}-{3}\", cardState.sideNumber[0], cardState.sideNumber[1], cardState.sideNumber[2], cardState.sideNumber[3]);\n\n            UpdateCachedText();\n        }\n\n        public override void RefreshLocalization()\n        {\n            UpdateCachedText();\n            OnPropertyChanged(\"DescType\");\n            OnPropertyChanged(\"NameLocalized\");\n            // DescSides doesn't depend on loc\n        }\n\n        public void UpdateCachedText()\n        {\n            cachedType =\n                (cardState.location == ScannerTriad.ECardLocation.BlueDeck) ? string.Format(loc.strings.MainForm_Dynamic_Screenshot_CardLocation_BlueDeck, cardState.locationContext + 1) :\n                (cardState.location == ScannerTriad.ECardLocation.RedDeck) ? string.Format(loc.strings.MainForm_Dynamic_Screenshot_CardLocation_RedDeck, cardState.locationContext + 1) :\n                string.Format(loc.strings.MainForm_Dynamic_Screenshot_CardLocation_Board,\n                    cardState.locationContext < 3 ? loc.strings.MainForm_Dynamic_Screenshot_BoardRow_Top :\n                        (cardState.locationContext < 6) ? loc.strings.MainForm_Dynamic_Screenshot_BoardRow_Middle :\n                        loc.strings.MainForm_Dynamic_Screenshot_BoardRow_Bottom,\n                    (cardState.locationContext % 3) + 1);\n\n            cachedName = cardState.card == null ? loc.strings.MainForm_Dynamic_Screenshot_CardNotDetected : cardState.card.Name.GetLocalized();\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/viewmodel/LocalSavesViewModel.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Windows.Input;\n\nnamespace FFTriadBuddy.UI\n{\n    public class LocalSavesViewModel : LocalizedViewModel\n    {\n        public string LocalSaves_Export => loc.strings.LocalSaves_Export;\n        public string LocalSaves_Import => loc.strings.LocalSaves_Import;\n        public string LocalSaves_ShowBackupFolder => loc.strings.LocalSaves_ShowBackupFolder;\n        public string LocalSaves_Title => loc.strings.LocalSaves_Title;\n\n        public ICommand CommandExport { get; private set; }\n        public ICommand CommandImport { get; private set; }\n        public ICommand CommandViewBackups { get; private set; }\n\n        private readonly string defaultPath;\n\n        public LocalSavesViewModel()\n        {\n            CommandExport = new RelayCommand<object>(CommandExportFunc);\n            CommandImport = new RelayCommand<object>(CommandImportFunc);\n            CommandViewBackups = new RelayCommand<object>(CommandViewBackupFunc);\n\n            defaultPath = PlayerSettingsDB.Get().GetBackupFolderPath();\n        }\n\n        private void CommandExportFunc(object dummyParam)\n        {\n            var dialog = new Microsoft.Win32.SaveFileDialog\n            {\n                FileName = \"export\",\n                DefaultExt = \".json\",\n                Filter = \"Settings|*.json\",\n                InitialDirectory = defaultPath,\n                OverwritePrompt = true\n            };\n\n            var result = dialog.ShowDialog();\n            if (result == true)\n            {\n                try\n                {\n                    var fileName = dialog.FileName;\n                    Logger.WriteLine(\"Exporting settings to: {0}\", fileName);\n\n                    string jsonStr = PlayerSettingsDB.Get().SaveToJson(true);\n                    if (File.Exists(fileName))\n                    {\n                        File.Delete(fileName);\n                    }\n\n                    File.WriteAllText(fileName, jsonStr);\n                }\n                catch (Exception ex)\n                {\n                    Logger.WriteLine(\"Failed to export: {0}\", ex);\n                }\n            }\n        }\n\n        private void CommandImportFunc(object dummyParam)\n        {\n            var dialog = new Microsoft.Win32.OpenFileDialog\n            {\n                FileName = \"export\",\n                DefaultExt = \".json\",\n                Filter = \"Settings|*.json\",\n                InitialDirectory = defaultPath,\n                CheckFileExists = true\n            };\n\n            var result = dialog.ShowDialog();\n            if (result == true)\n            {\n                try\n                {\n                    var fileName = dialog.FileName;\n                    Logger.WriteLine(\"Importing settings from: {0}\", fileName);\n\n                    string jsonStr = File.ReadAllText(fileName);\n\n                    var settingsDB = PlayerSettingsDB.Get();\n                    settingsDB.LoadFromJson(jsonStr);\n                    settingsDB.OnImport();\n                }\n                catch (Exception ex)\n                {\n                    Logger.WriteLine(\"Failed to import: {0}\", ex);\n                }\n            }\n        }\n\n        private void CommandViewBackupFunc(object dummyParam)\n        {\n            Process.Start(defaultPath);\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/viewmodel/MainWindowViewModel.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System;\nusing System.Linq;\nusing System.Reflection;\nusing System.Threading.Tasks;\nusing System.Windows.Input;\nusing System.Windows.Media.Imaging;\n\nnamespace FFTriadBuddy.UI\n{\n    public class MainWindowViewModel : LocalizedViewModel\n    {\n        public enum PageType\n        {\n            Setup,\n            Screenshot,\n            Simulate,\n            Cards,\n            Npcs,\n            Info,\n        }\n\n        public PageSetupViewModel PageSetup { get; }\n        public PageScreenshotViewModel PageScreenshot { get; }\n        public PageSimulateViewModel PageSimulate { get; }\n        public PageCardsViewModel PageCards { get; }\n        public PageNpcsViewModel PageNpcs { get; }\n        public PageInfoViewModel PageInfo { get; }\n        public OverlayWindowViewModel Overlay { get; }\n\n        public TriadGameModel GameModel;\n\n        private int activePageIndex = 0;\n        public int ActivePageIndex { get => activePageIndex; set => PropertySetAndNotify(value, ref activePageIndex); }\n\n        private bool isUpdateNotifyVisible = false;\n        public bool IsUpdateNotifyVisible { get => isUpdateNotifyVisible; set => PropertySetAndNotify(value, ref isUpdateNotifyVisible); }\n\n        public BitmapImage LanguageFlag => IconDB.Get().mapFlags[LocResourceManager.Get().UserCultureCode];\n        public ICommand CommandChangeLanguage { get; private set; }\n        public ICommand CommandHideUpdateNotify { get; private set; }\n        public ICommand CommandDebugScreenshot { get; private set; }\n\n        public string WindowTitle => string.Format(\"{0}: {1} [{2}]\", loc.strings.App_Title, GameModel != null ? GameModel.Npc.Name.GetLocalized() : \"??\", descTitleVersion);\n        private int descTitleVersion = 0;\n\n        public string MainForm_Cards_Title => loc.strings.MainForm_Cards_Title;\n        public string MainForm_Info_Title => loc.strings.MainForm_Info_Title;\n        public string MainForm_Npcs_Title => loc.strings.MainForm_Npcs_Title;\n        public string MainForm_Screenshot_Title => loc.strings.MainForm_Screenshot_Title;\n        public string MainForm_Setup_Title => loc.strings.MainForm_Setup_Title;\n        public string MainForm_Simulate_Title => loc.strings.MainForm_Simulate_Title;\n        public string MainForm_UpdateNotify => loc.strings.MainForm_UpdateNotify;\n\n        public MainWindowViewModel()\n        {\n            GameModel = new TriadGameModel();\n            GameModel.OnNpcChanged += (npc) => OnPropertyChanged(\"WindowTitle\");\n\n            // info must be created before any deckVM (settings)\n            PageInfo = new PageInfoViewModel();\n\n            PageSetup = new PageSetupViewModel(this);\n            PageScreenshot = new PageScreenshotViewModel(this);\n            PageSimulate = new PageSimulateViewModel(this);\n            PageCards = new PageCardsViewModel(this);\n            PageNpcs = new PageNpcsViewModel(this);\n            Overlay = new OverlayWindowViewModel(this);\n\n            CommandChangeLanguage = new RelayCommand<object>(SwitchToNextLanguage);\n            CommandHideUpdateNotify = new RelayCommand<object>((o) => IsUpdateNotifyVisible = false);\n            CommandDebugScreenshot = new RelayCommand<object>((_) => PageSimulate.SpecialRules.RequestRuleDebug());\n\n            var version = Assembly.GetEntryAssembly().GetName().Version;\n            descTitleVersion = version.Major;\n\n            LocalizationDB.OnLanguageChanged += RefreshLocalization;\n            RefreshLocalization();\n\n            RunUpdateCheck();\n        }\n\n        public override void RefreshLocalization()\n        {\n            base.RefreshLocalization();\n            OnPropertyChanged(\"LanguageFlag\");\n\n            PageSetup.RefreshLocalization();\n            PageScreenshot.RefreshLocalization();\n            PageSimulate.RefreshLocalization();\n            PageCards.RefreshLocalization();\n            PageNpcs.RefreshLocalization();\n            PageInfo.RefreshLocalization();\n            Overlay.RefreshLocalization();\n        }\n\n        private void SwitchToNextLanguage(object dummyParam)\n        {\n            LocResourceManager locManager = LocResourceManager.Get();\n            string[] cultureCodes = locManager.SupportedCultureCodes.ToArray();\n\n            int currentIdx = Array.IndexOf(cultureCodes, locManager.UserCultureCode);\n            int nextValidIdx = (currentIdx < 0) ? 0 : ((currentIdx + 1) % cultureCodes.Length);\n\n            string newCultureCode = cultureCodes[nextValidIdx];\n\n            PlayerSettingsDB.Get().forcedLanguage = newCultureCode;\n            LocalizationDB.SetCurrentUserLanguage(newCultureCode);\n        }\n\n        public void SwitchToPage(PageType page)\n        {\n            ActivePageIndex = (int)page;\n        }\n\n        private void RunUpdateCheck()\n        {\n            Task updateTask = new Task(() =>\n            {\n                bool bFoundUpdate = GithubUpdater.FindAndDownloadUpdates(out string statusMsg);\n\n                App.Current.Dispatcher.Invoke(() =>\n                {\n                    Logger.WriteLine(\"Version check: \" + statusMsg);\n                    IsUpdateNotifyVisible = bFoundUpdate;\n                });\n            });\n\n            updateTask.Start();\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/viewmodel/OverlayWindowViewModel.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Drawing;\nusing System.Windows;\nusing System.Windows.Input;\nusing System.Windows.Interop;\nusing System.Windows.Media.Imaging;\nusing System.Windows.Threading;\n\nnamespace FFTriadBuddy.UI\n{\n    public class OverlayWindowViewModel : LocalizedViewModel, IOverlayWindowViewModel\n    {\n        public MainWindowViewModel MainWindow { get; private set; }\n\n        public TriadGameScreenMemory ScreenMemory = new TriadGameScreenMemory();\n        private ScreenAnalyzer ScreenAnalyzer;\n\n        public class ScreenCoordVM\n        {\n            public enum Mode\n            {\n                Default,\n                SwapWarning,\n                CapturePanel,\n                AdjustedCapturePanel,\n            }\n\n            public System.Drawing.Point ScreenCoords { get; set; }\n            public System.Drawing.Size ScreenSize { get; set; }\n            public float Duration { get; set; }\n            public Mode DrawMode { get; set; }\n        }\n\n        public class ScreenCardVM : ScreenCoordVM\n        {\n            public enum SolverResult\n            {\n                Win,\n                Draw,\n                Lose,\n            }\n\n            public SolverResult Result { get; set; }\n        }\n\n        public class ScreenCactpotVM : ScreenCoordVM\n        {\n            public enum LineType\n            {\n                Horizontal,\n                Vertical,\n                DiagSE,\n                DiagSW,\n            }\n\n            public LineType Line { get; set; }\n        }\n\n        public class BoardInfoVM : BaseViewModel\n        {\n            private CardViewModel card;\n            public CardViewModel Card { get => card; set => PropertySetAndNotify(value, ref card); }\n\n            private int cactpotNum = 0;\n            public int CactpotNum { get => cactpotNum; set => PropertySetAndNotify(value, ref cactpotNum); }\n        }\n\n        private ScreenCoordVM markerCapturePanel;\n        public ScreenCoordVM MarkerCapturePanel { get => markerCapturePanel; set => PropertySetAndNotify(value, ref markerCapturePanel); }\n        private bool canMoveCapturePanelOnScan;\n\n        private ScreenCoordVM markerSwapWarning;\n        public ScreenCoordVM MarkerSwapWarning { get => markerSwapWarning; set => PropertySetAndNotify(value, ref markerSwapWarning); }\n\n        private ScreenCoordVM markerSwapCard;\n        public ScreenCoordVM MarkerSwapCard { get => markerSwapCard; set => PropertySetAndNotify(value, ref markerSwapCard); }\n\n        private ScreenCoordVM markerCactpotCircle;\n        public ScreenCoordVM MarkerCactpotCircle { get => markerCactpotCircle; set => PropertySetAndNotify(value, ref markerCactpotCircle); }\n\n        private ScreenCardVM markerDeck;\n        public ScreenCardVM MarkerDeck { get => markerDeck; set => PropertySetAndNotify(value, ref markerDeck); }\n\n        private ScreenCardVM markerBoard;\n        public ScreenCardVM MarkerBoard { get => markerBoard; set => PropertySetAndNotify(value, ref markerBoard); }\n\n        private ScreenCactpotVM markerCactpotLine;\n        public ScreenCactpotVM MarkerCactpotLine { get => markerCactpotLine; set => PropertySetAndNotify(value, ref markerCactpotLine); }\n\n        public DeckViewModel BlueDeck { get; } = new DeckViewModel() { IsShowingDetails = false };\n        public DeckViewModel RedDeck { get; } = new DeckViewModel() { IsShowingDetails = false };\n        public CardCollectionViewModel RedKnownCards { get; } = new CardCollectionViewModel();\n        public CardCollectionViewModel RedUnknownCards { get; } = new CardCollectionViewModel();\n\n        public BulkObservableCollection<BoardInfoVM> Board { get; } = new BulkObservableCollection<BoardInfoVM>();\n\n        private int scanId;\n        public int ScanId { get => scanId; set { PropertySetAndNotify(value, ref scanId); OnPropertyChanged(\"DescScanId\"); } }\n        public string DescScanId => string.Format(loc.strings.OverlayForm_Details_ScanId, scanId);\n\n        private int numRedPlaced;\n        public int NumRedPlaced { get => numRedPlaced; set { PropertySetAndNotify(value, ref numRedPlaced); OnPropertyChanged(\"DescRedPlaced\"); } }\n        public string DescRedPlaced => string.Format(loc.strings.OverlayForm_Details_RedPlacedAll, numRedPlaced);\n\n        private int numRedVarPlaced;\n        public int NumRedVarPlaced { get => numRedVarPlaced; set { PropertySetAndNotify(value, ref numRedVarPlaced); OnPropertyChanged(\"DescRedVarPlaced\"); } }\n        public string DescRedVarPlaced => string.Format(loc.strings.OverlayForm_Details_RedPlacedVariable, numRedVarPlaced);\n\n        private string descNpc;\n        public string DescNpc { get => descNpc; set => PropertySetAndNotify(value, ref descNpc); }\n\n        private string descRules;\n        public string DescRules { get => descRules; set => PropertySetAndNotify(value, ref descRules); }\n\n        private string descAnalyzerState;\n        public string DescAnalyzerState { get => descAnalyzerState; set => PropertySetAndNotify(value, ref descAnalyzerState); }\n\n        private BitmapSource analyzerStateIcon;\n        public BitmapSource AnalyzerStateIcon { get => analyzerStateIcon; set => PropertySetAndNotify(value, ref analyzerStateIcon); }\n\n        private bool useAutoScan;\n        public bool UseAutoScan\n        {\n            get => useAutoScan;\n            set\n            {\n                PropertySetAndNotify(value, ref useAutoScan);\n                if (!value)\n                {\n                    DisableAutoScanTimers();\n                }\n            }\n        }\n\n        private bool isAutoScanActive;\n        public bool IsAutoScanActive { get => isAutoScanActive; set => PropertySetAndNotify(value, ref isAutoScanActive); }\n\n        private bool useDetails;\n        public bool UseDetails\n        {\n            get => useDetails;\n            set\n            {\n                if (value != useDetails)\n                {\n                    PropertySetAndNotify(value, ref useDetails);\n                }\n\n                OnPropertyChanged(\"ShowDetailsDeck\");\n                OnPropertyChanged(\"ShowDetailsBoard\");\n            }\n        }\n        public bool ShowDetailsDeck => (ScreenAnalyzer == null) || (useDetails && (ScreenAnalyzer?.activeScanner is ScannerTriad));\n        public bool ShowDetailsBoard => (ScreenAnalyzer == null) || useDetails;\n\n        private Dictionary<Icon, BitmapSource> cachedIcons = new Dictionary<Icon, BitmapSource>();\n        private DispatcherTimer timerAutoScan;\n        private DispatcherTimer timerAutoScanUpkeep;\n        private Stopwatch perfTimer = new Stopwatch();\n        private bool canStopAutoCapture = true;\n        private bool canRunAutoCapture = false;\n\n        public ICommand CommandCapture { get; private set; }\n        public ICommand CommandToggleDetails { get; private set; }\n\n        public string OverlayForm_Capture_AutoScan => loc.strings.OverlayForm_Capture_AutoScan;\n        public string OverlayForm_Capture_Button => loc.strings.OverlayForm_Capture_Button;\n        public string OverlayForm_Capture_Details => loc.strings.OverlayForm_Capture_Details;\n        public string OverlayForm_Capture_Status => loc.strings.OverlayForm_Capture_Status;\n        public string OverlayForm_CardInfo_Swapped => loc.strings.OverlayForm_CardInfo_Swapped;\n        public string OverlayForm_DeckInfo_Mismatch => loc.strings.OverlayForm_DeckInfo_Mismatch;\n        public string OverlayForm_Details_RedDeck => loc.strings.OverlayForm_Details_RedDeck;\n        public string OverlayForm_Details_RedInfo => loc.strings.OverlayForm_Details_RedInfo;\n\n        public OverlayWindowViewModel()\n        {\n            // design time only\n        }\n\n        public OverlayWindowViewModel(MainWindowViewModel mainVM)\n        {\n            MainWindow = mainVM;\n            ScreenAnalyzer = mainVM.PageScreenshot.ScreenAnalyzer;\n\n            for (int idx = 0; idx < 9; idx++)\n            {\n                Board.Add(new BoardInfoVM() { Card = new CardViewModel() { IsShowingDetails = false } });\n            }\n\n            CommandCapture = new RelayCommand<object>(CommandCapureFunc);\n            CommandToggleDetails = new RelayCommand<bool>((wantsDetails) => UseDetails = wantsDetails);\n\n            mainVM.GameModel.OnDeckChanged += (deck) => ScreenMemory.UpdatePlayerDeck(deck);\n            mainVM.GameModel.OnNpcChanged += GameModel_OnNpcChanged;\n            ScreenMemory.UpdatePlayerDeck(mainVM.GameModel.PlayerDeck);\n            GameModel_OnNpcChanged(mainVM.GameModel.Npc);\n\n            timerAutoScanUpkeep = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(15) };\n            timerAutoScanUpkeep.Tick += TimerAutoScanUpkeep_Tick;\n\n            timerAutoScan = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(0.25) };\n            timerAutoScan.Tick += TimerAutoScan_Tick;\n\n            UpdateAutoScanState();\n        }\n\n        public override void RefreshLocalization()\n        {\n            base.RefreshLocalization();\n\n            UpdateAnalyzerDesc();\n            UpdateCachedText();\n            OnPropertyChanged(\"DescRedPlaced\");\n            OnPropertyChanged(\"DescRedVarPlaced\");\n        }\n\n        private void GameModel_OnNpcChanged(TriadNpc npcOb)\n        {\n            UpdateAnalyzerDesc();\n            UpdateCachedText();\n        }\n\n        private void CommandCapureFunc(object dummyParam)\n        {\n            ScreenAnalyzer.DoWork(ScreenAnalyzer.EMode.Default);\n\n            UpdateScreenState();\n            MainWindow.PageScreenshot.UpdateState();\n        }\n\n        public void OnOverlayActive()\n        {\n            UpdateAnalyzerDesc();\n\n            // multi monitor setup: make sure that overlay and game and on the same monitor\n            var screenBounds = ViewModelServices.OverlayWindow.GetScreenBounds(ScreenAnalyzer != null ? ScreenAnalyzer.screenReader.GetCachedGameWindow() : Rectangle.Empty);\n            var newMarker = new ScreenCoordVM()\n            {\n                DrawMode = ScreenCoordVM.Mode.CapturePanel,\n                ScreenCoords = new System.Drawing.Point(screenBounds.X + (int)screenBounds.Width / 2, screenBounds.Y + screenBounds.Bottom)\n            };\n            MarkerCapturePanel = newMarker;\n\n            // allow one time auto placement on successful scan\n            canMoveCapturePanelOnScan = true;\n        }\n\n        public void UpdateScreenState(bool debugMode = false)\n        {\n            // refresh details (depended on scanner)\n            UseDetails = useDetails;\n\n            if (ScreenAnalyzer.GetCurrentState() != ScreenAnalyzer.EState.NoErrors)\n            {\n                Logger.WriteLine(\"Capture failed: \" + ScreenAnalyzer.GetCurrentState());\n                UpdateAnalyzerDesc();\n                return;\n            }\n\n            ScanId++;\n            Logger.WriteLine(\"Capture scanId:{0}\", ScanId);\n\n            // multi monitor setup: make sure that overlay and game and on the same monitor\n            Rectangle gameWindowRect = ScreenAnalyzer.screenReader.GetCachedGameWindow();\n            ViewModelServices.OverlayWindow.OnProcessingGameWindow(gameWindowRect, out bool invalidatedPositions);\n            if (invalidatedPositions)\n            {\n                canMoveCapturePanelOnScan = true;\n            }\n\n            Rectangle boardBox = Rectangle.Empty;\n            var settingsDB = PlayerSettingsDB.Get();\n\n            var updateFlags = TriadGameScreenMemory.EUpdateFlags.None;\n            if (ScreenAnalyzer.activeScanner is ScannerTriad)\n            {\n                boardBox = ScreenAnalyzer.scannerTriad.GetBoardBox();\n\n                // solver logic\n                updateFlags = ScreenMemory.OnNewScan(ScreenAnalyzer.scannerTriad.cachedGameState, MainWindow.GameModel.Npc);\n                if (updateFlags != TriadGameScreenMemory.EUpdateFlags.None)\n                {\n                    ScreenMemory.gameSolver.FindNextMove(ScreenMemory.gameState, out int solverCardIdx, out int solverBoardPos, out var bestChance);\n\n                    var solverTriadCard = ScreenMemory.deckBlue.GetCard(solverCardIdx);\n                    if ((ScreenMemory.gameState.forcedCardIdx >= 0) && (solverCardIdx != ScreenMemory.gameState.forcedCardIdx))\n                    {\n                        // swap + chaos may cause selecting wrong instance of duplicated card?\n                        // it really, really shouldn't unless solver's agent is broken\n\n                        var forcedCardOb = ScreenMemory.deckBlue.GetCard(ScreenMemory.gameState.forcedCardIdx);\n\n                        var solverCardDesc = solverTriadCard != null ? solverTriadCard.Name.GetCodeName() : \"??\";\n                        var forcedCardDesc = forcedCardOb != null ? forcedCardOb.Name.GetCodeName() : \"??\";\n                        Logger.WriteLine($\"Solver selected card [{solverCardIdx}]:{solverCardDesc}, but game wants: [{ScreenMemory.gameState.forcedCardIdx}]:{forcedCardDesc} !\");\n\n                        solverCardIdx = ScreenMemory.gameState.forcedCardIdx;\n                        solverTriadCard = forcedCardOb;\n                    }\n\n                    int blueCardIdx = solverCardIdx;\n                    int boardCardIdx = (solverCardIdx < 0) ? -1 : solverBoardPos;\n\n                    Logger.WriteLine(\"  suggested move: [{0}] {1} {2} (expected: {3})\",\n                        boardCardIdx, ETriadCardOwner.Blue,\n                        solverTriadCard != null ? solverTriadCard.Name.GetCodeName() : \"??\",\n                        bestChance.expectedResult);\n\n                    if (blueCardIdx >= 0 && boardCardIdx >= 0)\n                    {\n                        try\n                        {\n                            Rectangle rectDeckPos = ScreenAnalyzer.scannerTriad.GetBlueCardBox(blueCardIdx);\n                            var newMarkerDeck = new ScreenCardVM() { DrawMode = ScreenCoordVM.Mode.Default, Duration = settingsDB.markerDurationCard };\n                            AssignGameBoundsToMarker(newMarkerDeck, rectDeckPos);\n\n                            Rectangle rectBoardPos = ScreenAnalyzer.scannerTriad.GetBoardCardBox(boardCardIdx);\n                            var newMarkerBoard = new ScreenCardVM() { DrawMode = ScreenCoordVM.Mode.Default, Duration = settingsDB.markerDurationCard };\n                            AssignGameBoundsToMarker(newMarkerBoard, rectBoardPos);\n\n                            switch (bestChance.expectedResult)\n                            {\n                                case ETriadGameState.BlueWins: newMarkerBoard.Result = ScreenCardVM.SolverResult.Win; break;\n                                case ETriadGameState.BlueDraw: newMarkerBoard.Result = ScreenCardVM.SolverResult.Draw; break;\n                                default: newMarkerBoard.Result = ScreenCardVM.SolverResult.Lose; break;\n                            }\n\n                            MarkerBoard = newMarkerBoard;\n                            MarkerDeck = newMarkerDeck;\n                        }\n                        catch (Exception) { }\n                    }\n                }\n                else\n                {\n                    // refresh markers\n                    var tempBoard = markerBoard;\n                    var tempDeck = markerDeck;\n                    MarkerBoard = null; MarkerBoard = tempBoard;\n                    MarkerDeck = null; MarkerDeck = tempDeck;\n                }\n            }\n            else if (ScreenAnalyzer.activeScanner is ScannerCactpot)\n            {\n                boardBox = ScreenAnalyzer.scannerCactpot.GetBoardBox();\n\n                // solver logic\n                if (ScreenAnalyzer.scannerCactpot.cachedGameState.numRevealed > 3)\n                {\n                    CactpotGame.FindBestLine(ScreenAnalyzer.scannerCactpot.cachedGameState.board, out int fromIdx, out int toIdx);\n                    Logger.WriteLine(\"  suggested line: [{0}] -> [{1}]\", fromIdx, toIdx);\n\n                    if (fromIdx >= 0 && toIdx >= 0)\n                    {\n                        var newMarker = new ScreenCactpotVM() { Duration = settingsDB.markerDurationCactpot };\n                        Rectangle gameFromBox = ScreenAnalyzer.scannerCactpot.GetCircleBox(fromIdx);\n                        Rectangle gameToBox = ScreenAnalyzer.scannerCactpot.GetCircleBox(toIdx);\n\n                        Rectangle gameCombinedBox = Rectangle.Union(gameFromBox, gameToBox);\n                        AssignGameBoundsToMarker(newMarker, gameCombinedBox);\n\n                        newMarker.Line =\n                            (gameFromBox.X == gameToBox.X) ? ScreenCactpotVM.LineType.Vertical :\n                            (gameFromBox.Y == gameToBox.Y) ? ScreenCactpotVM.LineType.Horizontal :\n                            (gameFromBox.X < gameToBox.X) ? ScreenCactpotVM.LineType.DiagSE :\n                            ScreenCactpotVM.LineType.DiagSW;\n                        MarkerCactpotLine = newMarker;\n                    }\n                }\n                else\n                {\n                    int markerPos = CactpotGame.FindNextCircle(ScreenAnalyzer.scannerCactpot.cachedGameState.board);\n                    Logger.WriteLine(\"  suggested move: [{0}]\", markerPos);\n\n                    if (markerPos >= 0)\n                    {\n                        Rectangle gameBoardPos = ScreenAnalyzer.scannerCactpot.GetCircleBox(markerPos);\n\n                        var newMarker = new ScreenCoordVM() { Duration = settingsDB.markerDurationCactpot };\n                        AssignGameBoundsToMarker(newMarker, gameBoardPos);\n                        MarkerCactpotCircle = newMarker;\n                    }\n                }\n\n                Board.SuspendNotifies();\n                for (int Idx = 0; Idx < ScreenAnalyzer.scannerCactpot.cachedGameState.board.Length; Idx++)\n                {\n                    Board[Idx].Card.CardModel = null;\n                    Board[Idx].CactpotNum = ScreenAnalyzer.scannerCactpot.cachedGameState.board[Idx];\n                }\n                Board.ResumeNotifies();\n            }\n\n            // update overlay location if needed\n            if (!boardBox.IsEmpty && canMoveCapturePanelOnScan)\n            {\n                canMoveCapturePanelOnScan = false;\n\n                var newMarker = new ScreenCoordVM() { DrawMode = ScreenCoordVM.Mode.AdjustedCapturePanel };\n                AssignGameBoundsToMarker(newMarker, boardBox, 0);\n                MarkerCapturePanel = newMarker;\n            }\n\n            // update what's needed\n            if ((updateFlags & TriadGameScreenMemory.EUpdateFlags.Modifiers) != TriadGameScreenMemory.EUpdateFlags.None)\n            {\n                UpdateCachedText();\n            }\n\n            if ((updateFlags & TriadGameScreenMemory.EUpdateFlags.BlueDeck) != TriadGameScreenMemory.EUpdateFlags.None)\n            {\n                SyncDeckVM(BlueDeck, ScreenAnalyzer.scannerTriad.cachedGameState.blueDeck);\n            }\n\n            if ((updateFlags & TriadGameScreenMemory.EUpdateFlags.RedDeck) != TriadGameScreenMemory.EUpdateFlags.None)\n            {\n                UpdateRedDeckDetails();\n            }\n\n            if ((updateFlags & TriadGameScreenMemory.EUpdateFlags.Board) != TriadGameScreenMemory.EUpdateFlags.None)\n            {\n                Board.SuspendNotifies();\n                for (int Idx = 0; Idx < ScreenMemory.gameState.board.Length; Idx++)\n                {\n                    Board[Idx].CactpotNum = 0;\n                    Board[Idx].Card.Assign(ScreenMemory.gameState.board[Idx]);\n                    Board[Idx].Card.IsShowingDetails = false;\n                }\n                Board.ResumeNotifies();\n            }\n\n            if ((updateFlags & TriadGameScreenMemory.EUpdateFlags.SwapWarning) != TriadGameScreenMemory.EUpdateFlags.None)\n            {\n                Rectangle ruleRect = ScreenAnalyzer.scannerTriad.GetRuleBox();\n                if (gameWindowRect.Width > 0 && ruleRect.Width > 0)\n                {\n                    var newMarker = new ScreenCoordVM() { DrawMode = ScreenCoordVM.Mode.SwapWarning, Duration = settingsDB.markerDurationSwap };\n                    AssignGamePosToMarker(newMarker, ruleRect.Left, ruleRect.Top);\n                    MarkerSwapWarning = newMarker;\n                }\n            }\n\n            if ((updateFlags & TriadGameScreenMemory.EUpdateFlags.SwapHints) != TriadGameScreenMemory.EUpdateFlags.None)\n            {\n                Rectangle gameDeckPos = ScreenAnalyzer.scannerTriad.GetBlueCardBox(ScreenMemory.swappedBlueCardIdx);\n\n                var newMarker = new ScreenCoordVM() { Duration = settingsDB.markerDurationCard };\n                AssignGameBoundsToMarker(newMarker, gameDeckPos, -10);\n                MarkerSwapCard = newMarker;\n            }\n\n            if (IsUsingAutoScan())\n            {\n                canStopAutoCapture = false;\n                canRunAutoCapture = false;\n                timerAutoScan.IsEnabled = true;\n                TimerAutoScan_Tick(null, null);\n            }\n        }\n\n        private void UpdateCachedText()\n        {\n            string desc = \"\";\n            foreach (TriadGameModifier mod in ScreenMemory.gameSolver.simulation.modifiers)\n            {\n                if (desc.Length > 0) { desc += \", \"; }\n                desc += mod.GetLocalizedName();\n            }\n\n            DescRules = string.Format(loc.strings.OverlayForm_Details_Rules, (desc.Length > 0) ? desc : loc.strings.OverlayForm_Dynamic_RulesWaiting);\n\n            var npcOb = MainWindow.GameModel.Npc;\n            DescNpc = string.Format(loc.strings.OverlayForm_Details_Npc, (npcOb != null) ? npcOb.Name.GetLocalized() : loc.strings.OverlayForm_Dynamic_NpcUnknown);\n        }\n\n        private void UpdateRedDeckDetails()\n        {\n            var modelProxyDB = ModelProxyDB.Get();\n\n            if (ScreenMemory.deckRed == null || ScreenMemory.deckRed.deck == null)\n            {\n                SyncDeckVM(RedDeck, null);\n                SyncDeckVM(RedKnownCards, null);\n                SyncDeckVM(RedUnknownCards, null);\n\n                NumRedPlaced = 0;\n                NumRedVarPlaced = 0;\n            }\n            else\n            {\n                SyncDeckVM(RedDeck, ScreenMemory.deckRed.cards);\n\n                SyncDeckVM(RedKnownCards, ScreenMemory.deckRed.deck.knownCards);\n                int deckIdx = 5;\n                for (int idx = 0; idx < RedKnownCards.Cards.Count; idx++, deckIdx++)\n                {\n                    RedKnownCards.Cards[idx].IsPreview = ScreenMemory.deckRed.IsPlaced(deckIdx);\n\n                    if (ScreenMemory.deckRed.swappedCardIdx == deckIdx)\n                    {\n                        RedKnownCards.Cards[idx].CardModel = modelProxyDB.GetCardProxy(ScreenMemory.deckRed.swappedCard);\n                    }\n                }\n\n                SyncDeckVM(RedUnknownCards, ScreenMemory.deckRed.deck.unknownCardPool);\n                for (int idx = 0; idx < RedUnknownCards.Cards.Count; idx++, deckIdx++)\n                {\n                    RedUnknownCards.Cards[idx].IsPreview = ScreenMemory.deckRed.IsPlaced(deckIdx);\n\n                    if (ScreenMemory.deckRed.swappedCardIdx == deckIdx)\n                    {\n                        RedUnknownCards.Cards[idx].CardModel = modelProxyDB.GetCardProxy(ScreenMemory.deckRed.swappedCard);\n                    }\n                }\n\n                NumRedPlaced = ScreenMemory.deckRed.numPlaced;\n                NumRedVarPlaced = ScreenMemory.deckRed.numUnknownPlaced;\n            }\n        }\n\n        private void TimerAutoScan_Tick(object sender, EventArgs e)\n        {\n            bool bDebugMode = false;\n\n            bool useVerboseLogs = Logger.IsSuperVerbose();\n            bool scannedTimer = false;\n            bool scannedFull = false;\n            if (useVerboseLogs)\n            {\n                perfTimer.Restart();\n            }\n\n            UpdateAutoScanState();\n\n            bool attemptScan = (ScreenAnalyzer != null) && (ScreenAnalyzer.activeScanner == null || ScreenAnalyzer.activeScanner is ScannerTriad);\n            if (attemptScan)\n            {\n                if (timerAutoScanUpkeep.IsEnabled)\n                {\n                    // always retry in upkeep mode\n                    if (useVerboseLogs) { Logger.WriteLine(\"Auto scan: upkeep, force scan!\"); }\n                }\n                else if (ScreenAnalyzer.scannerTriad.cachedGameState == null ||\n                    canStopAutoCapture && ScreenAnalyzer.scannerTriad.cachedGameState.turnState == ScannerTriad.ETurnState.MissingTimer)\n                {\n                    attemptScan = false;\n                    canRunAutoCapture = false;\n                    if (useVerboseLogs) { Logger.WriteLine(\"Auto scan: missing timer, no scan, no capture\"); }\n\n                    var isOverlayActive = MainWindow.PageScreenshot.IsOverlayActive;\n                    var wantsAutoScan = IsUsingAutoScan();\n                    if (isOverlayActive && wantsAutoScan)\n                    {\n                        Logger.WriteLine(\"Auto scan: entering upkeep mode\");\n                        timerAutoScanUpkeep.Start();\n                    }\n                    else\n                    {\n                        timerAutoScan.Stop();\n                    }\n                }\n            }\n\n            if (attemptScan)\n            {\n                Rectangle timerGameBox = ScreenAnalyzer.scannerTriad.GetTimerScanBox();\n                ScreenAnalyzer.scanClipBounds = timerGameBox;\n\n                ScreenAnalyzer.DoWork(ScreenAnalyzer.EMode.ScanTriad | ScreenAnalyzer.EMode.NeverResetCache, (int)ScannerTriad.EScanMode.TimerOnly);\n\n                ScreenAnalyzer.scanClipBounds = Rectangle.Empty;\n                canStopAutoCapture = true;\n                scannedTimer = true;\n            }\n\n            UpdateAnalyzerDesc();\n\n            if (ScreenAnalyzer != null && ScreenAnalyzer.scannerTriad.cachedGameState != null && IsUsingAutoScan())\n            {\n                ScannerTriad.ETurnState turnState = ScreenAnalyzer.scannerTriad.cachedGameState.turnState;\n                if (turnState != ScannerTriad.ETurnState.MissingTimer && timerAutoScanUpkeep.IsEnabled)\n                {\n                    Logger.WriteLine(\"Auto scan: aborting upkeep mode (scanned)\");\n                    timerAutoScanUpkeep.Stop();\n                }\n\n                if (turnState == ScannerTriad.ETurnState.Waiting)\n                {\n                    canRunAutoCapture = true;\n                    if (useVerboseLogs) { Logger.WriteLine(\"Auto scan: in Waiting state, can capture\"); }\n                }\n                else if (turnState == ScannerTriad.ETurnState.Active)\n                {\n                    if (canRunAutoCapture)\n                    {\n                        bool bIsMouseOverGrid = IsCursorInScanArea();\n                        if (bDebugMode) { Logger.WriteLine(\"Checking auto scan: mouse:{0}, state:{1}\", bIsMouseOverGrid ? \"OverGrid\" : \"ok\", ScreenAnalyzer.GetCurrentState()); }\n\n                        if (!bIsMouseOverGrid && ScreenAnalyzer.GetCurrentState() == ScreenAnalyzer.EState.NoErrors)\n                        {\n                            if (useVerboseLogs) { Logger.WriteLine(\"Auto scan: in Active state, no capture\"); }\n                            canRunAutoCapture = false;\n                            scannedFull = true;\n                            CommandCapture.Execute(null);\n                        }\n                    }\n                }\n            }\n\n            if (useVerboseLogs)\n            {\n                perfTimer.Stop();\n\n                if (scannedFull || scannedTimer)\n                {\n                    Logger.WriteLine(\"Auto scan stats: time:{0}ms, scannedTimer:{1}, scannedFull:{2}\", perfTimer.ElapsedMilliseconds, scannedTimer, scannedFull);\n                }\n            }\n        }\n\n        private void TimerAutoScanUpkeep_Tick(object sender, EventArgs e)\n        {\n            Logger.WriteLine(\"Auto scan: upkeep mode timed out\");\n            DisableAutoScanTimers();\n        }\n\n        private void DisableAutoScanTimers()\n        {\n            Logger.WriteLine(\"Auto scan: disabled\");\n            canRunAutoCapture = false;\n            timerAutoScanUpkeep.Stop();\n            timerAutoScan.Stop();\n            UpdateAutoScanState();\n        }\n\n        private void SyncDeckVM(CardCollectionViewModel deckVM, IList<TriadCard> cards)\n        {\n            if (cards == null)\n            {\n                deckVM.Cards.Clear();\n                return;\n            }\n\n            while (deckVM.Cards.Count > cards.Count)\n            {\n                deckVM.Cards.RemoveAt(deckVM.Cards.Count - 1);\n            }\n\n            while (deckVM.Cards.Count < cards.Count)\n            {\n                deckVM.Cards.Add(new CardViewModel());\n            }\n\n            var modelProxyDB = ModelProxyDB.Get();\n            int hiddenCardId = TriadCardDB.Get().hiddenCard.Id;\n\n            for (int idx = 0; idx < cards.Count; idx++)\n            {\n                var cardVM = deckVM.Cards[idx];\n                cardVM.CardModel = modelProxyDB.GetCardProxy(cards[idx]);\n                cardVM.IsHidden = cards[idx]?.Id == hiddenCardId;\n            }\n        }\n\n        private void AssignGameBoundsToMarker(ScreenCoordVM markerVM, Rectangle gameBounds, int inflateSize = 10)\n        {\n            Rectangle screenBounds = ScreenAnalyzer.ConvertGameToScreen(gameBounds);\n            if (screenBounds.Width == 0)\n            {\n                // screen reader not initialized / not tied to game window\n                screenBounds = new Rectangle(gameBounds.Location, gameBounds.Size);\n            }\n\n            screenBounds.Inflate(inflateSize, inflateSize);\n\n            markerVM.ScreenCoords = screenBounds.Location;\n            markerVM.ScreenSize = screenBounds.Size;\n        }\n\n        private void AssignGamePosToMarker(ScreenCoordVM markerVM, int gamePosX, int gamePosY)\n        {\n            Rectangle screenBounds = ScreenAnalyzer.ConvertGameToScreen(new Rectangle(gamePosX, gamePosY, 100, 100));\n            if (screenBounds.Width == 0)\n            {\n                // screen reader not initialized / not tied to game window\n                screenBounds = new Rectangle(gamePosX, gamePosY, 0, 0);\n            }\n\n            markerVM.ScreenCoords = screenBounds.Location;\n            markerVM.ScreenSize = System.Drawing.Size.Empty;\n        }\n\n        private bool IsCursorInScanArea()\n        {\n            Rectangle screenScanArea = ScreenAnalyzer.ConvertGameToScreen(ScreenAnalyzer.currentScanArea);\n            return ViewModelServices.OverlayWindow.IsCursorInside(screenScanArea);\n        }\n\n        private bool IsUsingAutoScan()\n        {\n            bool bIsAutoScanAllowed = (ScreenAnalyzer == null) || (ScreenAnalyzer.activeScanner is ScannerTriad);\n            return useAutoScan && bIsAutoScanAllowed;\n        }\n\n        private void UpdateAutoScanState()\n        {\n            bool newIsActive = IsUsingAutoScan() && (ScreenAnalyzer != null);\n            if (newIsActive)\n            {\n                if (timerAutoScanUpkeep.IsEnabled)\n                {\n                    // always active during upkeep\n                }\n                else if (ScreenAnalyzer.scannerTriad.cachedGameState != null)\n                {\n                    newIsActive = ScreenAnalyzer.scannerTriad.cachedGameState.turnState != ScannerTriad.ETurnState.MissingTimer;\n                }\n            }\n\n            IsAutoScanActive = newIsActive;\n        }\n\n        private BitmapSource FindOrAddIcon(Icon icon)\n        {\n            if (!cachedIcons.ContainsKey(icon))\n            {\n                var bitmap = Imaging.CreateBitmapSourceFromHIcon(icon.Handle, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());\n                cachedIcons.Add(icon, bitmap);\n            }\n\n            return cachedIcons[icon];\n        }\n\n        private void UpdateAnalyzerDesc()\n        {\n            if (ScreenAnalyzer == null)\n            {\n                return;\n            }\n\n            var showState = ScreenAnalyzer.GetCurrentState();\n            switch (showState)\n            {\n                case ScreenAnalyzer.EState.NoInputImage:\n                    AnalyzerStateIcon = FindOrAddIcon(SystemIcons.Error);\n                    switch (ScreenAnalyzer.screenReader.currentState)\n                    {\n                        case ScreenReader.EState.MissingGameProcess: DescAnalyzerState = loc.strings.OverlayForm_Dynamic_Status_MissingGameProcess; break;\n                        case ScreenReader.EState.MissingGameWindow: DescAnalyzerState = loc.strings.OverlayForm_Dynamic_Status_MissingGameWindow; break;\n                        default: DescAnalyzerState = loc.strings.OverlayForm_Dynamic_Status_NoInputImage; break;\n                    }\n                    break;\n\n                case ScreenAnalyzer.EState.NoScannerMatch:\n                    AnalyzerStateIcon = FindOrAddIcon(SystemIcons.Error);\n                    DescAnalyzerState = loc.strings.OverlayForm_Dynamic_Status_NoScannerMatch;\n                    break;\n\n                case ScreenAnalyzer.EState.UnknownHash:\n                    AnalyzerStateIcon = FindOrAddIcon(SystemIcons.Warning);\n                    DescAnalyzerState = loc.strings.OverlayForm_Dynamic_Status_UnknownHash;\n                    break;\n\n                case ScreenAnalyzer.EState.ScannerErrors:\n                    AnalyzerStateIcon = FindOrAddIcon(SystemIcons.Error);\n                    DescAnalyzerState = loc.strings.OverlayForm_Dynamic_Status_ScannerErrors;\n\n                    if (ScreenAnalyzer.activeScanner is ScannerTriad)\n                    {\n                        switch (ScreenAnalyzer.scannerTriad.cachedScanError)\n                        {\n                            case ScannerTriad.EScanError.MissingGrid: DescAnalyzerState = loc.strings.OverlayForm_Dynamic_Status_MissingGrid; break;\n                            case ScannerTriad.EScanError.MissingCards: DescAnalyzerState = loc.strings.OverlayForm_Dynamic_Status_MissingCards; break;\n                            case ScannerTriad.EScanError.FailedCardMatching:\n                                DescAnalyzerState = loc.strings.OverlayForm_Dynamic_Status_FailedCardMatching;\n                                AnalyzerStateIcon = FindOrAddIcon(SystemIcons.Warning);\n                                break;\n\n                            default: break;\n                        }\n                    }\n                    break;\n\n                default:\n                    {\n                        string npcDesc = (MainWindow.GameModel.Npc != null) ? (MainWindow.GameModel.Npc.Name.GetLocalized() + \": \") : \"\";\n                        AnalyzerStateIcon = FindOrAddIcon(SystemIcons.Information);\n\n                        if (ScreenAnalyzer.activeScanner == null || ScreenAnalyzer.activeScanner.cachedGameStateBase == null)\n                        {\n                            DescAnalyzerState = npcDesc + loc.strings.OverlayForm_Dynamic_Status_Ready;\n                        }\n                        else if (ScreenAnalyzer.activeScanner is ScannerTriad)\n                        {\n                            switch (ScreenAnalyzer.scannerTriad.cachedGameState.turnState)\n                            {\n                                case ScannerTriad.ETurnState.MissingTimer:\n                                    DescAnalyzerState = npcDesc + loc.strings.OverlayForm_Dynamic_Status_Ready;\n                                    break;\n\n                                case ScannerTriad.ETurnState.Waiting:\n                                    DescAnalyzerState = npcDesc + loc.strings.OverlayForm_Dynamic_Status_WaitingForTurn;\n                                    AnalyzerStateIcon = FindOrAddIcon(SystemIcons.Shield);\n                                    break;\n\n                                default:\n                                    if (IsUsingAutoScan() && IsCursorInScanArea())\n                                    {\n                                        DescAnalyzerState = loc.strings.OverlayForm_Dynamic_Status_AutoScanMouseOverBoard;\n                                        AnalyzerStateIcon = FindOrAddIcon(SystemIcons.Warning);\n                                    }\n                                    else\n                                    {\n                                        DescAnalyzerState = npcDesc + loc.strings.OverlayForm_Dynamic_Status_ActiveTurn;\n                                    }\n                                    break;\n                            }\n                        }\n                        else if (ScreenAnalyzer.activeScanner is ScannerCactpot)\n                        {\n                            DescAnalyzerState = loc.strings.OverlayForm_Dynamic_Status_CactpotReady;\n                        }\n                        else\n                        {\n                            DescAnalyzerState = npcDesc + loc.strings.OverlayForm_Dynamic_Status_Ready;\n                        }\n                    }\n                    break;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/viewmodel/PageCardsViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Windows.Data;\nusing System.Windows.Input;\n\nnamespace FFTriadBuddy.UI\n{\n    public class PageCardsViewModel : LocalizedViewModel\n    {\n        public MainWindowViewModel MainWindow;\n\n        public List<CardCollectionViewModel> GridViews { get; } = new List<CardCollectionViewModel>();\n        public List<CardModelProxy> Cards => ModelProxyDB.Get().Cards;\n        public int NumOwnedCards => ModelProxyDB.Get().OwnedCards.Count;\n\n        private CardModelProxy foundItem = null;\n        public CardModelProxy FoundItem { get => foundItem; set => PropertySetAndNotify(value, ref foundItem); }\n\n        public BulkObservableCollection<ContextActionViewModel> ContextActions { get; } = new BulkObservableCollection<ContextActionViewModel>();\n\n        public ICommand CommandToggleOwnedCard { get; private set; }\n        public ICommand CommandSearchCard { get; private set; }\n        public ICommand CommandBuildContextActions { get; private set; }\n\n        public string MainForm_Cards_IconsTitle => loc.strings.MainForm_Cards_IconsTitle;\n        public string MainForm_Cards_ListTitle => loc.strings.MainForm_Cards_ListTitle;\n        public string MainForm_Cards_List_ColumnId => loc.strings.MainForm_Cards_List_ColumnId;\n        public string MainForm_Cards_List_ColumnName => loc.strings.MainForm_Cards_List_ColumnName;\n        public string MainForm_Cards_List_ColumnOwned => loc.strings.MainForm_Cards_List_ColumnOwned;\n        public string MainForm_Cards_List_ColumnPower => loc.strings.MainForm_Cards_List_ColumnPower;\n        public string MainForm_Cards_List_ColumnRarity => loc.strings.MainForm_Cards_List_ColumnRarity;\n        public string MainForm_Cards_List_ColumnType => loc.strings.MainForm_Cards_List_ColumnType;\n        public string MainForm_Cards_NumOwned => loc.strings.MainForm_Cards_NumOwned;\n        public string MainForm_CtxMenu_FindCard => loc.strings.MainForm_CtxMenu_FindCard;\n\n        public PageCardsViewModel()\n        {\n            // design time only\n        }\n\n        public PageCardsViewModel(MainWindowViewModel mainVM)\n        {\n            MainWindow = mainVM;\n            CreateGridViews();\n\n            ModelProxyDB.Get().OwnedCards.CollectionChanged += (s, e) => OnPropertyChanged(\"NumOwnedCards\");\n            PlayerSettingsDB.Get().OnUpdated += OnSettingsUpdated;\n\n            CommandToggleOwnedCard = new RelayCommand<CardModelProxy>(CommandToggleOwnedCardFunc);\n            CommandSearchCard = new RelayCommand<string>(CommandSearchCardFunc);\n            CommandBuildContextActions = new RelayCommand<CardModelProxy>(CommandBuildContextActionsFunc);\n        }\n\n        public override void RefreshLocalization()\n        {\n            base.RefreshLocalization();\n            ContextActions.Clear();\n        }\n\n        private void CreateGridViews()\n        {\n            ModelProxyDB modelProxyDB = ModelProxyDB.Get();\n            int gridSize = 30;\n\n            var sortedList = new List<CardModelProxy>();\n            sortedList.AddRange(modelProxyDB.Cards);\n            sortedList.Sort((a, b) => a.GameSortOrder.CompareTo(b.GameSortOrder));\n\n            CardCollectionViewModel currentGridVM = null;\n            int currentSortGroup = -1;\n            for (int idx = 0; idx < sortedList.Count; idx++)\n            {\n                var cardProxy = sortedList[idx];\n                bool createNewGrid =\n                    (currentGridVM == null) ||\n                    (currentSortGroup != cardProxy.GameSortGroup) ||\n                    (currentGridVM.Cards.Count >= gridSize);\n\n                if (createNewGrid)\n                {\n                    currentGridVM = new CardCollectionViewModel() { Name = string.Format(\"-- {0} --\", GridViews.Count + 1) };\n                    GridViews.Add(currentGridVM);\n                }\n\n                currentGridVM.Cards.Add(new CardViewModel() { CardModel = cardProxy });\n                currentSortGroup = cardProxy.GameSortGroup;\n            }\n        }\n\n        private void OnSettingsUpdated(bool bCards, bool bNpcs, bool bDecks)\n        {\n            if (bCards)\n            {\n                GridViews.Clear();\n                CreateGridViews();\n\n                CollectionViewSource.GetDefaultView(GridViews).Refresh();\n            }\n        }\n\n        private void CommandToggleOwnedCardFunc(CardModelProxy cardProxy)\n        {\n            cardProxy.IsOwned = !cardProxy.IsOwned;\n        }\n\n        private void CommandSearchCardFunc(string text)\n        {\n            FoundItem = Cards.Find(x => x.NameLocalized.StartsWith(text, StringComparison.OrdinalIgnoreCase));\n        }\n\n        private void CommandBuildContextActionsFunc(CardModelProxy cardProxy)\n        {\n            ContextActions.SuspendNotifies();\n\n            const int numDefaultItems = 3;\n            if (ContextActions.Count == 0)\n            {\n                ContextActions.Add(new ContextActionViewModel() { Name = loc.strings.MainForm_CtxMenu_CardInfo_FindOnline, Command = new RelayCommand<object>(x => FindCardOnline(x as CardModelProxy)) });\n                ContextActions.Add(new ContextActionViewModel() { IsSeparator = true });\n                ContextActions.Add(new ContextActionViewModel() { Name = loc.strings.MainForm_CtxMenu_CardInfo_NpcReward, Command = new RelayCommand<object>(x => { }, x => false) });\n            }\n            else if (ContextActions.Count > numDefaultItems)\n            {\n                while (ContextActions.Count > numDefaultItems)\n                {\n                    ContextActions.RemoveAt(numDefaultItems);\n                }\n            }\n\n            if (cardProxy != null)\n            {\n                List<NpcModelProxy> matchingNpcs = ModelProxyDB.Get().Npcs.FindAll(x => x.npcOb.Rewards.Contains(cardProxy.cardOb));\n                if (matchingNpcs.Count > 0)\n                {\n                    foreach (var npc in matchingNpcs)\n                    {\n                        ContextActions.Add(new ContextActionViewModel() { Name = npc.NameLocalized, Command = new RelayCommand<object>(x => SelectCardNpc(npc)) });\n                    }\n                }\n            }\n\n            if (ContextActions.Count == numDefaultItems)\n            {\n                ContextActions.Add(new ContextActionViewModel() { Name = loc.strings.MainForm_Dynamic_RuleListEmpty, Command = new RelayCommand<object>(x => { }, x => false) });\n            }\n\n            ContextActions.ResumeNotifies();\n        }\n\n        private void FindCardOnline(CardModelProxy cardProxy)\n        {\n            Process.Start(new ProcessStartInfo(\"https://ffxivcollect.com/triad/cards/\" + cardProxy.cardOb.Id) { UseShellExecute = true });\n        }\n\n        private void SelectCardNpc(NpcModelProxy npcProxy)\n        {\n            MainWindow.SwitchToPage(MainWindowViewModel.PageType.Npcs);\n            MainWindow.PageNpcs.SelectNpc(npcProxy);\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/viewmodel/PageInfoViewModel.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.Windows;\n\nnamespace FFTriadBuddy.UI\n{\n    public class SettingsEventArgs : EventArgs\n    {\n        public enum Setting\n        {\n            UseSmallIcons,\n        }\n\n        public Setting Type;\n        public bool BoolValue;\n        public float FloatValue;\n    }\n\n    public class PageInfoViewModel : LocalizedViewModel, IDataErrorInfo\n    {\n        public MainWindowViewModel MainWindow;\n        public LocalSavesViewModel LocalSaves { get; } = new LocalSavesViewModel();\n\n        private float valueFontSize = 0;\n        public float ValueFontSize\n        {\n            get => valueFontSize;\n            set\n            {\n                PropertySetAndNotify(value, ref valueFontSize);\n                if (IsPropertyValueValid(value, GetValidFontSizeRange))\n                {\n                    PlayerSettingsDB.Get().fontSize = valueFontSize;\n                    ViewModelServices.AppWindow.SetFontSize(valueFontSize);\n                }\n            }\n        }\n\n        private float valueMarkerCard = 0;\n        public float ValueMarkerCard\n        {\n            get => valueMarkerCard;\n            set\n            {\n                PropertySetAndNotify(value, ref valueMarkerCard);\n                if (IsPropertyValueValid(value, GetValidMarkerDurationRange))\n                {\n                    PlayerSettingsDB.Get().markerDurationCard = valueMarkerCard;\n                }\n            }\n        }\n\n        private float valueMarkerSwap = 0;\n        public float ValueMarkerSwap\n        {\n            get => valueMarkerSwap;\n            set\n            {\n                PropertySetAndNotify(value, ref valueMarkerSwap);\n                if (IsPropertyValueValid(value, GetValidMarkerDurationRange))\n                {\n                    PlayerSettingsDB.Get().markerDurationSwap = valueMarkerSwap;\n                }\n            }\n        }\n\n        private float valueMarkerCactpot = 0;\n        public float ValueMarkerCactpot\n        {\n            get => valueMarkerCactpot;\n            set\n            {\n                PropertySetAndNotify(value, ref valueMarkerCactpot);\n                if (IsPropertyValueValid(value, GetValidMarkerDurationRange))\n                {\n                    PlayerSettingsDB.Get().markerDurationCactpot = valueMarkerCactpot;\n                }\n            }\n        }\n\n        private bool valueAlwaysOnTop = false;\n        public bool ValueAlwaysOnTop\n        {\n            get => valueAlwaysOnTop;\n            set\n            {\n                PropertySetAndNotify(value, ref valueAlwaysOnTop);\n                PlayerSettingsDB.Get().alwaysOnTop = valueAlwaysOnTop;\n                ViewModelServices.AppWindow.SetAlwaysOnTop(valueAlwaysOnTop);\n            }\n        }\n\n        private bool valueSkipOptionalRules = false;\n        public bool ValueSkipOptionalRules\n        {\n            get => valueSkipOptionalRules;\n            set\n            {\n                PropertySetAndNotify(value, ref valueSkipOptionalRules);\n                PlayerSettingsDB.Get().skipOptionalSimulateRules = valueSkipOptionalRules;\n            }\n        }\n\n        private bool valueUseSmallIcons = false;\n        public bool ValueUseSmallIcons\n        {\n            get => valueUseSmallIcons;\n            set\n            {\n                PropertySetAndNotify(value, ref valueUseSmallIcons);\n                PlayerSettingsDB.Get().useSmallIcons = valueUseSmallIcons;\n\n                OnSettingsChanged?.Invoke(this, new SettingsEventArgs() { Type = SettingsEventArgs.Setting.UseSmallIcons, BoolValue = value });\n            }\n        }\n\n        private bool valueDisableHardwareAcceleration = false;\n        public bool ValueDisableHardwareAcceleration\n        {\n            get => valueDisableHardwareAcceleration;\n            set\n            {\n                PropertySetAndNotify(value, ref valueDisableHardwareAcceleration);\n                PlayerSettingsDB.Get().useSoftwareRendering = valueDisableHardwareAcceleration;\n\n                ViewModelServices.AppWindow.SetSoftwareRendering(valueDisableHardwareAcceleration);\n            }\n        }\n\n        public string MainForm_Info_HomePage => loc.strings.MainForm_Info_HomePage;\n        public string MainForm_Info_BugReports => loc.strings.MainForm_Info_BugReports;\n        public string MainForm_Info_Localization => loc.strings.MainForm_Info_Localization;\n        public string MainForm_Info_TranslatorLove => loc.strings.MainForm_Info_TranslatorLove;\n        public string MainForm_Info_TranslatorNeeded => loc.strings.MainForm_Info_TranslatorNeeded;\n\n        public string Settings_AlwaysOnTop => loc.strings.Settings_AlwaysOnTop;\n        public string Settings_Title => loc.strings.Settings_Title;\n        public string Settings_FontSize => loc.strings.Settings_FontSize;\n        public string Settings_MarkerDurationCard => loc.strings.Settings_MarkerDurationCard;\n        public string Settings_MarkerDurationSwap => loc.strings.Settings_MarkerDurationSwap;\n        public string Settings_MarkerDurationCactpot => loc.strings.Settings_MarkerDurationCactpot;\n        public string Settings_SkipOptionalSimulateRules => loc.strings.Settings_SkipOptionalSimulateRules;\n        public string Settings_AlwaysSmallIcons => loc.strings.Settings_AlwaysSmallIcons;\n        public string Settings_DisableHardwareAcceleration => loc.strings.Settings_DisableHardwareAcceleration;\n\n        public string Error => null;\n        public string this[string columnName]\n        {\n            get\n            {\n                if (columnName == \"ValueFontSize\")\n                {\n                    var (minV, maxV) = GetValidFontSizeRange();\n                    if (valueFontSize < minV || valueFontSize > maxV)\n                    {\n                        return string.Format(\"[{0} .. {1}]\", minV, maxV);\n                    }\n                }\n                else if (columnName == \"ValueMarkerCard\" || columnName == \"ValueMarkerSwap\" || columnName == \"ValueMarkerCactpot\")\n                {\n                    float testV =\n                        (columnName == \"ValueMarkerCard\") ? valueMarkerCard :\n                        (columnName == \"ValueMarkerSwap\") ? valueMarkerSwap :\n                        valueMarkerCactpot;\n\n                    var (minV, maxV) = GetValidMarkerDurationRange();\n                    if (testV < minV || testV > maxV)\n                    {\n                        return string.Format(\"[{0} .. {1}]\", minV, maxV);\n                    }\n                }\n\n                return null;\n            }\n        }\n\n        public event EventHandler<SettingsEventArgs> OnSettingsChanged;\n        public static PageInfoViewModel lastInstance;\n\n        public PageInfoViewModel()\n        {\n            var settingsDB = PlayerSettingsDB.Get();\n            lastInstance = this;\n\n            ValueAlwaysOnTop = settingsDB.alwaysOnTop;\n            ValueSkipOptionalRules = settingsDB.skipOptionalSimulateRules;\n\n            // avoid setters here\n            valueFontSize = settingsDB.fontSize;\n            valueMarkerCard = settingsDB.markerDurationCard;\n            valueMarkerSwap = settingsDB.markerDurationSwap;\n            valueMarkerCactpot = settingsDB.markerDurationCactpot;\n            valueUseSmallIcons = settingsDB.useSmallIcons;\n            valueDisableHardwareAcceleration = settingsDB.useSoftwareRendering;\n        }\n\n        public override void RefreshLocalization()\n        {\n            base.RefreshLocalization();\n            LocalSaves.RefreshLocalization();\n        }\n\n        private (float, float) GetValidFontSizeRange()\n        {\n            return (10.0f, 40.0f);\n        }\n\n        private (float, float) GetValidMarkerDurationRange()\n        {\n            return (0.5f, 10.0f);\n        }\n\n        private bool IsPropertyValueValid(float value, Func<(float, float)> funcRange)\n        {\n            var (minV, maxV) = funcRange();\n            return (value >= minV) && (value <= maxV);\n        }\n    }\n\n    public class SettingsWeakEventManager : WeakEventManager\n    {\n        private SettingsWeakEventManager() { }\n\n        public static void AddHandler(PageInfoViewModel sourceVM, EventHandler<SettingsEventArgs> handler)\n        {\n            if (sourceVM != null && handler != null)\n            {\n                CurrentManager.ProtectedAddHandler(sourceVM, handler);\n            }\n        }\n\n        public static void RemoveHandler(PageInfoViewModel sourceVM, EventHandler<SettingsEventArgs> handler)\n        {\n            if (sourceVM != null && handler != null)\n            {\n                CurrentManager.ProtectedRemoveHandler(sourceVM, handler);\n            }\n        }\n\n        private static SettingsWeakEventManager CurrentManager\n        {\n            get\n            {\n                Type managerType = typeof(SettingsWeakEventManager);\n                SettingsWeakEventManager manager =\n                    (SettingsWeakEventManager)GetCurrentManager(managerType);\n\n                // at first use, create and register a new manager\n                if (manager == null)\n                {\n                    manager = new SettingsWeakEventManager();\n                    SetCurrentManager(managerType, manager);\n                }\n\n                return manager;\n            }\n        }\n\n        protected override ListenerList NewListenerList()\n        {\n            return new ListenerList<SettingsEventArgs>();\n        }\n\n        protected override void StartListening(object source)\n        {\n            PageInfoViewModel typedSource = (PageInfoViewModel)source;\n            typedSource.OnSettingsChanged += new EventHandler<SettingsEventArgs>(OnSettingsChanged);\n        }\n\n        protected override void StopListening(object source)\n        {\n            PageInfoViewModel typedSource = (PageInfoViewModel)source;\n            typedSource.OnSettingsChanged -= new EventHandler<SettingsEventArgs>(OnSettingsChanged);\n        }\n\n        void OnSettingsChanged(object sender, SettingsEventArgs e)\n        {\n            DeliverEvent(sender, e);\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/viewmodel/PageNpcsViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Linq;\nusing System.Windows.Data;\nusing System.Windows.Input;\n\nnamespace FFTriadBuddy.UI\n{\n    public class PageNpcsViewModel : LocalizedViewModel\n    {\n        public MainWindowViewModel MainWindow;\n\n        private CollectionViewSource unfilteredNpc = new CollectionViewSource();\n        public ICollectionView Npcs => unfilteredNpc.View;\n\n        private int remainingNpcs = 0;\n        public int RemainingNpcWithReward { get => remainingNpcs; set => PropertySetAndNotify(value, ref remainingNpcs); }\n\n        private NpcModelProxy foundItem = null;\n        public NpcModelProxy FoundItem { get => foundItem; set => PropertySetAndNotify(value, ref foundItem); }\n\n        public BulkObservableCollection<ContextActionViewModel> ContextActions { get; } = new BulkObservableCollection<ContextActionViewModel>();\n\n        public ICommand CommandSearchNpc { get; private set; }\n        public ICommand CommandBuildContextActions { get; private set; }\n\n        public string MainForm_Npcs_List_ColumnCompleted => loc.strings.MainForm_Npcs_List_ColumnCompleted;\n        public string MainForm_Npcs_List_ColumnLocation => loc.strings.MainForm_Npcs_List_ColumnLocation;\n        public string MainForm_Npcs_List_ColumnName => loc.strings.MainForm_Npcs_List_ColumnName;\n        public string MainForm_Npcs_List_ColumnPower => loc.strings.MainForm_Npcs_List_ColumnPower;\n        public string MainForm_Npcs_List_ColumnReward => loc.strings.MainForm_Npcs_List_ColumnReward;\n        public string MainForm_Npcs_List_ColumnRules => loc.strings.MainForm_Npcs_List_ColumnRules;\n        public string MainForm_Npcs_NumKnown => loc.strings.MainForm_Npcs_NumKnown;\n        public string MainForm_CtxMenu_FindNpc => loc.strings.MainForm_CtxMenu_FindNpc;\n\n        public PageNpcsViewModel()\n        {\n            // design time only\n        }\n\n        public PageNpcsViewModel(MainWindowViewModel mainVM)\n        {\n            MainWindow = mainVM;\n\n            var modelProxyDB = ModelProxyDB.Get();\n\n            unfilteredNpc.Source = modelProxyDB.Npcs;\n            modelProxyDB.OwnedCards.CollectionChanged += (s, e) => UpdateRemainingNpcs();\n            UpdateRemainingNpcs();\n\n            CommandSearchNpc = new RelayCommand<string>(CommandSearchNpcFunc);\n            CommandBuildContextActions = new RelayCommand<NpcModelProxy>(CommandBuildContextActionsFunc);\n        }\n\n        public override void RefreshLocalization()\n        {\n            base.RefreshLocalization();\n            ContextActions.Clear();\n        }\n\n        public void SelectNpc(NpcModelProxy npcProxy)\n        {\n            FoundItem = npcProxy;\n        }\n\n        private void UpdateRemainingNpcs()\n        {\n            RemainingNpcWithReward = ModelProxyDB.Get().Npcs.Count(x => !x.IsCompleted);\n        }\n\n        private void CommandSearchNpcFunc(string text)\n        {\n            FoundItem = ModelProxyDB.Get().Npcs.Find(x => x.NameLocalized.StartsWith(text, StringComparison.OrdinalIgnoreCase));\n        }\n\n        private void CommandBuildContextActionsFunc(NpcModelProxy npcProxy)\n        {\n            ContextActions.SuspendNotifies();\n\n            const int numDefaultItems = 3;\n            if (ContextActions.Count == 0)\n            {\n                ContextActions.Add(new ContextActionViewModel() { Name = loc.strings.MainForm_CtxMenu_SelectNpc_Select, Command = new RelayCommand<object>(x => SelectNpcToPlay(x as NpcModelProxy)) });\n                ContextActions.Add(new ContextActionViewModel() { IsSeparator = true });\n                ContextActions.Add(new ContextActionViewModel() { Name = loc.strings.MainForm_CtxMenu_SelectNpc_Rewards, Command = new RelayCommand<object>(x => { }, x => false) });\n            }\n            else if (ContextActions.Count > numDefaultItems)\n            {\n                while (ContextActions.Count > numDefaultItems)\n                {\n                    ContextActions.RemoveAt(numDefaultItems);\n                }\n            }\n\n            if (npcProxy != null)\n            {\n                var modelDB = ModelProxyDB.Get();\n                List<CardModelProxy> matchingCards = modelDB.Cards.FindAll(x => npcProxy.npcOb.Rewards.Contains(x.cardOb));\n                if (matchingCards.Count > 0)\n                {\n                    foreach (var card in matchingCards)\n                    {\n                        ContextActions.Add(new ContextActionViewModel()\n                        {\n                            Name = card.NameLocalized,\n                            IsCheckbox = true,\n                            IsChecked = card.IsOwned,\n                            Command = new RelayCommand<object>(x => card.IsOwned = !card.IsOwned)\n                        });\n                    }\n                }\n            }\n\n            if (ContextActions.Count == numDefaultItems)\n            {\n                ContextActions.Add(new ContextActionViewModel() { Name = loc.strings.MainForm_Dynamic_RuleListEmpty, Command = new RelayCommand<object>(x => { }, x => false) });\n            }\n\n            ContextActions.ResumeNotifies();\n        }\n\n        private void SelectNpcToPlay(NpcModelProxy npcProxy)\n        {\n            MainWindow.GameModel.SetNpc(npcProxy?.npcOb);\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/viewmodel/PageScreenshotViewModel.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System.Drawing;\nusing System.Windows.Input;\n\nnamespace FFTriadBuddy.UI\n{\n    public class PageScreenshotViewModel : LocalizedViewModel\n    {\n        public enum Mode\n        {\n            Info,\n            Hashes,\n            Learn,\n        }\n\n        public enum AnalyzerState\n        {\n            Disabled,\n            Warning,\n            Error,\n            Active,\n        }\n\n        public MainWindowViewModel MainWindow;\n        public ScreenAnalyzer ScreenAnalyzer = new ScreenAnalyzer();\n\n        public BulkObservableCollection<ImageHashDataModelProxy> Hashes { get; } = new BulkObservableCollection<ImageHashDataModelProxy>();\n        public BulkObservableCollection<ImageCardDataViewModel> Cards { get; } = new BulkObservableCollection<ImageCardDataViewModel>();\n\n        private ImageHashDataModelProxy unknownHash;\n        public ImageHashDataModelProxy UnknownHash { get => unknownHash; set => PropertySetAndNotify(value, ref unknownHash); }\n\n        public BulkObservableCollection<ContextActionViewModel> ContextActions { get; } = new BulkObservableCollection<ContextActionViewModel>();\n\n        private bool isOverlayActive = false;\n        public bool IsOverlayActive { get => isOverlayActive; set => PropertySetAndNotify(value, ref isOverlayActive); }\n\n        private Mode activeMode = Mode.Info;\n        public Mode ActiveMode { get => activeMode; set { PropertySetAndNotify(value, ref activeMode); OnPropertyChanged(\"ActiveModeSwitcherIdx\"); } }\n        public int ActiveModeSwitcherIdx => (int)ActiveMode;\n\n        private string descAnalyzerState;\n        public string DescAnalyzerState { get => descAnalyzerState; set => PropertySetAndNotify(value, ref descAnalyzerState); }\n\n        private AnalyzerState currentAnalyzerState;\n        public AnalyzerState CurrentAnalyzerState { get => currentAnalyzerState; set => PropertySetAndNotify(value, ref currentAnalyzerState); }\n\n        private IImageHashMatch selectedLearnMatch;\n        public IImageHashMatch SelectedLearnMatch { get => selectedLearnMatch; set => PropertySetAndNotify(value, ref selectedLearnMatch); }\n\n        private int numUnknownHashes = 0;\n        public int NumUnknownHashes { get => numUnknownHashes; set { PropertySetAndNotify(value, ref numUnknownHashes); OnPropertyChanged(\"MainForm_Screenshot_Learn_PendingPlural\"); } }\n\n        public bool HasAnyHashes => Hashes.Count > 0;\n        public bool HasAnyCards => Cards.Count > 0;\n\n        public ICommand CommandToggleOverlay { get; private set; }\n        public ICommand CommandRemoveLocalHashes { get; private set; }\n        public ICommand CommandBuildContextActions { get; private set; }\n        public ICommand CommandLearnHash { get; private set; }\n        public ICommand CommandLearnDiscardAll { get; private set; }\n\n\n        public string MainForm_Dynamic_Screenshot_SelectDetectionMatch => loc.strings.MainForm_Dynamic_Screenshot_SelectDetectionMatch;\n        public string MainForm_Screenshot_CurrentState => loc.strings.MainForm_Screenshot_CurrentState;\n        public string MainForm_Screenshot_History_CardsColumnDetection => loc.strings.MainForm_Screenshot_History_CardsColumnDetection;\n        public string MainForm_Screenshot_History_CardsColumnSides => loc.strings.MainForm_Screenshot_History_CardsColumnSides;\n        public string MainForm_Screenshot_History_CardsColumnType => loc.strings.MainForm_Screenshot_History_CardsColumnType;\n        public string MainForm_Screenshot_History_HashColumnDetection => loc.strings.MainForm_Screenshot_History_HashColumnDetection;\n        public string MainForm_Screenshot_History_HashColumnType => loc.strings.MainForm_Screenshot_History_HashColumnType;\n        public string MainForm_Screenshot_InfoLines => loc.strings.MainForm_Screenshot_InfoLines;\n        public string MainForm_Screenshot_Learn_DetectList => loc.strings.MainForm_Screenshot_Learn_DetectList;\n        public string MainForm_Screenshot_Learn_DiscardAllButton => loc.strings.MainForm_Screenshot_Learn_DiscardAllButton;\n        public string MainForm_Screenshot_Learn_DiscardAllInfo => loc.strings.MainForm_Screenshot_Learn_DiscardAllInfo;\n        public string MainForm_Screenshot_Learn_PendingPlural => string.Format(loc.strings.MainForm_Screenshot_Learn_PendingPlural, ScreenAnalyzer.unknownHashes.Count - 1);\n        public string MainForm_Screenshot_Learn_PendingSingular => loc.strings.MainForm_Screenshot_Learn_PendingSingular;\n        public string MainForm_Screenshot_Learn_SaveButton => loc.strings.MainForm_Screenshot_Learn_SaveButton;\n        public string MainForm_Screenshot_Learn_SourceImage => loc.strings.MainForm_Screenshot_Learn_SourceImage;\n        public string MainForm_Screenshot_Learn_Type => loc.strings.MainForm_Screenshot_Learn_Type;\n        public string MainForm_Screenshot_ListHint => loc.strings.MainForm_Screenshot_ListHint;\n        public string MainForm_Screenshot_RemovePatternsButton => loc.strings.MainForm_Screenshot_RemovePatternsButton;\n        public string MainForm_Screenshot_RemovePatternsTitle => loc.strings.MainForm_Screenshot_RemovePatternsTitle;\n\n        public PageScreenshotViewModel()\n        {\n            // design time only\n        }\n\n        public PageScreenshotViewModel(MainWindowViewModel mainVM)\n        {\n            MainWindow = mainVM;\n\n            CommandToggleOverlay = new RelayCommand<object>(CommandToggleOverlayFunc);\n            CommandRemoveLocalHashes = new RelayCommand<object>(CommandRemoveLocalHashesFunc, (_) => PlayerSettingsDB.Get().customHashes.Count > 0);\n            CommandBuildContextActions = new RelayCommand<object>(CommandBuildContextActionsFunc);\n            CommandLearnHash = new RelayCommand<object>(CommandLearnHashFunc, (_) => SelectedLearnMatch != null);\n            CommandLearnDiscardAll = new RelayCommand<object>(CommandLearnDiscardAllFunc);\n        }\n\n        public override void RefreshLocalization()\n        {\n            base.RefreshLocalization();\n\n            UpdateAnalyzerDesc();\n            ContextActions.Clear();\n\n            foreach (var entry in Hashes)\n            {\n                entry.RefreshLocalization();\n            }\n\n            foreach (var entry in Cards)\n            {\n                entry.RefreshLocalization();\n            }\n\n            UnknownHash?.RefreshLocalization();\n        }\n\n        public void RequestDebugScreenshot(bool useCachedOnly)\n        {\n            ScreenAnalyzer.EMode mode = ScreenAnalyzer.EMode.Debug | (useCachedOnly ? ScreenAnalyzer.EMode.DebugScreenshotOnly : ScreenAnalyzer.EMode.None);\n\n            //ScreenAnalyzer.unknownHashes.Clear();\n            //ScreenAnalyzer.currentHashMatches.Clear();\n\n            ScreenAnalyzer.DoWork(mode | ScreenAnalyzer.EMode.ScanAll | ScreenAnalyzer.EMode.DebugSaveMarkup);\n\n            var state = ScreenAnalyzer.GetCurrentState();\n            if (state == ScreenAnalyzer.EState.NoErrors)\n            {\n                Rectangle clipBounds = ScreenAnalyzer.scannerTriad.GetTimerScanBox();\n                if (clipBounds.Width > 0)\n                {\n                    ScreenAnalyzer.scanClipBounds = clipBounds;\n                    ScreenAnalyzer.DoWork(mode | ScreenAnalyzer.EMode.ScanTriad | ScreenAnalyzer.EMode.NeverResetCache, (int)ScannerTriad.EScanMode.TimerOnly);\n                    ScreenAnalyzer.scanClipBounds = Rectangle.Empty;\n                }\n            }\n\n            MainWindow.Overlay.UpdateScreenState(true);\n            UpdateState();\n        }\n\n        private void CommandToggleOverlayFunc(object dummyParam)\n        {\n            IsOverlayActive = !IsOverlayActive;\n            ViewModelServices.OverlayWindow.SetOverlayActive(MainWindow.Overlay, IsOverlayActive);\n\n            if (IsOverlayActive)\n            {\n                ScreenAnalyzer.InitializeScreenData();\n                MainWindow.Overlay.OnOverlayActive();\n            }\n\n            UpdateAnalyzerDesc();\n        }\n\n        private void CommandRemoveLocalHashesFunc(object dummyParam)\n        {\n            ImageHashDB.Get().Load();\n\n            PlayerSettingsDB.Get().customHashes.Clear();\n            PlayerSettingsDB.Get().MarkDirty();\n\n            ScreenAnalyzer.ClearKnownHashes();\n            ScreenAnalyzer.scannerTriad.cachedCardState.Clear();\n\n            UpdateState();\n        }\n\n        private void CommandLearnHashFunc(object dummyParam)\n        {\n            if (ScreenAnalyzer.unknownHashes.Count > 0 && selectedLearnMatch != null)\n            {\n                ImageHashData hashData = UnknownHash.hashData;\n                hashData.ownerOb = selectedLearnMatch.GetMatchOwner();\n\n                PlayerSettingsDB.Get().AddKnownHash(hashData);\n\n                ScreenAnalyzer.PopUnknownHash();\n                NumUnknownHashes = ScreenAnalyzer.unknownHashes.Count;\n                SelectedLearnMatch = null;\n\n                UpdateState();\n            }\n        }\n\n        private void CommandLearnDiscardAllFunc(object dummyParam)\n        {\n            ScreenAnalyzer.ClearAll();\n            NumUnknownHashes = ScreenAnalyzer.unknownHashes.Count;\n\n            UpdateState();\n        }\n\n        public void UpdateAnalyzerDesc()\n        {\n            var showState = ScreenAnalyzer.GetCurrentState();\n            var newAnalyzerState = AnalyzerState.Error;\n\n            if (IsOverlayActive || showState == ScreenAnalyzer.EState.UnknownHash)\n            {\n                switch (showState)\n                {\n                    case ScreenAnalyzer.EState.NoInputImage:\n                        switch (ScreenAnalyzer.screenReader.currentState)\n                        {\n                            case ScreenReader.EState.MissingGameProcess: DescAnalyzerState = loc.strings.OverlayForm_Dynamic_Status_MissingGameProcess; break;\n                            case ScreenReader.EState.MissingGameWindow: DescAnalyzerState = loc.strings.OverlayForm_Dynamic_Status_MissingGameWindow; break;\n                            default: DescAnalyzerState = loc.strings.OverlayForm_Dynamic_Status_NoInputImage; break;\n                        }\n                        break;\n\n                    case ScreenAnalyzer.EState.NoScannerMatch:\n                        DescAnalyzerState = loc.strings.OverlayForm_Dynamic_Status_NoScannerMatch;\n                        break;\n\n                    case ScreenAnalyzer.EState.UnknownHash:\n                        DescAnalyzerState = loc.strings.MainForm_Dynamic_Screenshot_Status_UnknownHash;\n                        newAnalyzerState = AnalyzerState.Warning;\n                        break;\n\n                    case ScreenAnalyzer.EState.ScannerErrors:\n                        DescAnalyzerState = loc.strings.OverlayForm_Dynamic_Status_ScannerErrors;\n                        if (ScreenAnalyzer.activeScanner is ScannerTriad)\n                        {\n                            switch (ScreenAnalyzer.scannerTriad.cachedScanError)\n                            {\n                                case ScannerTriad.EScanError.MissingGrid: DescAnalyzerState = loc.strings.MainForm_Dynamic_Screenshot_Status_MissingGridOrCards; break;\n                                case ScannerTriad.EScanError.MissingCards: DescAnalyzerState = loc.strings.MainForm_Dynamic_Screenshot_Status_MissingGridOrCards; break;\n                                case ScannerTriad.EScanError.FailedCardMatching: DescAnalyzerState = loc.strings.MainForm_Dynamic_Screenshot_Status_FailedCardMatching; break;\n                                default: break;\n                            }\n                        }\n                        break;\n\n                    case ScreenAnalyzer.EState.NoErrors:\n                        DescAnalyzerState = loc.strings.MainForm_Dynamic_Screenhot_Status_NoErrors;\n                        newAnalyzerState = AnalyzerState.Active;\n                        break;\n\n                    default:\n                        DescAnalyzerState = \"??\";\n                        break;\n                }\n            }\n            else\n            {\n                DescAnalyzerState = loc.strings.MainForm_Dynamic_Screenshot_Status_Disabled;\n                newAnalyzerState = AnalyzerState.Disabled;\n            }\n\n            CurrentAnalyzerState = newAnalyzerState;\n        }\n\n        public void UpdateState()\n        {\n            UpdateAnalyzerDesc();\n\n            var showState = ScreenAnalyzer.GetCurrentState();\n            if ((showState == ScreenAnalyzer.EState.UnknownHash) && (ScreenAnalyzer.unknownHashes.Count > 0))\n            {\n                UnknownHash = new ImageHashDataModelProxy(ScreenAnalyzer.unknownHashes[0]);\n                NumUnknownHashes = ScreenAnalyzer.unknownHashes.Count;\n                ActiveMode = Mode.Learn;\n            }\n            else if ((ScreenAnalyzer.currentHashMatches.Count > 0) || (ScreenAnalyzer.scannerTriad.cachedCardState.Count > 0))\n            {\n                UpdateStateHashes();\n                ActiveMode = Mode.Hashes;\n            }\n            else\n            {\n                ActiveMode = Mode.Info;\n            }\n        }\n\n        private void UpdateStateHashes()\n        {\n            Hashes.SuspendNotifies();\n            Hashes.Clear();\n            foreach (var hashData in ScreenAnalyzer.currentHashMatches)\n            {\n                if ((hashData.type != EImageHashType.CardNumber) && (hashData.type != EImageHashType.CardImage))\n                {\n                    Hashes.Add(new ImageHashDataModelProxy(hashData));\n                }\n            }\n\n            Hashes.ResumeNotifies();\n            OnPropertyChanged(\"HasAnyHashes\");\n\n            Cards.SuspendNotifies();\n            Cards.Clear();\n            ScreenAnalyzer.scannerTriad.cachedCardState.Sort();\n            foreach (var cardState in ScreenAnalyzer.scannerTriad.cachedCardState)\n            {\n                if ((cardState.state != ScannerTriad.ECardState.None) && (cardState.state != ScannerTriad.ECardState.Hidden))\n                {\n                    Cards.Add(new ImageCardDataViewModel(cardState));\n                }\n            }\n\n            Cards.ResumeNotifies();\n            OnPropertyChanged(\"HasAnyCards\");\n        }\n\n        private void CommandBuildContextActionsFunc(object param)\n        {\n            ContextActions.SuspendNotifies();\n\n            if (param is ImageHashDataModelProxy)\n            {\n                // hash: adjust & delete\n                if (ContextActions.Count != 2)\n                {\n                    ContextActions.Clear();\n\n                    ContextActions.Add(new ContextActionViewModel()\n                    {\n                        Name = loc.strings.MainForm_CtxMenu_Learn_Adjust,\n                        Command = new RelayCommand<object>(x => CommandAdjustImageHash(x as ImageHashDataModelProxy))\n                    });\n\n                    ContextActions.Add(new ContextActionViewModel()\n                    {\n                        Name = loc.strings.MainForm_CtxMenu_Learn_Delete,\n                        Command = new RelayCommand<object>(x => CommandDeleteImageHash(x as ImageHashDataModelProxy), x => CanDeleteImageHash(x as ImageHashDataModelProxy))\n                    });\n                }\n            }\n            else\n            {\n                // card: adjust only\n                if (ContextActions.Count != 1)\n                {\n                    ContextActions.Clear();\n\n                    ContextActions.Add(new ContextActionViewModel()\n                    {\n                        Name = loc.strings.MainForm_CtxMenu_Learn_Adjust,\n                        Command = new RelayCommand<object>(x => CommandAdjustImageCard(x as ImageCardDataViewModel), x => CanAdjustImageCard(x as ImageCardDataViewModel))\n                    });\n                }\n            }\n\n            ContextActions.ResumeNotifies();\n        }\n\n        private void CommandAdjustImageHash(ImageHashDataModelProxy hashVM)\n        {\n            var editVM = new AdjustHashViewModel() { HashProxy = hashVM };\n            var result = ViewModelServices.DialogWindow.ShowDialog(editVM);\n            if (result ?? false)\n            {\n                Logger.WriteLine(\"Adjust: {0} => {1}\", hashVM.NameLocalized, editVM.SelectedMatch.NameLocalized);\n\n                PlayerSettingsDB.Get().RemoveKnownHash(hashVM.hashData);\n                hashVM.hashData.ownerOb = editVM.SelectedMatch.GetMatchOwner();\n                hashVM.hashData.matchDistance = 0;\n                hashVM.hashData.isAuto = false;\n\n                PlayerSettingsDB.Get().AddKnownHash(hashVM.hashData);\n                UpdateState();\n            }\n        }\n\n        private bool CanDeleteImageHash(ImageHashDataModelProxy hashVM)\n        {\n            return (hashVM != null) && (hashVM.hashData.matchDistance == 0) && !hashVM.hashData.isAuto;\n        }\n\n        private void CommandDeleteImageHash(ImageHashDataModelProxy hashVM)\n        {\n            Logger.WriteLine(\"Delete hash: {0}\", hashVM.NameLocalized);\n\n            PlayerSettingsDB.Get().RemoveKnownHash(hashVM.hashData);\n\n            // remove from screen analyzer and update list to show result of user actions\n            ScreenAnalyzer.currentHashMatches.Remove(hashVM.hashData);\n            UpdateState();\n        }\n\n        private bool CanAdjustImageCard(ImageCardDataViewModel cardVM)\n        {\n            return (cardVM != null) && (cardVM.cardState.state != ScannerTriad.ECardState.Hidden) && (cardVM.cardState.state != ScannerTriad.ECardState.None);\n        }\n\n        private void CommandAdjustImageCard(ImageCardDataViewModel cardVM)\n        {\n            var orgName = cardVM.NameLocalized;\n            var editVM = new AdjustCardViewModel(cardVM);\n            var result = ViewModelServices.DialogWindow.ShowDialog(editVM);\n            if (result ?? false)\n            {\n                bool hasChanges = false;\n\n                // check modified numbers\n                int[] AdjustedNum = { editVM.SideUp, editVM.SideLeft, editVM.SideDown, editVM.SideRight };\n                for (int idx = 0; idx < 4; idx++)\n                {\n                    if (cardVM.cardState.sideNumber[idx] != AdjustedNum[idx])\n                    {\n                        Logger.WriteLine(\"Adjust: {0} => {1} ([{2}]: {3} => {4})\",\n                            orgName, editVM.SelectedMatch.NameLocalized,\n                            idx,\n                            cardVM.cardState.sideNumber[idx], AdjustedNum[idx]);\n\n                        ImageHashData digitPattern = new ImageHashData() { type = EImageHashType.CardNumber, previewBounds = cardVM.cardState.sideInfo[idx].scanBox, previewContextBounds = cardVM.cardState.scanBox, isKnown = true };\n                        digitPattern.CalculateHash(cardVM.cardState.sideInfo[idx].hashValues);\n                        digitPattern.ownerOb = AdjustedNum[idx];\n\n                        PlayerSettingsDB.Get().RemoveKnownHash(digitPattern);\n                        if (AdjustedNum[idx] != cardVM.cardState.sideInfo[idx].matchNum)\n                        {\n                            PlayerSettingsDB.Get().AddKnownHash(digitPattern);\n                            cardVM.cardState.sideInfo[idx].hasOverride = true;\n                        }\n\n                        cardVM.cardState.sideNumber[idx] = AdjustedNum[idx];\n                        cardVM.cardState.card = editVM.SelectedMatch.cardOb;\n                        cardVM.cardState.failedMatching = false;\n                        hasChanges = true;\n                    }\n                }\n\n                // check multicard hash\n                if (editVM.HasMultipleMatches && cardVM.cardState.cardImageHash != null && cardVM.cardState.card != editVM.SelectedMatch.cardOb)\n                {\n                    Logger.WriteLine(\"Adjust: {0} => {1} (image hash)\", orgName, editVM.SelectedMatch.NameLocalized);\n                    cardVM.cardState.cardImageHash.ownerOb = editVM.SelectedMatch.cardOb;\n\n                    PlayerSettingsDB.Get().RemoveKnownHash(cardVM.cardState.cardImageHash);\n                    PlayerSettingsDB.Get().AddKnownHash(cardVM.cardState.cardImageHash);\n\n                    cardVM.cardState.card = editVM.SelectedMatch.cardOb;\n                    hasChanges = true;\n                }\n\n                if (hasChanges)\n                {\n                    UpdateStateHashes();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/viewmodel/PageSetupViewModel.cs",
    "content": "﻿using MgAl2O4.GoogleAPI;\nusing MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\nusing System.Windows.Data;\nusing System.Windows.Input;\nusing System.Windows.Threading;\n\nnamespace FFTriadBuddy.UI\n{\n    public class PageSetupViewModel : LocalizedViewModel\n    {\n        public MainWindowViewModel MainWindow;\n        public TriadDeckOptimizer DeckOptimizer = new TriadDeckOptimizer();\n        public SolvableDeckViewModel DeckSolver { get; } = new SolvableDeckViewModel();\n        public SetupFavDeckViewModel FavDeckVM { get; } = new SetupFavDeckViewModel();\n\n        public List<NpcModelProxy> Npcs => ModelProxyDB.Get().Npcs;\n        public List<RuleModelProxy> Rules => ModelProxyDB.Get().Rules;\n        public List<TournamentModelProxy> Tournaments => ModelProxyDB.Get().Tournaments;\n\n        private NpcModelProxy activeNpc;\n        public NpcModelProxy ActiveNpc\n        {\n            get => activeNpc;\n            set\n            {\n                if (value == null)\n                {\n                    // nope, force going back to previous one\n                    OnPropertyChanged();\n                }\n                else if (value != activeNpc)\n                {\n                    PropertySetAndNotify(value, ref activeNpc);\n                    MainWindow.GameModel.SetNpc(activeNpc?.npcOb);\n                }\n            }\n        }\n\n        private RuleModelProxy activeRegionRule1;\n        public RuleModelProxy ActiveRegionRule1 { get => activeRegionRule1; set { PropertySetAndNotify(value, ref activeRegionRule1); UpdateModelRules(); } }\n\n        private RuleModelProxy activeRegionRule2;\n        public RuleModelProxy ActiveRegionRule2 { get => activeRegionRule2; set { PropertySetAndNotify(value, ref activeRegionRule2); UpdateModelRules(); } }\n\n        private TournamentModelProxy activeTournament;\n        public TournamentModelProxy ActiveTournament { get => activeTournament; set { PropertySetAndNotify(value, ref activeTournament); UpdateModelRules(); } }\n\n        private readonly DeckViewModel activeDeck = new DeckViewModel();\n        public DeckViewModel ActiveDeck => DeckSolver.Deck as DeckViewModel;\n\n        private string descDeckState;\n        public string DescDeckState { get => descDeckState; set { if (value != descDeckState) { PropertySetAndNotify(value, ref descDeckState); } } }\n\n        private bool isRegionMode = true;\n        public bool IsRegionMode { get => isRegionMode; set { PropertySetAndNotify(value, ref isRegionMode); UpdateModelRules(); } }\n\n        private bool isCloudSaveEnabled = false;\n        public bool IsCloudSaveEnabled { get => isCloudSaveEnabled; set => PropertySetAndNotify(value, ref isCloudSaveEnabled); }\n\n        private bool isCloudSaveButtonActive = false;\n        public bool IsCloudSaveButtonActive { get => isCloudSaveButtonActive; set => PropertySetAndNotify(value, ref isCloudSaveButtonActive); }\n\n        private bool isDeckOptimizerRunning = false;\n        public bool IsDeckOptimizerRunning { get => isDeckOptimizerRunning; set { PropertySetAndNotify(value, ref isDeckOptimizerRunning); OnPropertyChanged(\"IsDeckOptimizerStopped\"); } }\n        public bool IsDeckOptimizerStopped => !isDeckOptimizerRunning;\n\n        private string cloudSaveStatus;\n        public string CloudSaveStatus { get => cloudSaveStatus; set => PropertySetAndNotify(value, ref cloudSaveStatus); }\n\n        private int optimizerProgress;\n        public int OptimizerProgress { get => optimizerProgress; set { PropertySetAndNotify(value, ref optimizerProgress); OnPropertyChanged(\"OptimizerProgressDesc\"); } }\n        public string OptimizerProgressDesc => DeckOptimizer.IsAborted() ? string.Format(loc.strings.MainForm_Dynamic_Setup_OptimizerProgressAborted, optimizerProgress * 0.01f) : (optimizerProgress + \"%\");\n\n        public int OptimizerNumOwned => ModelProxyDB.Get().OwnedCards.Count;\n\n        private string optimizerNumTestedDesc;\n        public string OptimizerNumTestedDesc { get => optimizerNumTestedDesc; set => PropertySetAndNotify(value, ref optimizerNumTestedDesc); }\n\n        private string optimizerNumPossibleDesc;\n        public string OptimizerNumPossibleDesc { get => optimizerNumPossibleDesc; set => PropertySetAndNotify(value, ref optimizerNumPossibleDesc); }\n\n        private string optimizerTimeLeftDesc;\n        public string OptimizerTimeLeftDesc { get => optimizerTimeLeftDesc; set => PropertySetAndNotify(value, ref optimizerTimeLeftDesc); }\n\n        private TriadDeck optimizerFoundDeck;\n        private bool optimizerFoundDeckDelay = false;\n        private bool optimizerHistoryOverride = false;\n        private int optimizerWaitingForSolveId = -1;\n\n        private List<Tuple<TriadDeck, SolverResult>> deckOptimizerHistory = new List<Tuple<TriadDeck, SolverResult>>();\n\n        public ICommand CommandPickNpc { get; private set; }\n        public ICommand CommandToggleTournament { get; private set; }\n        public ICommand CommandToggleCloudSaves { get; private set; }\n        public ICommand CommandCloudAuth { get; private set; }\n        public ICommand CommandDeckOptimizerStart { get; private set; }\n        public ICommand CommandDeckOptimizerAbort { get; private set; }\n\n        public string MainForm_Setup_Cloud_AuthButton => loc.strings.MainForm_Setup_Cloud_AuthButton;\n        public string MainForm_Setup_Cloud_Desc => loc.strings.MainForm_Setup_Cloud_Desc;\n        public string MainForm_Setup_Deck_OptimizeAbortButton => loc.strings.MainForm_Setup_Deck_OptimizeAbortButton;\n        public string MainForm_Setup_Deck_OptimizeStartButton => loc.strings.MainForm_Setup_Deck_OptimizeStartButton;\n        public string MainForm_Setup_Deck_Title => loc.strings.MainForm_Setup_Deck_Title;\n        public string MainForm_Setup_Fav_AddSlotButton => loc.strings.MainForm_Setup_Fav_AddSlotButton;\n        public string MainForm_Setup_NPC => loc.strings.MainForm_Setup_NPC;\n        public string MainForm_Setup_NPC_DeckPower => loc.strings.MainForm_Setup_NPC_DeckPower;\n        public string MainForm_Setup_NPC_Location => loc.strings.MainForm_Setup_NPC_Location;\n        public string MainForm_Setup_NPC_Rules => loc.strings.MainForm_Setup_NPC_Rules;\n        public string MainForm_Setup_NPC_WinChance => loc.strings.MainForm_Setup_NPC_WinChance;\n        public string MainForm_Setup_OptimizerStats_NumOwned => loc.strings.MainForm_Setup_OptimizerStats_NumOwned;\n        public string MainForm_Setup_OptimizerStats_NumPossible => loc.strings.MainForm_Setup_OptimizerStats_NumPossible;\n        public string MainForm_Setup_OptimizerStats_NumTested => loc.strings.MainForm_Setup_OptimizerStats_NumTested;\n        public string MainForm_Setup_OptimizerStats_Progress => loc.strings.MainForm_Setup_OptimizerStats_Progress;\n        public string MainForm_Setup_OptimizerStats_TimeLeft => loc.strings.MainForm_Setup_OptimizerStats_TimeLeft;\n        public string MainForm_Setup_OptimizeStats_Title => loc.strings.MainForm_Setup_OptimizeStats_Title;\n        public string MainForm_Setup_RulesToggle => loc.strings.MainForm_Setup_RulesToggle;\n        public string MainForm_Setup_Rules_Region1 => loc.strings.MainForm_Setup_Rules_Region1;\n        public string MainForm_Setup_Rules_Region2 => loc.strings.MainForm_Setup_Rules_Region2;\n        public string MainForm_Setup_Rules_Tournament => loc.strings.MainForm_Setup_Rules_Tournament;\n        public string MainForm_Setup_Rules_TournamentRules => loc.strings.MainForm_Setup_Rules_TournamentRules;\n        public string FavDeckCtrl_Edit => loc.strings.FavDeckCtrl_Edit;\n        public string FavDeckForm_Dynamic_UpdateButton => loc.strings.FavDeckForm_Dynamic_UpdateButton;\n        public string FavDeckForm_RemoveButton => loc.strings.FavDeckForm_RemoveButton;\n\n        public PageSetupViewModel()\n        {\n            // design time only\n        }\n\n        public PageSetupViewModel(MainWindowViewModel mainVM)\n        {\n            MainWindow = mainVM;\n\n            CommandPickNpc = new RelayCommand<NpcModelProxy>((npcProxy) => ActiveNpc = npcProxy);\n            CommandToggleTournament = new RelayCommand<bool>((wantsRegionMode) => IsRegionMode = wantsRegionMode);\n            CommandCloudAuth = new RelayCommand<object>((_) => SettingsModel.CloudStorageInit(), (_) => isCloudSaveButtonActive);\n            CommandToggleCloudSaves = new RelayCommand<bool>(CommandToggleCloudSavesFunc);\n            CommandDeckOptimizerStart = new RelayCommand<object>(CommandDeckOptimizerStartFunc);\n            CommandDeckOptimizerAbort = new RelayCommand<object>((_) => DeckOptimizer.AbortProcess());\n\n            DeckOptimizer.OnFoundDeck += DeckOptimizer_OnFoundDeck;\n            DeckSolver.Deck = activeDeck;\n            DeckSolver.EnableTrackingProgress();\n            DeckSolver.solver.OnSolved += DeckOptimizer_OnSolved;\n\n            // force combo box initial values, assign underlying value to avoid setter's notifies\n            var viewRules = CollectionViewSource.GetDefaultView(Rules);\n            viewRules.MoveCurrentToFirst();\n            activeRegionRule1 = activeRegionRule2 = viewRules.CurrentItem as RuleModelProxy;\n\n            var viewTournaments = CollectionViewSource.GetDefaultView(Tournaments);\n            viewTournaments.MoveCurrentToFirst();\n            activeTournament = viewTournaments.CurrentItem as TournamentModelProxy;\n\n            // setup game callbacks\n            mainVM.GameModel.OnNpcChanged += GameModel_OnNpcChanged;\n            mainVM.GameModel.OnDeckChanged += GameModel_OnDeckChanged;\n            GameModel_OnNpcChanged(mainVM.GameModel.Npc);\n            GameModel_OnDeckChanged(mainVM.GameModel.PlayerDeck);\n            DeckSolver.RefreshSolver(mainVM.GameModel, mainVM.GameModel.PlayerDeck);\n\n            ModelProxyDB.Get().OnCardOwnerChanged += PageSetupViewModel_OnCardOwnerChanged;\n            activeDeck.OnCardsChanged += ActiveDeck_OnCardsChanged;\n\n            // setup cloud saves\n            SettingsModel.OnCloudStorageApiUpdate += SettingsModel_OnCloudStorageApiUpdate;\n            SettingsModel.OnCloudStorageStateUpdate += SettingsModel_OnCloudStorageStateUpdate;\n            CommandToggleCloudSavesFunc(PlayerSettingsDB.Get().useCloudStorage);\n\n            // fav decks\n            FavDeckVM.activeDeck = activeDeck;\n            FavDeckVM.gameModel = mainVM.GameModel;\n            FavDeckVM.Initialize();\n        }\n\n        public override void RefreshLocalization()\n        {\n            base.RefreshLocalization();\n\n            DeckSolver.RefreshLocalization();\n            UpdateCloudStorageState();\n            UpdateDeckState();\n        }\n\n        private void GameModel_OnNpcChanged(TriadNpc npcOb)\n        {\n            ActiveNpc = ModelProxyDB.Get().GetNpcProxy(npcOb);\n        }\n\n        private void GameModel_OnDeckChanged(TriadDeck deckOb)\n        {\n            if (activeDeck.Cards.Count != deckOb.knownCards.Count)\n            {\n                activeDeck.Cards.Clear();\n                for (int idx = 0; idx < deckOb.knownCards.Count; idx++)\n                {\n                    activeDeck.Cards.Add(new CardViewModel() { CardOwner = ECardOwner.None });\n                }\n            }\n\n            var modelProxyDB = ModelProxyDB.Get();\n            for (int idx = 0; idx < deckOb.knownCards.Count; idx++)\n            {\n                activeDeck.Cards[idx].CardModel = modelProxyDB.GetCardProxy(deckOb.knownCards[idx]);\n            }\n\n            DeckSolver.RefreshSolver(MainWindow.GameModel, deckOb);\n            UpdateDeckState();\n        }\n\n        private void UpdateDeckState()\n        {\n            var deckState = MainWindow.GameModel.PlayerDeck.GetDeckState();\n            switch (deckState)\n            {\n                case ETriadDeckState.TooMany5Star: DescDeckState = loc.strings.MainForm_Dynamic_DeckState_TooMany5Star; break;\n                case ETriadDeckState.TooMany4Star: DescDeckState = loc.strings.MainForm_Dynamic_DeckState_TooMany4Star; break;\n                case ETriadDeckState.MissingCards: DescDeckState = loc.strings.MainForm_Dynamic_DeckState_MissingCards; break;\n                case ETriadDeckState.HasDuplicates: DescDeckState = loc.strings.MainForm_Dynamic_DeckState_HasDuplicates; break;\n                default: DescDeckState = \"\"; break;\n            }\n        }\n\n        private void PageSetupViewModel_OnCardOwnerChanged(CardModelProxy cardProxy)\n        {\n            if (MainWindow.GameModel.PlayerDeck.GetCardIndex(cardProxy.cardOb) >= 0)\n            {\n                UpdateDeckState();\n            }\n\n            OnPropertyChanged(\"OptimizerNumOwned\");\n        }\n\n        private void ActiveDeck_OnCardsChanged(DeckViewModel deckVM)\n        {\n            var cards = new List<TriadCard>();\n            foreach (var cardVM in deckVM.Cards)\n            {\n                cards.Add(cardVM.CardModel.cardOb);\n            }\n\n            MainWindow.GameModel.SetPlayerDeck(new TriadDeck(cards));\n        }\n\n        private void CommandToggleCloudSavesFunc(bool wantsCloudSaves)\n        {\n            IsCloudSaveEnabled = wantsCloudSaves;\n            SettingsModel.SetUseCloudSaves(wantsCloudSaves);\n\n            UpdateCloudStorageState();\n        }\n\n        private void UpdateCloudStorageState()\n        {\n            if (!IsCloudSaveEnabled)\n            {\n                CloudSaveStatus = loc.strings.MainForm_Dynamic_Setup_CloudStatus_Disabled;\n                IsCloudSaveButtonActive = false;\n            }\n            else if (SettingsModel.CloudStorage == null)\n            {\n                CloudSaveStatus = loc.strings.MainForm_Dynamic_Setup_CloudStatus_NoDatabase;\n                IsCloudSaveButtonActive = false;\n            }\n            else\n            {\n                SettingsModel.CloudStorageRequestState();\n            }\n        }\n\n        private void SettingsModel_OnCloudStorageStateUpdate(SettingsModel.CloudSaveState state)\n        {\n            switch (state)\n            {\n                case SettingsModel.CloudSaveState.Loaded: CloudSaveStatus = loc.strings.MainForm_Dynamic_Setup_CloudSave_Loaded; break;\n                case SettingsModel.CloudSaveState.Saved: CloudSaveStatus = loc.strings.MainForm_Dynamic_Setup_CloudStatus_Uploaded; break;\n                case SettingsModel.CloudSaveState.UpToDate: CloudSaveStatus = loc.strings.MainForm_Dynamic_Setup_CloudStatus_Synced; break;\n                default: break;\n            }\n        }\n\n        private void SettingsModel_OnCloudStorageApiUpdate(GoogleDriveService.EState state)\n        {\n            IsCloudSaveButtonActive = state == GoogleDriveService.EState.NotAuthorized;\n            switch (state)\n            {\n                case GoogleDriveService.EState.NoErrors: CloudSaveStatus = loc.strings.MainForm_Dynamic_Setup_CloudStatus_NoErrors; break;\n                case GoogleDriveService.EState.ApiFailure: CloudSaveStatus = loc.strings.MainForm_Dynamic_Setup_CloudStatus_ApiFailure; break;\n                case GoogleDriveService.EState.NotAuthorized: CloudSaveStatus = loc.strings.MainForm_Dynamic_Setup_CloudStatus_NotAuthorized; break;\n                case GoogleDriveService.EState.AuthInProgress: CloudSaveStatus = loc.strings.MainForm_Dynamic_Setup_CloudStatus_AuthInProgress; break;\n                case GoogleDriveService.EState.NotInitialized: CloudSaveStatus = loc.strings.MainForm_Dynamic_Setup_CloudStatus_NotInitialized; break;\n                default: CloudSaveStatus = \"\"; break;\n            }\n        }\n\n        private async void CommandDeckOptimizerStartFunc(object dummyParam)\n        {\n            var lockedCards = new List<TriadCard>();\n            foreach (var cardVM in activeDeck.Cards)\n            {\n                lockedCards.Add(cardVM.IsShowingLock ? cardVM.CardModel.cardOb : null);\n            }\n\n            DeckOptimizer.Initialize(MainWindow.GameModel.Npc, MainWindow.GameModel.Rules.ToArray(), lockedCards);\n\n            OptimizerNumPossibleDesc = DeckOptimizer.GetNumPossibleDecksDesc();\n            OptimizerNumTestedDesc = \"0\";\n            OptimizerProgress = 0;\n            OptimizerTimeLeftDesc = \"--\";\n            IsDeckOptimizerRunning = true;\n            optimizerHistoryOverride = false;\n            deckOptimizerHistory.Clear();\n\n            var updateTimer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(0.25) };\n            updateTimer.Tick += DeckOptimizerUpdateTimer_Tick;\n            updateTimer.Start();\n\n            await DeckOptimizer.Process(MainWindow.GameModel.Npc, MainWindow.GameModel.Rules.ToArray(), lockedCards);\n\n            IsDeckOptimizerRunning = false;\n            updateTimer.Stop();\n            DeckOptimizerUpdateTimer_Tick(null, null);\n            OptimizerTimeLeftDesc = \"--\";\n\n            if (!optimizerHistoryOverride)\n            {\n                MainWindow.GameModel.SetPlayerDeck(DeckOptimizer.optimizedDeck);\n                if (DeckSolver.IsSolverRunning)\n                {\n                    optimizerWaitingForSolveId = DeckSolver.SolverTaskId;\n                    Logger.WriteLine(\"Optimizer history: waiting for final solver task: {0}\", optimizerWaitingForSolveId);\n                }\n            }\n        }\n\n        private void DeckOptimizerUpdateTimer_Tick(object sender, EventArgs e)\n        {\n            OptimizerProgress = DeckOptimizer.GetProgress();\n            OptimizerNumTestedDesc = DeckOptimizer.GetNumTestedDesc();\n\n            if (sender != null)\n            {\n                var timerInterval = ((DispatcherTimer)sender).Interval;\n                int secondsRemaining = DeckOptimizer.GetSecondsRemaining((int)timerInterval.TotalMilliseconds);\n                var tspan = TimeSpan.FromSeconds(secondsRemaining);\n                if (tspan.Hours > 0 || tspan.Minutes > 55)\n                {\n                    OptimizerTimeLeftDesc = string.Format(\"{0:D2}h:{1:D2}m:{2:D2}s\", tspan.Hours, tspan.Minutes, tspan.Seconds);\n                }\n                else if (tspan.Minutes > 0 || tspan.Seconds > 55)\n                {\n                    OptimizerTimeLeftDesc = string.Format(\"{0:D2}m:{1:D2}s\", tspan.Minutes, tspan.Seconds);\n                }\n                else\n                {\n                    OptimizerTimeLeftDesc = string.Format(\"{0:D2}s\", tspan.Seconds);\n                }\n            }\n        }\n\n        private void DeckOptimizer_OnFoundDeck(TriadDeck deck, float dummyChance)\n        {\n            optimizerFoundDeck = deck;\n\n            // delay to buffer multiple changes\n            if (!optimizerFoundDeckDelay)\n            {\n                optimizerFoundDeckDelay = true;\n\n                var bufferTimer = new DispatcherTimer(DispatcherPriority.Normal, App.Current.Dispatcher) { Interval = TimeSpan.FromSeconds(0.5) };\n                bufferTimer.Tick += DeckOptimizerBufferTimer_Tick;\n                bufferTimer.Start();\n            }\n        }\n\n        private void DeckOptimizerBufferTimer_Tick(object sender, EventArgs e)\n        {\n            MainWindow.GameModel.SetPlayerDeck(optimizerFoundDeck);\n            optimizerFoundDeckDelay = false;\n            ((DispatcherTimer)sender).Stop();\n\n            // solver has better accuracy than optimizer, collect all win chances and assign best one to avoid going down with %\n            if (DeckSolver.IsSolverRunning)\n            {\n                optimizerWaitingForSolveId = DeckSolver.SolverTaskId;\n            }\n        }\n\n        private void DeckOptimizer_OnSolved(int id, TriadDeck deck, SolverResult chance)\n        {\n            MainWindow.GameModel.SetCachedWinChance(deck, chance);\n\n            if (optimizerWaitingForSolveId >= 0)\n            {\n                if (optimizerWaitingForSolveId == id)\n                {\n                    optimizerWaitingForSolveId = -1;\n                }\n\n                deckOptimizerHistory.Add(new Tuple<TriadDeck, SolverResult>(deck, chance));\n                UpdateDeckOptimizerHistory();\n            }\n        }\n\n        private void UpdateDeckOptimizerHistory()\n        {\n            var currentChance = MainWindow.GameModel.CachedWinChance;\n\n            TriadDeck switchToDeck = null;\n            foreach (var entry in deckOptimizerHistory)\n            {\n                if (entry.Item2.IsBetterThan(currentChance))\n                {\n                    currentChance = entry.Item2;\n                    switchToDeck = entry.Item1;\n                }\n            }\n\n            if (switchToDeck != null)\n            {\n                Logger.WriteLine(\"Optimizer history: switch back to win:{0:P0}, deck:{1}\", currentChance.winChance, switchToDeck);\n                optimizerHistoryOverride = true;\n                MainWindow.GameModel.SetPlayerDeck(switchToDeck);\n            }\n        }\n\n        private void UpdateModelRules()\n        {\n            if (isRegionMode)\n            {\n                MainWindow.GameModel.SetGameRules(activeRegionRule1?.modOb, activeRegionRule2?.modOb);\n            }\n            else if (activeTournament != null)\n            {\n                MainWindow.GameModel.SetGameRules(activeTournament.tournamentOb.Rules);\n            }\n            else\n            {\n                MainWindow.GameModel.SetGameRules(null);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/viewmodel/PageSimulateViewModel.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Windows.Input;\n\nnamespace FFTriadBuddy.UI\n{\n    public class PageSimulateViewModel : LocalizedViewModel, ICardDragDropTarget\n    {\n        public class BoardCardVM\n        {\n            public CardViewModel Card { get; private set; }\n            public int BoardX { get; private set; }\n            public int BoardY { get; private set; }\n\n            public BoardCardVM(int idx)\n            {\n                BoardX = idx % 3;\n                BoardY = idx / 3;\n\n                Card = new CardViewModel() { OwnerIndex = idx };\n            }\n        }\n\n        public MainWindowViewModel MainWindow;\n        public SimulateRulesViewModel SpecialRules { get; } = new SimulateRulesViewModel();\n\n        public DeckViewModel PlayerDeck { get; } = new DeckViewModel();\n        public DeckViewModel RedKnownCards { get; } = new DeckViewModel();\n        public DeckViewModel RedUnknownCards { get; } = new DeckViewModel();\n        public BulkObservableCollection<BoardCardVM> BoardCards { get; } = new BulkObservableCollection<BoardCardVM>();\n\n        public WinChanceViewModel WinChance { get; } = new WinChanceViewModel();\n\n        private string descRules;\n        public string DescRules { get => descRules; set { if (value != descRules) { PropertySetAndNotify(value, ref descRules); } } }\n\n        private bool waitingGameResult = false;\n        private string descGameResult = null;\n        public string DescChanceAndResult => descGameResult == null ? WinChance.DescProbability : descGameResult;\n\n        private bool hasGameHint = false;\n        public bool HasGameHint { get => hasGameHint; set { if (value != hasGameHint) { PropertySetAndNotify(value, ref hasGameHint); OnPropertyChanged(\"DescGameHint\"); } } }\n        public string DescGameHint => hasGameHint ? loc.strings.MainForm_Dynamic_Simulate_LastCardHint : loc.strings.MainForm_Simulate_Game_ListHint;\n\n        private string descUndoButton;\n        public string DescUndoButton { get => descUndoButton; set => PropertySetAndNotify(value, ref descUndoButton); }\n\n        private int numUnknownToPlace = 0;\n        public int NumUnknownToPlace { get => numUnknownToPlace; set { PropertySetAndNotify(value, ref numUnknownToPlace); OnPropertyChanged(\"DescUnknownCards\"); OnPropertyChanged(\"HasUnknownCards\"); } }\n        public string DescUnknownCards => string.Format(loc.strings.MainForm_Simulate_Game_UnknownCards, numUnknownToPlace);\n        public bool HasUnknownCards => numUnknownToPlace > 0;\n\n        private bool canStartWithBlue = false;\n        public bool CanStartWithBlue { get => canStartWithBlue; set => PropertySetAndNotify(value, ref canStartWithBlue); }\n\n        private bool canSelectBlue = false;\n        public bool CanSelectBlue { get => canSelectBlue; set => PropertySetAndNotify(value, ref canSelectBlue); }\n\n        public int SpecialRuleSwitcherIdx => (int)SpecialRules.ActiveRule;\n        public int SpecialRuleSwitcherBoardIdx => (SpecialRules.ActiveRule == SimulateRulesViewModel.Rule.None) ? 0 : 1;\n\n        private TriadGameSimulationState cachedLastState;\n        private TriadGameModel.Move cachedLastMove;\n\n        public ICommand CommandReset { get; private set; }\n        public ICommand CommandBlueFirst { get; private set; }\n        public ICommand CommandRedUndo { get; private set; }\n\n        public string MainForm_Simulate_Debug_ForceCached => loc.strings.MainForm_Simulate_Debug_ForceCached;\n        public string MainForm_Simulate_Debug_Info => loc.strings.MainForm_Simulate_Debug_Info;\n        public string MainForm_Simulate_Game_ApplyRuleButton => loc.strings.MainForm_Simulate_Game_ApplyRuleButton;\n        public string MainForm_Simulate_Game_KnownCards => loc.strings.MainForm_Simulate_Game_KnownCards;\n        public string MainForm_Simulate_Game_SkipRuleButton => loc.strings.MainForm_Simulate_Game_SkipRuleButton;\n        public string MainForm_Simulate_Game_SpecialRule => loc.strings.MainForm_Simulate_Game_SpecialRule;\n        public string MainForm_Simulate_Open_Hint => loc.strings.MainForm_Simulate_Open_Hint;\n        public string MainForm_Simulate_Random_Info => loc.strings.MainForm_Simulate_Random_Info;\n        public string MainForm_Simulate_ResetButton => loc.strings.MainForm_Simulate_ResetButton;\n        public string MainForm_Simulate_Roulette_Rule1 => loc.strings.MainForm_Simulate_Roulette_Rule1;\n        public string MainForm_Simulate_Roulette_Rule2 => loc.strings.MainForm_Simulate_Roulette_Rule2;\n        public string MainForm_Simulate_Roulette_Rule3 => loc.strings.MainForm_Simulate_Roulette_Rule3;\n        public string MainForm_Simulate_Roulette_Rule4 => loc.strings.MainForm_Simulate_Roulette_Rule4;\n        public string MainForm_Simulate_RuleList => loc.strings.MainForm_Simulate_RuleList;\n        public string MainForm_Simulate_WinChance => loc.strings.MainForm_Simulate_WinChance;\n        public string MainForm_Dynamic_Simulate_ChangeBlueHint => loc.strings.MainForm_Dynamic_Simulate_ChangeBlueHint;\n        public string MainForm_Dynamic_Simulate_SwapRuleButton => loc.strings.MainForm_Dynamic_Simulate_SwapRuleButton;\n        public string MainForm_Dynamic_Simulate_BlueStartButton => loc.strings.MainForm_Dynamic_Simulate_BlueStartButton;\n\n        public PageSimulateViewModel()\n        {\n            // design time only\n        }\n\n        public PageSimulateViewModel(MainWindowViewModel mainVM)\n        {\n            MainWindow = mainVM;\n\n            for (int idx = 0; idx < 9; idx++)\n            {\n                var boardVM = new BoardCardVM(idx);\n                boardVM.Card.OwnerObject = this;\n                boardVM.Card.IsUsingImageBig = PlayerSettingsDB.Get().useSmallIcons == false;\n                BoardCards.Add(boardVM);\n            }\n\n            PlayerDeck.CanUseBigIcons = false;\n            for (int idx = 0; idx < 5; idx++)\n            {\n                PlayerDeck.Cards.Add(new CardViewModel());\n            }\n\n            SpecialRules.OwnerVM = this;\n            SpecialRules.GameModel = mainVM.GameModel;\n\n            mainVM.GameModel.OnNpcChanged += GameModel_OnNpcChanged;\n            mainVM.GameModel.OnDeckChanged += GameModel_OnDeckChanged;\n            mainVM.GameModel.OnGameStateChanged += GameModel_OnGameStateChanged;\n            mainVM.GameModel.OnCachedWinChanceChanged += GameModel_OnCachedWinChanceChanged;\n            mainVM.GameModel.OnSetupChanged += GameModel_OnSetupChanged;\n            GameModel_OnNpcChanged(mainVM.GameModel.Npc);\n            GameModel_OnDeckChanged(mainVM.GameModel.PlayerDeck);\n            GameModel_OnSetupChanged(mainVM.GameModel);\n            GameModel_OnGameStateChanged(mainVM.GameModel.GameState, null);\n\n            CommandReset = new RelayCommand<object>((_) => MainWindow.GameModel.GameReset());\n            CommandBlueFirst = new RelayCommand<object>((_) => MainWindow.GameModel.GameStartBlue(), (_) => canStartWithBlue && SpecialRules.ActiveRule == SimulateRulesViewModel.Rule.None);\n            CommandRedUndo = new RelayCommand<object>((_) => { waitingGameResult = true; MainWindow.GameModel.GameUndoRed(); }, (_) => MainWindow.GameModel.UndoStateRed.Count > 0);\n\n            PlayerDeck.SetCustomSelect(new RelayCommand<CardViewModel>((card) => MainWindow.GameModel.SetGameForcedBlueCard(card.CardModel.cardOb)));\n            RedKnownCards.CanReceiveDropCards = false;\n            RedUnknownCards.CanReceiveDropCards = false;\n\n            MainWindow.PageInfo.OnSettingsChanged += PageInfo_OnSettingsChanged;\n        }\n\n        public override void RefreshLocalization()\n        {\n            base.RefreshLocalization();\n\n            UpdateRules();\n            UpdateCachedText();\n        }\n\n        public void GameModel_OnGameStateChanged(TriadGameSimulationState state, TriadGameModel.Move move)\n        {\n            var modelProxyDB = ModelProxyDB.Get();\n            cachedLastState = state;\n            cachedLastMove = move;\n\n            // player deck\n            for (int idx = 0; idx < PlayerDeck.Cards.Count; idx++)\n            {\n                var cardVM = PlayerDeck.Cards[idx];\n                var deckCardOb = state.deckBlue.GetCard(idx);\n\n                cardVM.IsPreview = (move != null) && (move.Card == deckCardOb);\n                cardVM.CardModel = (!cardVM.IsPreview && state.deckBlue.IsPlaced(idx)) ? null : modelProxyDB.GetCardProxy(deckCardOb);\n            }\n\n            var hasPendingInteractiveRules = VerifyInteractiveRules(state);\n            if (!hasPendingInteractiveRules)\n            {\n                // board\n                for (int idx = 0; idx < BoardCards.Count; idx++)\n                {\n                    var cardVM = BoardCards[idx].Card;\n                    cardVM.Assign(state.board[idx]);\n                    cardVM.IsHighlighted = false;\n                }\n\n                // red decks\n                int numKnownRed = state.deckRed.deck.knownCards.Count;\n                SyncDeckVM(RedKnownCards, state.deckRed.deck.knownCards);\n                for (int idx = 0; idx < numKnownRed; idx++)\n                {\n                    var isPlaced = state.deckRed.IsPlaced(idx);\n                    RedKnownCards.Cards[idx].IsShowingDetails = !isPlaced;\n                    if (isPlaced)\n                    {\n                        RedKnownCards.Cards[idx].CardModel = null;\n                    }\n                }\n\n                NumUnknownToPlace = 5 - numKnownRed - state.deckRed.numUnknownPlaced;\n                if (NumUnknownToPlace > 0)\n                {\n                    SyncDeckVM(RedUnknownCards, state.deckRed.deck.unknownCardPool);\n                    for (int idx = 0; idx < state.deckRed.deck.unknownCardPool.Count; idx++)\n                    {\n                        var isPlaced = state.deckRed.IsPlaced(numKnownRed + idx);\n                        RedUnknownCards.Cards[idx].IsShowingDetails = !isPlaced;\n                        if (isPlaced)\n                        {\n                            RedUnknownCards.Cards[idx].CardModel = null;\n                        }\n                    }\n                }\n            }\n\n            // current move\n            if (move != null)\n            {\n                BoardCards[move.BoardIdx].Card.IsHighlighted = true;\n                WinChance.SetValue(move.WinChance);\n\n                // make sure tat undo button is updated, lags a bit on first move\n                (CommandRedUndo as RelayCommand<object>).RaiseCanExecuteChanged();\n            }\n            else if (state.numCardsPlaced == 0)\n            {\n                WinChance.SetValue(MainWindow.GameModel.CachedWinChance);\n            }\n            else\n            {\n                WinChance.SetInvalid();\n            }\n\n            // other\n            CanStartWithBlue = state.numCardsPlaced == 0;\n            UpdateCachedText();\n        }\n\n        private void GameModel_OnCachedWinChanceChanged(TriadGameModel model)\n        {\n            if (model.GameState != null && model.GameState.numCardsPlaced == 0)\n            {\n                WinChance.SetValue(model.CachedWinChance);\n            }\n        }\n\n        private void GameModel_OnSetupChanged(TriadGameModel model)\n        {\n            UpdateRules();\n\n            CanSelectBlue = model.Solver.HasSimulationRule(ETriadGameSpecialMod.BlueCardSelection);\n        }\n\n        private void GameModel_OnDeckChanged(TriadDeck deckOb)\n        {\n            SyncDeckVM(PlayerDeck, deckOb.knownCards);\n        }\n\n        private void GameModel_OnNpcChanged(TriadNpc npcOb)\n        {\n            SyncDeckVM(RedKnownCards, npcOb.Deck.knownCards);\n            SyncDeckVM(RedUnknownCards, npcOb.Deck.unknownCardPool);\n        }\n\n        private void PageInfo_OnSettingsChanged(object sender, SettingsEventArgs e)\n        {\n            if (e.Type == SettingsEventArgs.Setting.UseSmallIcons)\n            {\n                // updating just IsUsingImageBig is causing weird artifacts, rebuild entire VM \n                // this isn't changing often (or hopefully: at all)\n\n                BoardCards.Clear();\n                for (int idx = 0; idx < 9; idx++)\n                {\n                    var boardVM = new BoardCardVM(idx);\n                    boardVM.Card.OwnerObject = this;\n                    boardVM.Card.IsUsingImageBig = PlayerSettingsDB.Get().useSmallIcons == false;\n                    BoardCards.Add(boardVM);\n                }\n\n                GameModel_OnGameStateChanged(cachedLastState, cachedLastMove);\n            }\n        }\n\n        private void SyncDeckVM(DeckViewModel deckVM, List<TriadCard> cards)\n        {\n            while (deckVM.Cards.Count > cards.Count)\n            {\n                deckVM.Cards.RemoveAt(deckVM.Cards.Count - 1);\n            }\n\n            while (deckVM.Cards.Count < cards.Count)\n            {\n                deckVM.Cards.Add(new CardViewModel());\n            }\n\n            var modelProxyDB = ModelProxyDB.Get();\n            for (int idx = 0; idx < cards.Count; idx++)\n            {\n                deckVM.Cards[idx].CardModel = modelProxyDB.GetCardProxy(cards[idx]);\n            }\n        }\n\n        private bool VerifyInteractiveRules(TriadGameSimulationState state)\n        {\n            var pendingUIRules = (state.numCardsPlaced == 0) ? (MainWindow.GameModel.Solver.simulation.specialRules & ~state.resolvedSpecial) : ETriadGameSpecialMod.None;\n            if (pendingUIRules != ETriadGameSpecialMod.None)\n            {\n                if ((pendingUIRules & ETriadGameSpecialMod.RandomizeRule) != ETriadGameSpecialMod.None)\n                {\n                    SpecialRules.RequestRuleRoulette();\n                }\n                else if ((pendingUIRules & ETriadGameSpecialMod.RandomizeBlueDeck) != ETriadGameSpecialMod.None)\n                {\n                    SpecialRules.RequestRuleRandom();\n                }\n                else if ((pendingUIRules & ETriadGameSpecialMod.SwapCards) != ETriadGameSpecialMod.None)\n                {\n                    SpecialRules.RequestRuleSwap();\n                }\n                else if ((pendingUIRules & ETriadGameSpecialMod.SelectVisible3) != ETriadGameSpecialMod.None)\n                {\n                    SpecialRules.RequestRuleXOpen(3);\n                }\n                else if ((pendingUIRules & ETriadGameSpecialMod.SelectVisible5) != ETriadGameSpecialMod.None)\n                {\n                    SpecialRules.RequestRuleXOpen(5);\n                }\n            }\n\n            OnPropertyChanged(\"SpecialRuleSwitcherIdx\");\n            OnPropertyChanged(\"SpecialRuleSwitcherBoardIdx\");\n\n            return SpecialRules.ActiveRule != SimulateRulesViewModel.Rule.None;\n        }\n\n        private void UpdateRules()\n        {\n            string desc = \"\";\n            foreach (var rule in MainWindow.GameModel.Solver.simulation.modifiers)\n            {\n                string ruleName = rule.GetLocalizedName();\n                if (ruleName.Length > 0)\n                {\n                    if (desc.Length > 0) { desc += \", \"; }\n                    desc += ruleName;\n                }\n            }\n\n            DescRules = (desc.Length > 0) ? desc : loc.strings.MainForm_Dynamic_RuleListEmpty;\n        }\n\n        private void UpdateCachedText()\n        {\n            string newGameResult = null;\n            switch (MainWindow.GameModel.GameState.state)\n            {\n                case ETriadGameState.BlueWins: newGameResult = loc.strings.MainForm_Dynamic_Simulate_EndGame_BlueWin; break;\n                case ETriadGameState.BlueDraw: newGameResult = loc.strings.MainForm_Dynamic_Simulate_EndGame_BlueDraw; break;\n                case ETriadGameState.BlueLost: newGameResult = loc.strings.MainForm_Dynamic_Simulate_EndGame_BlueLost; break;\n                default:\n                    if (waitingGameResult)\n                    {\n                        waitingGameResult = false;\n                        newGameResult = \"...\";\n                    }\n                    break;\n            }\n\n            descGameResult = newGameResult;\n            OnPropertyChanged(\"DescChanceAndResult\");\n\n            bool hasLastRedReminder = false;\n            if (MainWindow.GameModel.GameState.numCardsPlaced == (MainWindow.GameModel.GameState.board.Length - 1) &&\n                MainWindow.GameModel.GameState.state == ETriadGameState.InProgressRed)\n            {\n                foreach (TriadGameModifier mod in MainWindow.GameModel.Solver.simulation.modifiers)\n                {\n                    hasLastRedReminder = hasLastRedReminder || mod.HasLastRedReminder();\n                }\n            }\n\n            HasGameHint = hasLastRedReminder;\n            DescUndoButton = (MainWindow.GameModel.GameState.numCardsPlaced == 0) ? loc.strings.MainForm_Dynamic_Simulate_RedStartButton :\n                    loc.strings.MainForm_Simulate_UndoRedMoveButton;\n        }\n\n        public bool IsCardDropAllowed(CardViewModel sourceCard, object sourceContainer)\n        {\n            return true;\n        }\n\n        public void OnCardDragEnter(CardViewModel sourceCard, CardViewModel destCard)\n        {\n            destCard.DragImage = sourceCard.CardImage;\n            destCard.CardDragMode = ECardDragMode.DragIn;\n        }\n\n        public void OnCardDragLeave(CardViewModel sourceCard, CardViewModel destCard)\n        {\n            destCard.CardDragMode = ECardDragMode.None;\n        }\n\n        public void OnCardDrop(CardViewModel sourceCard, CardViewModel destCard, object sourceContainer)\n        {\n            destCard.CardDragMode = ECardDragMode.None;\n\n            var sourceDeck = sourceContainer as DeckViewModel;\n            if (sourceDeck == RedKnownCards || sourceDeck == RedUnknownCards)\n            {\n                MainWindow.GameModel.SetGameRedCard(sourceCard.CardModel.cardOb, destCard.OwnerIndex);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/viewmodel/SetupFavDeckViewModel.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Windows;\nusing System.Windows.Input;\n\nnamespace FFTriadBuddy.UI\n{\n    public class SetupFavDeckViewModel : LocalizedViewModel\n    {\n        private BulkObservableCollection<SolvableDeckViewModel> favDecks = new BulkObservableCollection<SolvableDeckViewModel>();\n        public BulkObservableCollection<SolvableDeckViewModel> FavDecks => favDecks;\n\n        public TriadGameModel gameModel;\n        public DeckViewModel activeDeck;\n\n        public ICommand CommandFavUse { get; private set; }\n        public ICommand CommandFavAssign { get; private set; }\n        public ICommand CommandFavCreate { get; private set; }\n        public ICommand CommandFavEdit { get; private set; }\n        public ICommand CommandFavRemove { get; private set; }\n\n        public SetupFavDeckViewModel()\n        {\n            CommandFavUse = new RelayCommand<SolvableDeckViewModel>(CommandFavUseFunc);\n            CommandFavAssign = new RelayCommand<SolvableDeckViewModel>((favDeck) => AssignFavDeckFrom(favDeck, activeDeck));\n            CommandFavEdit = new RelayCommand<SolvableDeckViewModel>(CommandFavEditFunc);\n            CommandFavRemove = new RelayCommand<SolvableDeckViewModel>(CommandFavRemoveFunc);\n            CommandFavCreate = new RelayCommand<object>(CommandFavCreateFunc);\n        }\n\n        public void Initialize()\n        {\n            var settingsDB = PlayerSettingsDB.Get();\n            var modelProxyDB = ModelProxyDB.Get();\n\n            favDecks.SuspendNotifies();\n\n            foreach (var favDeckOb in settingsDB.favDecks)\n            {\n                var deck = new CardCollectionViewModel() { Name = favDeckOb.Name };\n                foreach (var cardOb in favDeckOb.knownCards)\n                {\n                    var cardVM = new CardViewModel() { CardModel = modelProxyDB.GetCardProxy(cardOb) };\n                    deck.Cards.Add(cardVM);\n                }\n\n                var favDeck = new SolvableDeckViewModel() { Deck = deck };\n                favDecks.Add(favDeck);\n\n                favDeck.RefreshSolver(gameModel, favDeckOb);\n            }\n\n            favDecks.ResumeNotifies();\n        }\n\n        public override void RefreshLocalization()\n        {\n            // ignore base, there aren't any strings to localize directly in here\n            // base.RefreshLocalization();\n\n            foreach (var favDeck in favDecks)\n            {\n                favDeck.RefreshLocalization();\n            }\n        }\n\n        private void CommandFavUseFunc(SolvableDeckViewModel favDeck)\n        {\n            if (activeDeck.Cards.Count == favDeck.Deck.Cards.Count)\n            {\n                activeDeck.Cards.SuspendNotifies();\n                for (int idx = 0; idx < activeDeck.Cards.Count; idx++)\n                {\n                    activeDeck.Cards[idx].CardModel = favDeck.Deck.Cards[idx].CardModel;\n                    activeDeck.Cards[idx].IsShowingLock = false;\n                }\n\n                activeDeck.Cards.ResumeNotifies();\n                activeDeck.ForceCardsUpdate();\n            }\n        }\n\n        private void AssignFavDeckFrom(SolvableDeckViewModel favDeck, DeckViewModel sourceDeck)\n        {\n            if (sourceDeck.Cards.Count == favDeck.Deck.Cards.Count)\n            {\n                favDeck.Deck.Cards.SuspendNotifies();\n                var cards = new List<TriadCard>();\n\n                for (int idx = 0; idx < sourceDeck.Cards.Count; idx++)\n                {\n                    var cardProxy = sourceDeck.Cards[idx].CardModel;\n                    favDeck.Deck.Cards[idx].CardModel = cardProxy;\n                    cards.Add(cardProxy.cardOb);\n                }\n\n                favDeck.Deck.Cards.ResumeNotifies();\n\n                int slotIdx = favDecks.IndexOf(favDeck);\n                var namedDeck = new TriadDeckNamed(new TriadDeck(cards)) { Name = favDeck.Deck.Name };\n                PlayerSettingsDB.Get().UpdateFavDeck(slotIdx, namedDeck);\n\n                favDeck.RefreshSolver(gameModel, namedDeck);\n            }\n        }\n\n        private void CommandFavEditFunc(SolvableDeckViewModel favDeck)\n        {\n            var deckVM = new DeckViewModel() { Name = favDeck.Deck.Name };\n            foreach (var card in favDeck.Deck.Cards)\n            {\n                deckVM.Cards.Add(new CardViewModel() { CardModel = card.CardModel });\n            }\n\n            var editVM = new FavDeckEditViewModel() { FavDeck = deckVM };\n            var result = ViewModelServices.DialogWindow.ShowDialog(editVM);\n            if (result ?? false)\n            {\n                favDeck.Deck.Name = deckVM.Name;\n                AssignFavDeckFrom(favDeck, deckVM);\n            }\n        }\n\n        private void CommandFavRemoveFunc(SolvableDeckViewModel favDeck)\n        {\n            int slotIdx = favDecks.IndexOf(favDeck);\n            if (slotIdx >= 0)\n            {\n                var result = MessageBox.Show(loc.strings.FavDeckForm_Dynamic_RemoveMsg, loc.strings.App_Title, MessageBoxButton.YesNo, MessageBoxImage.Warning);\n                if (result == MessageBoxResult.Yes)\n                {\n                    PlayerSettingsDB.Get().UpdateFavDeck(slotIdx, null);\n\n                    favDecks.RemoveAt(slotIdx);\n                }\n            }\n        }\n\n        private void CommandFavCreateFunc(object dummyParam)\n        {\n            var deck = new CardCollectionViewModel();\n\n            for (int idx = 1; idx < 10000; idx++)\n            {\n                deck.Name = string.Format(loc.strings.FavDeckForm_Dynamic_AutoName, idx);\n\n                bool foundMatch = false;\n                foreach (var testFav in favDecks)\n                {\n                    foundMatch = testFav.Deck.Name == deck.Name;\n                    if (foundMatch)\n                    {\n                        break;\n                    }\n                }\n\n                if (!foundMatch)\n                {\n                    break;\n                }\n            }\n\n            for (int idx = 0; idx < activeDeck.Cards.Count; idx++)\n            {\n                deck.Cards.Add(new CardViewModel());\n            }\n\n            var favDeck = new SolvableDeckViewModel() { Deck = deck };\n            favDecks.Add(favDeck);\n\n            AssignFavDeckFrom(favDeck, activeDeck);\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/viewmodel/SimulateRulesViewModel.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Windows.Input;\n\nnamespace FFTriadBuddy.UI\n{\n    public class SimulateRulesViewModel : BaseViewModel\n    {\n        public enum Rule\n        {\n            None,\n            Roulette,\n            Random,\n            Swap,\n            XOpen,\n            Debug,\n        }\n\n        public PageSimulateViewModel OwnerVM;\n        public TriadGameModel GameModel;\n\n        public List<RuleModelProxy> Rules => ModelProxyDB.Get().Rules;\n\n        private ETriadGameSpecialMod activeRuleMask;\n        private Rule activeRule = Rule.None;\n        public Rule ActiveRule { get => activeRule; set { if (value != activeRule) { PropertySetAndNotify(value, ref activeRule); } } }\n\n        private RuleModelProxy roulette1;\n        public RuleModelProxy Roulette1 { get => roulette1; set => PropertySetAndNotify(value, ref roulette1); }\n\n        private RuleModelProxy roulette2;\n        public RuleModelProxy Roulette2 { get => roulette2; set => PropertySetAndNotify(value, ref roulette2); }\n\n        private RuleModelProxy roulette3;\n        public RuleModelProxy Roulette3 { get => roulette3; set => PropertySetAndNotify(value, ref roulette3); }\n\n        private RuleModelProxy roulette4;\n        public RuleModelProxy Roulette4 { get => roulette4; set => PropertySetAndNotify(value, ref roulette4); }\n\n        private int numRouletteChoices;\n        public int NumRouletteChoices { get => numRouletteChoices; set => PropertySetAndNotify(value, ref numRouletteChoices); }\n\n        public DeckViewModel OverrideBlueDeck { get; } = new DeckViewModel() { DeckOwner = ECardOwner.Blue };\n        public DeckViewModel OverrideRedDeck { get; } = new DeckViewModel() { DeckOwner = ECardOwner.Red };\n\n        private bool debugForcedCached;\n        public bool DebugForceCached { get => debugForcedCached; set => PropertySetAndNotify(value, ref debugForcedCached); }\n\n        private ICommand commandApply;\n        public ICommand CommandApply { get => commandApply; set => PropertySetAndNotify(value, ref commandApply); }\n        public ICommand CommandSkip { get; private set; }\n\n        private Dictionary<Rule, ICommand> MapCommandApply = new Dictionary<Rule, ICommand>();\n        private int NumXOpen = 5;\n\n        public SimulateRulesViewModel()\n        {\n            MapCommandApply.Add(Rule.None, new RelayCommand<object>((_) => MarkRuleResolved()));\n            MapCommandApply.Add(Rule.Debug, new RelayCommand<object>((_) => { ApplyRuleDebug(); MarkRuleResolved(); }));\n            MapCommandApply.Add(Rule.Roulette, new RelayCommand<object>((_) => { ApplyRuleRoulette(); MarkRuleResolved(); }));\n            MapCommandApply.Add(Rule.Random, new RelayCommand<object>((_) => { ApplyRuleRandom(); MarkRuleResolved(); }));\n            MapCommandApply.Add(Rule.Swap, new RelayCommand<object>((_) => { ApplyRuleSwap(); MarkRuleResolved(); }, (_) => CanApplyRuleSwap()));\n            MapCommandApply.Add(Rule.XOpen, new RelayCommand<object>((_) => { ApplyRuleXOpen(); MarkRuleResolved(); }, (_) => CanApplyRuleXOpen()));\n\n            CommandSkip = MapCommandApply[Rule.None];\n            CommandApply = MapCommandApply[Rule.None];\n        }\n\n        public void RequestRuleDebug()\n        {\n            activeRuleMask = ETriadGameSpecialMod.None;\n            ActiveRule = Rule.Debug;\n            CommandApply = MapCommandApply[ActiveRule];\n\n            OwnerVM.MainWindow.SwitchToPage(MainWindowViewModel.PageType.Simulate);\n            OwnerVM.GameModel_OnGameStateChanged(GameModel.GameState, null);\n        }\n\n        private void ApplyRuleDebug()\n        {\n            OwnerVM.MainWindow.PageScreenshot.RequestDebugScreenshot(DebugForceCached);\n            OwnerVM.MainWindow.SwitchToPage(MainWindowViewModel.PageType.Screenshot);\n        }\n\n        public void RequestRuleRoulette()\n        {\n            int numRules = 0;\n            foreach (var mod in GameModel.Solver.simulation.modifiers)\n            {\n                var rouletteModOb = mod as TriadGameModifierRoulette;\n                if (rouletteModOb != null)\n                {\n                    rouletteModOb.SetRuleInstance(null);\n                    numRules++;\n                }\n            }\n\n            var defaultRule = Rules.Find(x => x.modOb is TriadGameModifierNone);\n\n            NumRouletteChoices = numRules;\n            Roulette1 = defaultRule;\n            Roulette2 = defaultRule;\n            Roulette3 = defaultRule;\n            Roulette4 = defaultRule;\n\n            activeRuleMask = ETriadGameSpecialMod.RandomizeRule;\n            ActiveRule = Rule.Roulette;\n            CommandApply = MapCommandApply[ActiveRule];\n        }\n\n        private void ApplyRuleRoulette()\n        {\n            int readIdx = 0;\n            foreach (var mod in GameModel.Solver.simulation.modifiers)\n            {\n                var rouletteModOb = mod as TriadGameModifierRoulette;\n                if (rouletteModOb != null)\n                {\n                    switch (readIdx)\n                    {\n                        case 0: rouletteModOb.SetRuleInstance(Roulette1.modOb); break;\n                        case 1: rouletteModOb.SetRuleInstance(Roulette2.modOb); break;\n                        case 2: rouletteModOb.SetRuleInstance(Roulette3.modOb); break;\n                        case 3: rouletteModOb.SetRuleInstance(Roulette4.modOb); break;\n                        default: break;\n                    }\n\n                    readIdx++;\n                }\n            }\n\n            GameModel.GameRouletteApplied();\n        }\n\n        public void RequestRuleRandom()\n        {\n            var deckBlueOb = GameModel.GameState.deckBlue.deck;\n            SyncDeckVM(OverrideBlueDeck, deckBlueOb.knownCards, deckBlueOb.unknownCardPool);\n\n            activeRuleMask = ETriadGameSpecialMod.RandomizeBlueDeck;\n            ActiveRule = Rule.Random;\n            CommandApply = MapCommandApply[ActiveRule];\n        }\n\n        private void ApplyRuleRandom()\n        {\n            var updatedCards = new List<TriadCard>();\n            foreach (var card in OverrideBlueDeck.Cards)\n            {\n                updatedCards.Add(card.CardModel.cardOb);\n            }\n\n            GameModel.GameState.deckBlue = new TriadDeckInstanceManual(new TriadDeck(updatedCards));\n\n            GameModel.GameState.bDebugRules = true;\n            TriadGameModifierRandom.StaticRandomized(GameModel.GameState);\n            GameModel.GameState.bDebugRules = false;\n        }\n\n        public void RequestRuleSwap()\n        {\n            var deckBlueOb = GameModel.GameState.deckBlue.deck;\n            SyncDeckVM(OverrideBlueDeck, deckBlueOb.knownCards, deckBlueOb.unknownCardPool);\n            OverrideBlueDeck.NumToSelect = 1;\n\n            var deckRedOb = GameModel.GameState.deckRed.deck;\n            SyncDeckVM(OverrideRedDeck, deckRedOb.knownCards, deckRedOb.unknownCardPool);\n            OverrideRedDeck.NumToSelect = 1;\n\n            GameModel.OnDeckChanged += OnSwapDeckChanged;\n\n            activeRuleMask = ETriadGameSpecialMod.SwapCards;\n            ActiveRule = Rule.Swap;\n            CommandApply = MapCommandApply[ActiveRule];\n        }\n\n        private bool CanApplyRuleSwap()\n        {\n            return (OverrideBlueDeck.NumSelected > 0) && (OverrideRedDeck.NumSelected > 0);\n        }\n\n        private void ApplyRuleSwap()\n        {\n            GameModel.OnDeckChanged -= OnSwapDeckChanged;\n\n            int redIdx = OverrideRedDeck.SelectedIndices[0];\n            int blueIdx = OverrideBlueDeck.SelectedIndices[0];\n\n            var redCardOb = OverrideRedDeck.Cards[redIdx].CardModel.cardOb;\n            var blueCardOb = OverrideBlueDeck.Cards[blueIdx].CardModel.cardOb;\n\n            TriadGameModifierSwap.StaticSwapCards(GameModel.GameState, blueCardOb, blueIdx, redCardOb, redIdx);\n        }\n\n        private void OnSwapDeckChanged(TriadDeck deckBlueOb)\n        {\n            SyncDeckVM(OverrideBlueDeck, deckBlueOb.knownCards, deckBlueOb.unknownCardPool);\n            OverrideBlueDeck.NumToSelect = 1;\n        }\n\n        public void RequestRuleXOpen(int count)\n        {\n            activeRuleMask = ETriadGameSpecialMod.SelectVisible3 | ETriadGameSpecialMod.SelectVisible5;\n            if (PlayerSettingsDB.Get().skipOptionalSimulateRules)\n            {\n                MarkRuleResolved();\n                return;\n            }\n\n            NumXOpen = count;\n\n            // ignore when it's all open vs 5 cards total (e.g. tutorial npc)\n            var deckOb = GameModel.GameState.deckRed.deck;\n            int numCardsTotal = deckOb.knownCards.Count + deckOb.unknownCardPool.Count;\n            if (numCardsTotal == count)\n            {\n                NumXOpen = 0;\n                MarkRuleResolved();\n                return;\n            }\n\n            SyncDeckVM(OverrideRedDeck, deckOb.knownCards, deckOb.unknownCardPool);\n            OverrideRedDeck.NumToSelect = count;\n\n            if (NumXOpen == 5)\n            {\n                for (int idx = 0; idx < deckOb.knownCards.Count; idx++)\n                {\n                    OverrideRedDeck.CommandSelect.Execute(OverrideRedDeck.Cards[idx]);\n                }\n            }\n\n            ActiveRule = Rule.XOpen;\n            CommandApply = MapCommandApply[ActiveRule];\n        }\n\n        private bool CanApplyRuleXOpen()\n        {\n            return OverrideRedDeck.NumSelected == NumXOpen;\n        }\n\n        private void ApplyRuleXOpen()\n        {\n            TriadGameModifierAllOpen.StaticMakeKnown(GameModel.GameState, OverrideRedDeck.SelectedIndices);\n        }\n\n        private void MarkRuleResolved()\n        {\n            ActiveRule = Rule.None;\n            GameModel.ResolveSpecialRule(activeRuleMask);\n        }\n\n        private void SyncDeckVM(DeckViewModel deckVM, List<TriadCard> cardsA, List<TriadCard> cardsB)\n        {\n            int numCards = cardsA.Count + cardsB.Count;\n            while (deckVM.Cards.Count > numCards)\n            {\n                deckVM.Cards.RemoveAt(deckVM.Cards.Count - 1);\n            }\n\n            while (deckVM.Cards.Count < numCards)\n            {\n                deckVM.Cards.Add(new CardViewModel());\n            }\n\n            var modelProxyDB = ModelProxyDB.Get();\n            for (int idx = 0; idx < cardsA.Count; idx++)\n            {\n                deckVM.Cards[idx].CardModel = modelProxyDB.GetCardProxy(cardsA[idx]);\n            }\n            for (int idx = 0; idx < cardsB.Count; idx++)\n            {\n                deckVM.Cards[idx + cardsA.Count].CardModel = modelProxyDB.GetCardProxy(cardsB[idx]);\n            }\n\n            deckVM.SelectedIndices.Clear();\n            deckVM.NumToSelect = 0;\n\n            foreach (var card in deckVM.Cards)\n            {\n                card.IsHighlighted = false;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/viewmodel/SolvableDeckViewModel.cs",
    "content": "﻿using System;\nusing System.Windows.Threading;\n\nnamespace FFTriadBuddy.UI\n{\n    public class SolvableDeckViewModel : LocalizedViewModel\n    {\n        public CardCollectionViewModel Deck { get; set; }\n        public WinChanceViewModel WinChance { get; } = new WinChanceViewModel();\n        public FavDeckSolver solver;\n\n        private DispatcherTimer timer;\n        private TriadDeck cachedDeckModel;\n        private bool hasModelHook = false;\n\n        private bool isSolverRunning = false;\n        public bool IsSolverRunning { get => isSolverRunning; set => PropertySetAndNotify(value, ref isSolverRunning); }\n        public int SolverTaskId => solver.calcId;\n\n        public int Progress => solver.progress;\n        public string DescProbability => isSolverRunning ? \"...\" : WinChance.DescProbability;\n\n        private static int NextSolverId = 0;\n\n        public SolvableDeckViewModel()\n        {\n            solver = new FavDeckSolver() { contextId = NextSolverId };\n            solver.OnSolved += Solver_OnSolved;\n            NextSolverId++;\n        }\n\n        private void Solver_OnSolved(int id, TriadDeck deck, SolverResult chance)\n        {\n            IsSolverRunning = false;\n            timer?.Stop();\n\n            if (cachedDeckModel.Equals(deck))\n            {\n                WinChance.SetValue(chance);\n                OnPropertyChanged(\"DescProbability\");\n            }\n        }\n\n        public void InitializeFor(TriadGameModel gameModel)\n        {\n            if (!hasModelHook)\n            {\n                gameModel.OnSetupChanged += (model) => RefreshSolver(model, cachedDeckModel);\n                hasModelHook = true;\n            }\n        }\n\n        public void RefreshSolver(TriadGameModel gameModel, TriadDeck deck)\n        {\n            cachedDeckModel = deck;\n            InitializeFor(gameModel);\n\n            int lastCalcId = solver.calcId;\n            solver.Update(gameModel.Solver.simulation, gameModel.Npc);\n            solver.SetDeck(deck);\n\n            if (lastCalcId != solver.calcId)\n            {\n                IsSolverRunning = true;\n                timer?.Start();\n\n                OnPropertyChanged(\"DescProbability\");\n                OnPropertyChanged(\"Progress\");\n            }\n        }\n\n        public override void RefreshLocalization()\n        {\n            WinChance.RefreshLocalization();\n\n            base.RefreshLocalization();\n        }\n\n        public void EnableTrackingProgress()\n        {\n            timer = new DispatcherTimer(DispatcherPriority.Normal) { Interval = TimeSpan.FromSeconds(0.1) };\n            timer.Tick += ProgressUpdate_Tick;\n        }\n\n        private void ProgressUpdate_Tick(object sender, EventArgs e)\n        {\n            OnPropertyChanged(\"Progress\");\n        }\n    }\n}\n"
  },
  {
    "path": "sources/ui/viewmodel/ViewModelUtils.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.Drawing;\nusing System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Windows.Input;\n\nnamespace FFTriadBuddy.UI\n{\n    public abstract class BaseViewModel : INotifyPropertyChanged\n    {\n        public event PropertyChangedEventHandler PropertyChanged;\n\n        protected void OnPropertyChanged([CallerMemberName] string name = null)\n        {\n            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));\n        }\n\n        protected void PropertySetAndNotify<T>(T value, ref T property, [CallerMemberName] string name = null)\n        {\n            property = value;\n            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));\n        }\n    }\n\n    public abstract class LocalizedViewModel : BaseViewModel\n    {\n        public virtual void RefreshLocalization()\n        {\n            var allProps = GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);\n            foreach (var prop in allProps)\n            {\n                if (prop.PropertyType == typeof(string))\n                {\n                    OnPropertyChanged(prop.Name);\n                }\n            }\n        }\n    }\n\n    public class RelayCommand<T> : ICommand\n    {\n        private Action<T> execute = null;\n        private Predicate<T> canExecute = null;\n\n        public RelayCommand(Action<T> execute)\n        {\n            this.execute = execute;\n            this.canExecute = null;\n        }\n\n        public RelayCommand(Action<T> execute, Predicate<T> canExecute)\n        {\n            this.execute = execute;\n            this.canExecute = canExecute;\n        }\n\n        public event EventHandler CanExecuteChanged\n        {\n            add { CommandManager.RequerySuggested += value; }\n            remove { CommandManager.RequerySuggested -= value; }\n        }\n\n        public void RaiseCanExecuteChanged()\n        {\n            CommandManager.InvalidateRequerySuggested();\n        }\n\n        public bool CanExecute(object parameter)\n        {\n            return (canExecute != null) ? canExecute.Invoke((T)parameter) : true;\n        }\n\n        public void Execute(object parameter)\n        {\n            execute.Invoke((T)parameter);\n        }\n    }\n\n    public interface IDialogWindowViewModel\n    {\n        string GetDialogWindowTitle();\n\n        event Action<bool?> RequestDialogWindowClose;\n    }\n\n    public interface IOverlayWindowViewModel\n    {\n    }\n\n    public interface IDialogWindowService\n    {\n        bool? ShowDialog(IDialogWindowViewModel viewModel);\n    }\n\n    public interface IOverlayWindowService\n    {\n        void SetOverlayActive(IOverlayWindowViewModel viewModel, bool wantsActive);\n        bool IsCursorInside(Rectangle screenBounds);\n        void OnProcessingGameWindow(Rectangle gameWindowBounds, out bool invalidatedPosition);\n        Rectangle GetScreenBounds(Rectangle gameWindowBounds);\n    }\n\n    public interface IAppWindowService\n    {\n        void SetFontSize(float value);\n        void SetAlwaysOnTop(bool value);\n        void SetSoftwareRendering(bool value);\n    }\n\n    public class ViewModelServices\n    {\n        public static IDialogWindowService DialogWindow;\n        public static IOverlayWindowService OverlayWindow;\n        public static IAppWindowService AppWindow;\n    }\n}\n"
  },
  {
    "path": "sources/ui/viewmodel/WinChanceViewModel.cs",
    "content": "﻿namespace FFTriadBuddy.UI\n{\n    public class WinChanceViewModel : LocalizedViewModel\n    {\n        public bool isInvalid = true;\n        public SolverResult chance;\n\n        public bool IsDraw => chance.expectedResult == ETriadGameState.BlueDraw;\n        public bool IsWin => chance.expectedResult == ETriadGameState.BlueWins;\n        public float Probability => isInvalid ? 0.0f :\n            (chance.expectedResult == ETriadGameState.BlueWins) ? chance.winChance :\n            chance.drawChance;\n\n        public string DescProbability => isInvalid ? \"0%\" :\n            (chance.expectedResult == ETriadGameState.BlueWins) ? chance.winChance.ToString(\"P0\") :\n            string.Format(\"{0:P0} {1}\", chance.drawChance, loc.strings.MainForm_Dynamic_Simulate_ChanceIsDraw);\n\n        public void SetInvalid()\n        {\n            isInvalid = true;\n            NotifyProperties();\n        }\n\n        public void SetValue(SolverResult value)\n        {\n            chance = value;\n            isInvalid = false;\n            NotifyProperties();\n        }\n\n        private void NotifyProperties()\n        {\n            OnPropertyChanged(\"IsDraw\");\n            OnPropertyChanged(\"IsWin\");\n            OnPropertyChanged(\"Probability\");\n            OnPropertyChanged(\"DescProbability\");\n        }\n\n        public void CopyFrom(WinChanceViewModel other)\n        {\n            if (other.isInvalid)\n            {\n                SetInvalid();\n            }\n            else\n            {\n                SetValue(other.chance);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/utils/DataCoverter.cs",
    "content": "﻿#if DEBUG\n\nusing FFTriadBuddy.Datamine;\nusing MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\nusing System.Reflection;\n\nnamespace FFTriadBuddy\n{\n    class DataConverter\n    {\n        public void Run()\n        {\n            GameDataLists gameDataLists = new GameDataLists();\n            gameDataLists.Load(@\"..\\..\\..\\datasource\\export\\exd-all\\\");\n\n            bool result = true;\n            result = result && ValidateCardIds(gameDataLists);\n            result = result && ValidateNpcIds(gameDataLists);\n            result = result && gameDataLists.Link();\n            if (result)\n            {\n                var mapRuleByCodeName = BuildRuleNameMap();\n                var mapCardTypeByCodeName = BuildCardTypes();\n\n                result = result && ExportRuleNames(gameDataLists.rules, mapRuleByCodeName);\n                result = result && ExportCardTypes(gameDataLists.cardTypes, mapCardTypeByCodeName);\n                result = result && UpdateCards(gameDataLists, mapCardTypeByCodeName);\n                result = result && UpdateNpcs(gameDataLists, mapRuleByCodeName);\n                result = result && UpdateTournaments(gameDataLists, mapRuleByCodeName);\n            }\n\n            if (result)\n            {\n                LocalizationDB.Get().Save();\n                TriadCardDB.Get().Save();\n                TriadNpcDB.Get().Save();\n                TriadTournamentDB.Get().Save();\n            }\n\n            Logger.WriteLine(result ? \"Done.\" : \"Aborted\");\n        }\n\n        private Dictionary<string, TriadGameModifier> BuildRuleNameMap()\n        {\n            var ruleMap = new Dictionary<string, TriadGameModifier>();\n            foreach (Type type in Assembly.GetAssembly(typeof(TriadGameModifier)).GetTypes())\n            {\n                if (type.IsSubclassOf(typeof(TriadGameModifier)) && type != typeof(TriadGameModifierNone))\n                {\n                    TriadGameModifier modInst = (TriadGameModifier)Activator.CreateInstance(type);\n                    ruleMap.Add(modInst.GetCodeName(), modInst);\n                }\n            }\n\n            return ruleMap;\n        }\n\n        private bool ExportRuleNames(List<GameDataRule> gameDataRules, Dictionary<string, TriadGameModifier> ruleMap)\n        {\n            var ruleCodeNames = new List<string>();\n            foreach (var ruleInfo in gameDataRules)\n            {\n                ruleCodeNames.Add(ruleInfo.Name.GetCodeName());\n            }\n\n            LocalizationDB locDB = LocalizationDB.Get();\n            {\n                TriadGameModifierNone ruleNone = new TriadGameModifierNone();\n                locDB.LocRuleNames[ruleNone.GetLocalizationId()].Text[LocalizationDB.CodeLanguageIdx] = \"\";\n            }\n\n            foreach (var kvp in ruleMap)\n            {\n                var matchGameData = gameDataRules.Find(x => (x.Name.GetCodeName() == kvp.Key));\n                if (matchGameData == null)\n                {\n                    Logger.WriteLine(\"FAILED rule export, no match for: {0}\", kvp.Key);\n                    return false;\n                }\n\n                locDB.LocRuleNames[kvp.Value.GetLocalizationId()].Text = matchGameData.Name.Text;\n                ruleCodeNames.Remove(kvp.Key);\n            }\n\n            if (ruleCodeNames.Count > 0)\n            {\n                Logger.WriteLine(\"FAILED rule export, not assigned: {0}\", string.Join(\", \", ruleCodeNames));\n                return false;\n            }\n\n            return true;\n        }\n\n        private Dictionary<string, ETriadCardType> BuildCardTypes()\n        {\n            var typeMap = new Dictionary<string, ETriadCardType>();\n\n            LocalizationDB locDB = LocalizationDB.Get();\n            for (int idx = 0; idx < locDB.LocCardTypes.Count; idx++)\n            {\n                typeMap.Add(locDB.LocCardTypes[idx].GetCodeName(), (ETriadCardType)idx);\n            }\n\n            return typeMap;\n        }\n\n        private bool ExportCardTypes(List<GameDataCardType> gameDataCardTypes, Dictionary<string, ETriadCardType> typeMap)\n        {\n            var typeCodeNames = new List<string>();\n            foreach (var typeInfo in gameDataCardTypes)\n            {\n                typeCodeNames.Add(typeInfo.Type.GetCodeName());\n            }\n\n            LocalizationDB locDB = LocalizationDB.Get();\n            foreach (var kvp in typeMap)\n            {\n                if (kvp.Value == ETriadCardType.None)\n                {\n                    continue;\n                }\n\n                var matchGameData = gameDataCardTypes.Find(x => (x.Type.GetCodeName() == kvp.Key));\n                if (matchGameData == null)\n                {\n                    Logger.WriteLine(\"FAILED card type export, no match for: {0}\", kvp.Key);\n                    return false;\n                }\n\n                locDB.mapCardTypes[kvp.Value].Text = matchGameData.Type.Text;\n                typeCodeNames.Remove(kvp.Key);\n            }\n\n            if (typeCodeNames.Count > 0)\n            {\n                Logger.WriteLine(\"FAILED card type export, not assigned: {0}\", string.Join(\", \", typeCodeNames));\n                return false;\n            }\n\n            return true;\n        }\n\n        private bool UpdateCards(GameDataLists gameDataLists, Dictionary<string, ETriadCardType> mapCardTypes)\n        {\n            Logger.WriteLine(\"Updating card list...\");\n\n            TriadCardDB cardDB = TriadCardDB.Get();\n            foreach (var cardData in gameDataLists.cards)\n            {\n                TriadCard cardOb = (cardData.Id < cardDB.cards.Count) ? cardDB.cards[cardData.Id] : null;\n                if (cardOb != null && cardOb.IsValid())\n                {\n                    // ensure side numbers are the same\n                    if (cardOb.Sides[0] != cardData.sideTop ||\n                            cardOb.Sides[1] != cardData.sideLeft ||\n                            cardOb.Sides[2] != cardData.sideBottom ||\n                            cardOb.Sides[3] != cardData.sideRight)\n                    {\n                        Logger.WriteLine(\"FAILED card update, id:{0} name:{1} is not matching side numbers!\", cardData.Id, cardData.LinkedName.Name.GetCodeName());\n                        return false;\n                    }\n                }\n                else\n                {\n                    while (cardDB.cards.Count <= cardData.Id)\n                    {\n                        cardDB.cards.Add(null);\n                    }\n\n                    cardOb = new TriadCard(cardData.Id,\n                        (ETriadCardRarity)(cardData.rarityIdx - 1),\n                        mapCardTypes[cardData.LinkedType == null ? \"\" : cardData.LinkedType.Type.GetCodeName()],\n                        cardData.sideTop,\n                        cardData.sideBottom,\n                        cardData.sideLeft,\n                        cardData.sideRight,\n                        cardData.sortOrder,\n                        cardData.uiGroup);\n\n                    Logger.WriteLine(\">> adding new card: \" + cardOb.ToString());\n                    cardDB.cards[cardData.Id] = cardOb;\n                }\n\n                cardOb.Name.Text = cardData.LinkedName.Name.Text;\n            }\n\n            for (int idx = 0; idx < cardDB.cards.Count; idx++)\n            {\n                TriadCard cardOb = cardDB.cards[idx];\n                if (cardOb != null)\n                {\n                    var matchingCardData = gameDataLists.cards.Find(x => (x.Id == cardOb.Id));\n                    if (matchingCardData == null)\n                    {\n                        Logger.WriteLine(\">> removing card: \" + cardOb.ToString());\n                        cardDB.cards[idx] = null;\n                        continue;\n                    }\n\n                    if (cardOb.Id != idx)\n                    {\n                        Logger.WriteLine(\"FAILED card update, index mismatch for card[{0}].Id:{1}, Name:{2}\", idx, cardOb.Id, cardOb.Name.GetCodeName());\n                        return false;\n                    }\n                }\n            }\n\n            return true;\n        }\n\n        private bool UpdateNpcs(GameDataLists gameDataLists, Dictionary<string, TriadGameModifier> mapRuleNames)\n        {\n            Logger.WriteLine(\"Updating npc list...\");\n\n            TriadCardDB cardDB = TriadCardDB.Get();\n            TriadNpcDB npcDB = TriadNpcDB.Get();\n            var validDeckIds = new List<string>();\n\n            foreach (var npcData in gameDataLists.npcs)\n            {\n                if (npcData.LinkedNpcId == null)\n                {\n                    continue;\n                }\n\n                if (npcData.LinkedNpcId.LinkedName == null || npcData.LinkedNpcId.LinkedLocation == null)\n                {\n                    continue;\n                }\n\n                TriadDeck npcDataDeck = new TriadDeck();\n                foreach (var cardData in npcData.LinkedCardsFixed)\n                {\n                    npcDataDeck.knownCards.Add(cardDB.cards[cardData.Id]);\n                }\n                foreach (var cardData in npcData.LinkedCardsVariable)\n                {\n                    npcDataDeck.unknownCardPool.Add(cardDB.cards[cardData.Id]);\n                }\n                npcDataDeck.UpdateDeckId();\n                validDeckIds.Add(npcDataDeck.deckId);\n\n                // mistakes were made...\n                TriadNpc npcOb = npcDB.FindByDeckId(npcDataDeck.deckId);\n                int npcId = (npcOb == null) ? npcDB.npcs.Count : npcOb.Id;\n\n                if (npcOb != null)\n                {\n                    // ensure decks are the same\n                    if (!npcOb.Deck.Equals(npcDataDeck))\n                    {\n                        Logger.WriteLine(\"FAILED npc update, id:{0} name:{1} is not matching cards!\", npcId, npcData.LinkedNpcId.LinkedName.Name.GetCodeName());\n                        return false;\n                    }\n                }\n                else\n                {\n                    while (npcDB.npcs.Count <= npcId)\n                    {\n                        npcDB.npcs.Add(null);\n                    }\n\n                    var listMods = new List<TriadGameModifier>();\n                    foreach (var ruleData in npcData.LinkedRules)\n                    {\n                        listMods.Add(mapRuleNames[ruleData.Name.GetCodeName()]);\n                    }\n\n                    var listRewards = new List<TriadCard>();\n                    foreach (var rewardData in npcData.LinkedRewards)\n                    {\n                        listRewards.Add(cardDB.cards[rewardData.Id]);\n                    }\n\n                    npcOb = new TriadNpc(npcId, listMods, listRewards, npcDataDeck);\n\n                    Logger.WriteLine(\">> adding new npc: \" + npcOb.ToString());\n                    npcDB.npcs[npcId] = npcOb;\n                }\n\n                var linkedLoc = npcData.LinkedNpcId.LinkedLocation;\n                linkedLoc.LinkedMap.GetCoords(linkedLoc.ScaledPosX, linkedLoc.ScaledPosZ, out npcOb.LocationX, out npcOb.LocationY);\n\n                npcOb.Name.Text = npcData.LinkedNpcId.LinkedName.Name.Text;\n                npcOb.LocationMap.Text = npcData.LinkedNpcId.LinkedLocation.LinkedMap.LinkedPlace.Name.Text;\n            }\n\n            for (int idx = 0; idx < npcDB.npcs.Count; idx++)\n            {\n                TriadNpc npcOb = npcDB.npcs[idx];\n                if (npcOb != null)\n                {\n                    if (!validDeckIds.Contains(npcOb.Deck.deckId))\n                    {\n                        Logger.WriteLine(\">> removing npc: \" + npcOb.ToString() + \", deck:\" + npcOb.Deck.ToString());\n                        npcDB.npcs[idx] = null;\n                        continue;\n                    }\n\n                    if (npcOb.Id != idx)\n                    {\n                        Logger.WriteLine(\"FAILED npc update, index mismatch for npc[{0}].Id:{1}, Name:{2}\", idx, npcOb.Id, npcOb.Name.GetCodeName());\n                        return false;\n                    }\n                }\n            }\n\n            return true;\n        }\n\n        private bool UpdateTournaments(GameDataLists gameDataLists, Dictionary<string, TriadGameModifier> mapRuleNames)\n        {\n            Logger.WriteLine(\"Updating tournament list...\");\n\n            // TODO: not sure how to find it in .csv data,\n            // hardcode entries + rules for now\n            // alert new new unique entry appears\n\n            var uniqueTournamentNames = new List<string>();\n            var uniqueTournamentLocNames = new List<LocString>();\n            foreach (var tourData in gameDataLists.tournamentNames)\n            {\n                var codeName = tourData.Name.GetCodeName();\n                if (!string.IsNullOrEmpty(codeName))\n                {\n                    if (!uniqueTournamentNames.Contains(codeName))\n                    {\n                        uniqueTournamentNames.Add(codeName);\n                        uniqueTournamentLocNames.Add(tourData.Name);\n                    }\n                }\n            }\n\n            string[] hardcodedNames = { \"the Manderville Tournament of Champions\", \"the Spinner's Pull\", \"the Durai Memorial\", \"the Rowena Cup Classic\" };\n            string[] hardcodedRules =\n            {\n                \"All Open\", \"Plus\",\n                \"Three Open\", \"Swap\",\n                \"Order\", \"Same\",\n                \"Roulette\", \"Roulette\",\n            };\n\n            if (uniqueTournamentNames.Count != hardcodedNames.Length)\n            {\n                Logger.WriteLine(\"FAILED tournament update, hardcoded list diff! [{0}]\", string.Join(\", \", uniqueTournamentNames));\n                return false;\n            }\n\n            TriadTournamentDB tournamentDB = TriadTournamentDB.Get();\n            for (int idx = 0; idx < uniqueTournamentNames.Count; idx++)\n            {\n                if (uniqueTournamentNames[idx] != hardcodedNames[idx])\n                {\n                    Logger.WriteLine(\"FAILED tournament update, id:{0} mismatch!\", idx);\n                    return false;\n                }\n\n                TriadTournament tourOb = (idx < tournamentDB.tournaments.Count) ? tournamentDB.tournaments[idx] : null;\n                if (tourOb == null)\n                {\n                    while (tournamentDB.tournaments.Count <= idx)\n                    {\n                        tournamentDB.tournaments.Add(new TriadTournament(idx, new List<TriadGameModifier>()));\n                    }\n\n                    tourOb = tournamentDB.tournaments[idx];\n                }\n\n                tourOb.Name.Text = uniqueTournamentLocNames[idx].Text;\n\n                tourOb.Rules.Clear();\n                int ruleStartIdx = idx * 2;\n                for (int ruleIdx = 0; ruleIdx < 2; ruleIdx++)\n                {\n                    tourOb.Rules.Add(mapRuleNames[hardcodedRules[ruleIdx + ruleStartIdx]]);\n                }\n            }\n\n            return true;\n        }\n\n        private bool ValidateCardIds(GameDataLists gameDataLists)\n        {\n            foreach (var cardData in gameDataLists.cards)\n            {\n                if (cardData.locSides.Count > 0)\n                {\n                    foreach (var kvp in cardData.locSides)\n                    {\n                        bool isMatch = cardData.HasMatchingLocSide(kvp.Value);\n                        if (!isMatch)\n                        {\n                            Logger.WriteLine(\"FAILED to validate card - Id:{0} [{1}, {2}, {3}, {4}] vs {5}:[{6}, {7}, {8}, {9}]\",\n                                cardData.Id, cardData.sideTop, cardData.sideLeft, cardData.sideBottom, cardData.sideRight,\n                                kvp.Key, kvp.Value[0], kvp.Value[1], kvp.Value[2], kvp.Value[3]);\n\n                            return false;\n                        }\n                    }\n                }\n            }\n\n            return true;\n        }\n\n        private bool ValidateNpcIds(GameDataLists gameDataLists)\n        {\n            foreach (var npcData in gameDataLists.npcs)\n            {\n                if (npcData.locCards.Count > 0)\n                {\n                    foreach (var kvp in npcData.locCards)\n                    {\n                        bool isMatch = npcData.HasMatchingCards(kvp.Value);\n                        if (!isMatch)\n                        {\n                            Logger.WriteLine(\"FAILED to validate npc - Id:{0} [{1}] + [{2}] vs {3}:[{4}]\",\n                                npcData.Id, string.Join(\", \", npcData.CardsFixed), string.Join(\", \", npcData.CardsVariable),\n                                kvp.Key, string.Join(\", \", kvp.Value));\n\n                            return false;\n                        }\n                    }\n                }\n            }\n\n            return true;\n        }\n    }\n}\n\n#endif // DEBUG\n"
  },
  {
    "path": "sources/utils/TestManager.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\n\nnamespace FFTriadBuddy\n{\n    public class TestManager\n    {\n#if DEBUG\n        private static bool exportDetectionPatterns;\n\n        public static void RunTests()\n        {\n            // ML run?\n            exportDetectionPatterns = false;\n\n            ScreenAnalyzer.EMode testMode = ScreenAnalyzer.EMode.AlwaysResetCache | ScreenAnalyzer.EMode.DebugScreenshotOnly | ScreenAnalyzer.EMode.AutoTest;\n\n            RunTests(\"test/auto/cactpot\", testMode | ScreenAnalyzer.EMode.ScanCactpot);\n            RunTests(\"test/auto/triad\", testMode | ScreenAnalyzer.EMode.ScanTriad);\n            RunTriadSolverTests(\"test/auto/triad-solver\");\n        }\n\n        public static void RunTests(string path, ScreenAnalyzer.EMode mode)\n        {\n            ScreenAnalyzer screenAnalyzer = new ScreenAnalyzer();\n            MLDataExporter dataExporter = null;\n\n            ScannerBase scannerOb = null;\n            foreach (var kvp in screenAnalyzer.mapScanners)\n            {\n                if ((kvp.Key & mode) != ScreenAnalyzer.EMode.None)\n                {\n                    scannerOb = kvp.Value;\n                    break;\n                }\n            }\n\n            if (scannerOb == null)\n            {\n                throw new Exception(\"Test failed! Can't find scanner for requested type:\" + mode);\n            }\n\n            if (exportDetectionPatterns)\n            {\n                dataExporter = new MLDataExporter();\n                dataExporter.exportPath = AssetManager.Get().CreateFilePath(\"ml/patternMatch/data\");\n                dataExporter.StartDataExport((mode & ScreenAnalyzer.EMode.ScanAll).ToString());\n            }\n\n            string testRoot = AssetManager.Get().CreateFilePath(path);\n            IEnumerable<string> configPaths = Directory.EnumerateFiles(testRoot, \"*.json\");\n            foreach (var configPath in configPaths)\n            {\n                string imagePath = configPath.Replace(\".json\", \".jpg\");\n                if (!File.Exists(imagePath))\n                {\n                    imagePath = imagePath.Replace(\".jpg\", \".png\");\n                }\n\n                if (File.Exists(imagePath))\n                {\n                    Logger.WriteLine(\"==> Testing: \" + Path.GetFileNameWithoutExtension(configPath));\n\n                    bool bNeedsDebugRun = false;\n                    screenAnalyzer.debugScreenshotPath = imagePath;\n                    screenAnalyzer.debugScannerContext = null;\n\n                    try\n                    {\n                        screenAnalyzer.DoWork(mode);\n                        scannerOb.ValidateScan(configPath, mode, dataExporter);\n                    }\n                    catch (Exception ex)\n                    {\n                        Logger.WriteLine(\"Exception:\" + ex);\n                        bNeedsDebugRun = true;\n                    }\n\n                    // retry, don't catch exceptions\n                    if (bNeedsDebugRun && !exportDetectionPatterns)\n                    {\n                        screenAnalyzer.DoWork(mode | ScreenAnalyzer.EMode.Debug);\n                        scannerOb.ValidateScan(configPath, mode | ScreenAnalyzer.EMode.Debug, null);\n                    }\n                }\n            }\n\n            if (exportDetectionPatterns)\n            {\n                dataExporter.FinishDataExport(\"ml-\" + Path.GetFileNameWithoutExtension(path) + \".json\");\n            }\n        }\n\n        public static void RunTriadSolverTests(string path)\n        {\n            string testRoot = AssetManager.Get().CreateFilePath(path);\n            IEnumerable<string> configPaths = Directory.EnumerateFiles(testRoot, \"*.json\");\n            foreach (var configPath in configPaths)\n            {\n                Logger.WriteLine(\"==> Testing: \" + Path.GetFileNameWithoutExtension(configPath));\n                bool bNeedsDebugRun = false;\n\n                try\n                {\n                    TriadGameScreenTests.RunTest(configPath, bNeedsDebugRun);\n                    TriadGameTests.RunTest(configPath, bNeedsDebugRun);\n                }\n                catch (Exception ex)\n                {\n                    Logger.WriteLine(\"Exception:\" + ex);\n                    bNeedsDebugRun = true;\n                }\n\n                if (bNeedsDebugRun)\n                {\n                    TriadGameScreenTests.RunTest(configPath, bNeedsDebugRun);\n                    TriadGameTests.RunTest(configPath, bNeedsDebugRun);\n                }\n            }\n        }\n#endif // DEBUG\n    }\n}\n"
  },
  {
    "path": "sources/utils/XInputStub.cs",
    "content": "﻿using System;\nusing System.Runtime.InteropServices;\nusing System.Threading;\n\nnamespace FFTriadBuddy\n{\n    // https://www.codeproject.com/Articles/492473/Using-XInput-to-access-an-Xbox-360-Controller-in-M\n\n    class XInputStub\n    {\n        int _playerIndex;\n        static bool keepRunning;\n        static int updateFrequency;\n        static int waitTime;\n        static bool isRunning;\n        static object SyncLock;\n        static Thread pollingThread;\n\n        bool _stopMotorTimerActive;\n        DateTime _stopMotorTime;\n        XInputBatteryInformation _batteryInformationGamepad;\n        XInputBatteryInformation _batterInformationHeadset;\n        //XInputCapabilities _capabilities;\n\n        XInputState gamepadStatePrev = new XInputState();\n        XInputState gamepadStateCurrent = new XInputState();\n\n        private bool _eventMotion = false;\n\n        public delegate void XInputEventMotionTrigger();\n        public static event XInputEventMotionTrigger OnEventMotionTrigger;\n\n        public static int UpdateFrequency\n        {\n            get { return updateFrequency; }\n            set\n            {\n                updateFrequency = value;\n                waitTime = 1000 / updateFrequency;\n            }\n        }\n\n        public XInputBatteryInformation BatteryInformationGamepad\n        {\n            get { return _batteryInformationGamepad; }\n            internal set { _batteryInformationGamepad = value; }\n        }\n\n        public XInputBatteryInformation BatteryInformationHeadset\n        {\n            get { return _batterInformationHeadset; }\n            internal set { _batterInformationHeadset = value; }\n        }\n\n        public const int MAX_CONTROLLER_COUNT = 4;\n        public const int FIRST_CONTROLLER_INDEX = 0;\n        public const int LAST_CONTROLLER_INDEX = MAX_CONTROLLER_COUNT - 1;\n\n        static XInputStub[] Controllers;\n\n\n        static XInputStub()\n        {\n            Controllers = new XInputStub[MAX_CONTROLLER_COUNT];\n            SyncLock = new object();\n            for (int i = FIRST_CONTROLLER_INDEX; i <= LAST_CONTROLLER_INDEX; ++i)\n            {\n                Controllers[i] = new XInputStub(i);\n            }\n            UpdateFrequency = 25;\n        }\n\n        public event EventHandler<XboxControllerStateChangedEventArgs> StateChanged = null;\n\n        public static XInputStub RetrieveController(int index)\n        {\n            return Controllers[index];\n        }\n\n        private XInputStub(int playerIndex)\n        {\n            _playerIndex = playerIndex;\n            gamepadStatePrev.Copy(gamepadStateCurrent);\n        }\n\n        public void UpdateBatteryState()\n        {\n            XInputBatteryInformation headset = new XInputBatteryInformation(),\n            gamepad = new XInputBatteryInformation();\n\n            try\n            {\n                XInputNative.XInputGetBatteryInformation(_playerIndex, (byte)BatteryDeviceType.BATTERY_DEVTYPE_GAMEPAD, ref gamepad);\n                XInputNative.XInputGetBatteryInformation(_playerIndex, (byte)BatteryDeviceType.BATTERY_DEVTYPE_HEADSET, ref headset);\n            }\n            catch (Exception)\n            {\n                // import errors\n            }\n\n            BatteryInformationHeadset = headset;\n            BatteryInformationGamepad = gamepad;\n        }\n\n        protected void OnStateChanged()\n        {\n            if (StateChanged != null)\n                StateChanged(this, new XboxControllerStateChangedEventArgs() { CurrentInputState = gamepadStateCurrent, PreviousInputState = gamepadStatePrev });\n        }\n\n        public XInputCapabilities GetCapabilities()\n        {\n            XInputCapabilities capabilities = new XInputCapabilities();\n            try\n            {\n                XInputNative.XInputGetCapabilities(_playerIndex, XInputConstants.XINPUT_FLAG_GAMEPAD, ref capabilities);\n            }\n            catch (Exception)\n            {\n                // import errors\n            }\n\n            return capabilities;\n        }\n\n\n        #region Digital Button States\n        public bool IsDPadUpPressed\n        {\n            get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_DPAD_UP); }\n        }\n\n        public bool IsDPadDownPressed\n        {\n            get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_DPAD_DOWN); }\n        }\n\n        public bool IsDPadLeftPressed\n        {\n            get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_DPAD_LEFT); }\n        }\n\n        public bool IsDPadRightPressed\n        {\n            get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_DPAD_RIGHT); }\n        }\n\n        public bool IsAPressed\n        {\n            get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_A); }\n        }\n\n        public bool IsBPressed\n        {\n            get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_B); }\n        }\n\n        public bool IsXPressed\n        {\n            get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_X); }\n        }\n\n        public bool IsYPressed\n        {\n            get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_Y); }\n        }\n\n\n        public bool IsBackPressed\n        {\n            get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_BACK); }\n        }\n\n\n        public bool IsStartPressed\n        {\n            get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_START); }\n        }\n\n\n        public bool IsLeftShoulderPressed\n        {\n            get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_LEFT_SHOULDER); }\n        }\n\n\n        public bool IsRightShoulderPressed\n        {\n            get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_RIGHT_SHOULDER); }\n        }\n\n        public bool IsLeftStickPressed\n        {\n            get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_LEFT_THUMB); }\n        }\n\n        public bool IsRightStickPressed\n        {\n            get { return gamepadStateCurrent.Gamepad.IsButtonPressed((int)ButtonFlags.XINPUT_GAMEPAD_RIGHT_THUMB); }\n        }\n        #endregion\n\n        #region Analogue Input States\n        public int LeftTrigger\n        {\n            get { return (int)gamepadStateCurrent.Gamepad.bLeftTrigger; }\n        }\n\n        public int RightTrigger\n        {\n            get { return (int)gamepadStateCurrent.Gamepad.bRightTrigger; }\n        }\n\n        public XInputPoint LeftThumbStick\n        {\n            get\n            {\n                XInputPoint p = new XInputPoint()\n                {\n                    X = gamepadStateCurrent.Gamepad.sThumbLX,\n                    Y = gamepadStateCurrent.Gamepad.sThumbLY\n                };\n                return p;\n            }\n        }\n\n        public XInputPoint RightThumbStick\n        {\n            get\n            {\n                XInputPoint p = new XInputPoint()\n                {\n                    X = gamepadStateCurrent.Gamepad.sThumbRX,\n                    Y = gamepadStateCurrent.Gamepad.sThumbRY\n                };\n                return p;\n            }\n        }\n\n        #endregion\n\n        bool _isConnected;\n        public bool IsConnected\n        {\n            get { return _isConnected; }\n            internal set { _isConnected = value; }\n        }\n\n        #region Polling\n        public static void StartPolling()\n        {\n            if (!isRunning)\n            {\n                lock (SyncLock)\n                {\n                    if (!isRunning)\n                    {\n                        pollingThread = new Thread(PollerLoop);\n                        pollingThread.Start();\n                    }\n                }\n            }\n        }\n\n        public static void StopPolling()\n        {\n            if (isRunning)\n                keepRunning = false;\n        }\n\n        static void PollerLoop()\n        {\n            lock (SyncLock)\n            {\n                if (isRunning == true)\n                    return;\n                isRunning = true;\n            }\n            keepRunning = true;\n            while (keepRunning)\n            {\n                for (int i = FIRST_CONTROLLER_INDEX; i <= LAST_CONTROLLER_INDEX; ++i)\n                {\n                    bool canRun = Controllers[i].UpdateState();\n                    keepRunning = keepRunning && canRun;\n                }\n                Thread.Sleep(updateFrequency);\n            }\n            lock (SyncLock)\n            {\n                isRunning = false;\n            }\n        }\n\n        public bool UpdateState()\n        {\n            bool canRun = true;\n\n            int result = 0;\n            try\n            {\n                XInputNative.XInputGetState(_playerIndex, ref gamepadStateCurrent);\n            }\n            catch (Exception)\n            {\n                canRun = false;\n            }\n\n            IsConnected = (result == 0);\n\n            UpdateBatteryState();\n            if (gamepadStateCurrent.PacketNumber != gamepadStatePrev.PacketNumber)\n            {\n                OnStateChanged();\n            }\n            gamepadStatePrev.Copy(gamepadStateCurrent);\n\n            if (_stopMotorTimerActive && (DateTime.Now >= _stopMotorTime))\n            {\n                try\n                {\n                    XInputVibration stopStrength = new XInputVibration() { LeftMotorSpeed = 0, RightMotorSpeed = 0 };\n                    XInputNative.XInputSetState(_playerIndex, ref stopStrength);\n                }\n                catch (Exception)\n                {\n                    canRun = false;\n                }\n            }\n\n            // event motion check\n            if (_eventMotion)\n            {\n                if (gamepadStateCurrent.Gamepad.bLeftTrigger < 20)\n                    _eventMotion = false;\n            }\n            else\n            {\n                if (gamepadStateCurrent.Gamepad.bLeftTrigger > 190 &&\n                    gamepadStateCurrent.Gamepad.bRightTrigger > 190)\n                {\n                    _eventMotion = true;\n\n                    OnEventMotionTrigger?.Invoke();\n                }\n            }\n\n            return canRun;\n        }\n        #endregion\n\n        #region Motor Functions\n        public void Vibrate(double leftMotor, double rightMotor)\n        {\n            Vibrate(leftMotor, rightMotor, TimeSpan.MinValue);\n        }\n\n        public void Vibrate(double leftMotor, double rightMotor, TimeSpan length)\n        {\n            leftMotor = Math.Max(0d, Math.Min(1d, leftMotor));\n            rightMotor = Math.Max(0d, Math.Min(1d, rightMotor));\n\n            XInputVibration vibration = new XInputVibration() { LeftMotorSpeed = (ushort)(65535d * leftMotor), RightMotorSpeed = (ushort)(65535d * rightMotor) };\n            Vibrate(vibration, length);\n        }\n\n\n        public void Vibrate(XInputVibration strength)\n        {\n            _stopMotorTimerActive = false;\n            try\n            {\n                XInputNative.XInputSetState(_playerIndex, ref strength);\n            }\n            catch (Exception)\n            {\n                // import errors\n            }\n        }\n\n        public void Vibrate(XInputVibration strength, TimeSpan length)\n        {\n            try\n            {\n                XInputNative.XInputSetState(_playerIndex, ref strength);\n            }\n            catch (Exception)\n            {\n                // import errors\n            }\n\n            if (length != TimeSpan.MinValue)\n            {\n                _stopMotorTime = DateTime.Now.Add(length);\n                _stopMotorTimerActive = true;\n            }\n        }\n        #endregion\n\n        public override string ToString()\n        {\n            return _playerIndex.ToString();\n        }\n    }\n\n    [StructLayout(LayoutKind.Explicit)]\n    public struct XInputGamepad\n    {\n        [MarshalAs(UnmanagedType.I2)]\n        [FieldOffset(0)]\n        public short wButtons;\n\n        [MarshalAs(UnmanagedType.I1)]\n        [FieldOffset(2)]\n        public byte bLeftTrigger;\n\n        [MarshalAs(UnmanagedType.I1)]\n        [FieldOffset(3)]\n        public byte bRightTrigger;\n\n        [MarshalAs(UnmanagedType.I2)]\n        [FieldOffset(4)]\n        public short sThumbLX;\n\n        [MarshalAs(UnmanagedType.I2)]\n        [FieldOffset(6)]\n        public short sThumbLY;\n\n        [MarshalAs(UnmanagedType.I2)]\n        [FieldOffset(8)]\n        public short sThumbRX;\n\n        [MarshalAs(UnmanagedType.I2)]\n        [FieldOffset(10)]\n        public short sThumbRY;\n\n        public bool IsButtonPressed(int buttonFlags)\n        {\n            return (wButtons & buttonFlags) == buttonFlags;\n        }\n\n        public bool IsButtonPresent(int buttonFlags)\n        {\n            return (wButtons & buttonFlags) == buttonFlags;\n        }\n\n        public void Copy(XInputGamepad source)\n        {\n            sThumbLX = source.sThumbLX;\n            sThumbLY = source.sThumbLY;\n            sThumbRX = source.sThumbRX;\n            sThumbRY = source.sThumbRY;\n            bLeftTrigger = source.bLeftTrigger;\n            bRightTrigger = source.bRightTrigger;\n            wButtons = source.wButtons;\n        }\n\n        public override bool Equals(object obj)\n        {\n            if (!(obj is XInputGamepad))\n                return false;\n            XInputGamepad source = (XInputGamepad)obj;\n            return ((sThumbLX == source.sThumbLX)\n            && (sThumbLY == source.sThumbLY)\n            && (sThumbRX == source.sThumbRX)\n            && (sThumbRY == source.sThumbRY)\n            && (bLeftTrigger == source.bLeftTrigger)\n            && (bRightTrigger == source.bRightTrigger)\n            && (wButtons == source.wButtons));\n        }\n\n        public override int GetHashCode()\n        {\n            return base.GetHashCode();\n        }\n    }\n\n    [StructLayout(LayoutKind.Sequential)]\n    public struct XInputVibration\n    {\n        [MarshalAs(UnmanagedType.I2)]\n        public ushort LeftMotorSpeed;\n\n        [MarshalAs(UnmanagedType.I2)]\n        public ushort RightMotorSpeed;\n    }\n\n    [StructLayout(LayoutKind.Explicit)]\n    public struct XInputState\n    {\n        [FieldOffset(0)]\n        public int PacketNumber;\n\n        [FieldOffset(4)]\n        public XInputGamepad Gamepad;\n\n        public void Copy(XInputState source)\n        {\n            PacketNumber = source.PacketNumber;\n            Gamepad.Copy(source.Gamepad);\n        }\n\n        public override bool Equals(object obj)\n        {\n            if ((obj == null) || (!(obj is XInputState)))\n                return false;\n            XInputState source = (XInputState)obj;\n\n            return ((PacketNumber == source.PacketNumber)\n                && (Gamepad.Equals(source.Gamepad)));\n        }\n\n        public override int GetHashCode()\n        {\n            return base.GetHashCode();\n        }\n    }\n\n    [StructLayout(LayoutKind.Explicit)]\n    public struct XInputCapabilities\n    {\n        [MarshalAs(UnmanagedType.I1)]\n        [FieldOffset(0)]\n        byte Type;\n\n        [MarshalAs(UnmanagedType.I1)]\n        [FieldOffset(1)]\n        public byte SubType;\n\n        [MarshalAs(UnmanagedType.I2)]\n        [FieldOffset(2)]\n        public short Flags;\n\n        [FieldOffset(4)]\n        public XInputGamepad Gamepad;\n\n        [FieldOffset(16)]\n        public XInputVibration Vibration;\n    }\n\n    [StructLayout(LayoutKind.Explicit)]\n    public struct XInputBatteryInformation\n    {\n        [MarshalAs(UnmanagedType.I1)]\n        [FieldOffset(0)]\n        public byte BatteryType;\n\n        [MarshalAs(UnmanagedType.I1)]\n        [FieldOffset(1)]\n        public byte BatteryLevel;\n    }\n\n    public class XInputNative\n    {\n        [DllImport(\"xinput1_4.dll\")]\n        public static extern int XInputGetState\n        (\n            int dwUserIndex,  // [in] Index of the gamer associated with the device\n            ref XInputState pState        // [out] Receives the current state\n        );\n\n        [DllImport(\"xinput1_4.dll\")]\n        public static extern int XInputSetState\n        (\n            int dwUserIndex,  // [in] Index of the gamer associated with the device\n            ref XInputVibration pVibration    // [in, out] The vibration information to send to the controller\n        );\n\n        [DllImport(\"xinput1_4.dll\")]\n        public static extern int XInputGetCapabilities\n        (\n            int dwUserIndex,   // [in] Index of the gamer associated with the device\n            int dwFlags,       // [in] Input flags that identify the device type\n            ref XInputCapabilities pCapabilities  // [out] Receives the capabilities\n        );\n\n\n        [DllImport(\"xinput1_4.dll\")]\n        public static extern int XInputGetBatteryInformation\n        (\n              int dwUserIndex,        // Index of the gamer associated with the device\n              byte devType,            // Which device on this user index\n            ref XInputBatteryInformation pBatteryInformation // Contains the level and types of batteries\n        );\n    }\n\n    public class XboxControllerStateChangedEventArgs : EventArgs\n    {\n        public XInputState CurrentInputState { get; set; }\n        public XInputState PreviousInputState { get; set; }\n    }\n\n    [Flags]\n    public enum ButtonFlags : int\n    {\n        XINPUT_GAMEPAD_DPAD_UP = 0x0001,\n        XINPUT_GAMEPAD_DPAD_DOWN = 0x0002,\n        XINPUT_GAMEPAD_DPAD_LEFT = 0x0004,\n        XINPUT_GAMEPAD_DPAD_RIGHT = 0x0008,\n        XINPUT_GAMEPAD_START = 0x0010,\n        XINPUT_GAMEPAD_BACK = 0x0020,\n        XINPUT_GAMEPAD_LEFT_THUMB = 0x0040,\n        XINPUT_GAMEPAD_RIGHT_THUMB = 0x0080,\n        XINPUT_GAMEPAD_LEFT_SHOULDER = 0x0100,\n        XINPUT_GAMEPAD_RIGHT_SHOULDER = 0x0200,\n        XINPUT_GAMEPAD_A = 0x1000,\n        XINPUT_GAMEPAD_B = 0x2000,\n        XINPUT_GAMEPAD_X = 0x4000,\n        XINPUT_GAMEPAD_Y = 0x8000,\n    };\n\n    [Flags]\n    public enum ControllerSubtypes\n    {\n        XINPUT_DEVSUBTYPE_UNKNOWN = 0x00,\n        XINPUT_DEVSUBTYPE_WHEEL = 0x02,\n        XINPUT_DEVSUBTYPE_ARCADE_STICK = 0x03,\n        XINPUT_DEVSUBTYPE_FLIGHT_STICK = 0x04,\n        XINPUT_DEVSUBTYPE_DANCE_PAD = 0x05,\n        XINPUT_DEVSUBTYPE_GUITAR = 0x06,\n        XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE = 0x07,\n        XINPUT_DEVSUBTYPE_DRUM_KIT = 0x08,\n        XINPUT_DEVSUBTYPE_GUITAR_BASS = 0x0B,\n        XINPUT_DEVSUBTYPE_ARCADE_PAD = 0x13\n    };\n\n    public enum BatteryTypes : byte\n    {\n        //\n        // Flags for battery status level\n        //\n        BATTERY_TYPE_DISCONNECTED = 0x00,    // This device is not connected\n        BATTERY_TYPE_WIRED = 0x01,    // Wired device, no battery\n        BATTERY_TYPE_ALKALINE = 0x02,    // Alkaline battery source\n        BATTERY_TYPE_NIMH = 0x03,    // Nickel Metal Hydride battery source\n        BATTERY_TYPE_UNKNOWN = 0xFF,    // Cannot determine the battery type\n    };\n\n\n    // These are only valid for wireless, connected devices, with known battery types\n    // The amount of use time remaining depends on the type of device.\n    public enum BatteryLevel : byte\n    {\n        BATTERY_LEVEL_EMPTY = 0x00,\n        BATTERY_LEVEL_LOW = 0x01,\n        BATTERY_LEVEL_MEDIUM = 0x02,\n        BATTERY_LEVEL_FULL = 0x03\n    };\n\n    public enum BatteryDeviceType : byte\n    {\n        BATTERY_DEVTYPE_GAMEPAD = 0x00,\n        BATTERY_DEVTYPE_HEADSET = 0x01,\n    }\n\n    public class XInputConstants\n    {\n        public const int XINPUT_DEVTYPE_GAMEPAD = 0x01;\n\n        //\n        // Device subtypes available in XINPUT_CAPABILITIES\n        //\n        public const int XINPUT_DEVSUBTYPE_GAMEPAD = 0x01;\n\n        //\n        // Flags for XINPUT_CAPABILITIES\n        //\n        public enum CapabilityFlags\n        {\n            XINPUT_CAPS_VOICE_SUPPORTED = 0x0004,\n            //For Windows 8 only\n            XINPUT_CAPS_FFB_SUPPORTED = 0x0001,\n            XINPUT_CAPS_WIRELESS = 0x0002,\n            XINPUT_CAPS_PMD_SUPPORTED = 0x0008,\n            XINPUT_CAPS_NO_NAVIGATION = 0x0010,\n        };\n        //\n        // Constants for gamepad buttons\n        //\n\n        //\n        // Gamepad thresholds\n        //\n        public const int XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE = 7849;\n        public const int XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE = 8689;\n        public const int XINPUT_GAMEPAD_TRIGGER_THRESHOLD = 30;\n\n        //\n        // Flags to pass to XInputGetCapabilities\n        //\n        public const int XINPUT_FLAG_GAMEPAD = 0x00000001;\n    }\n\n    public class XInputPoint\n    {\n        public int X { get; set; }\n        public int Y { get; set; }\n    }\n}\n"
  },
  {
    "path": "sources/utils/datamine/CsvData.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.IO;\n\n#if DEBUG\n\nnamespace FFTriadBuddy.Datamine\n{\n    public class CsvLocalizedData\n    {\n        public readonly static string DefaultLanguage = \"en\";\n\n        public Dictionary<string, CsvData> mapLanguages = new Dictionary<string, CsvData>();\n        public CsvData data;\n\n        public int GetNumRows() { return (data != null) ? data.rows.Count : 0; }\n        public int GetNumColumns() { return (data != null && data.rows.Count > 0 && data.rows[0] != null) ? data.rows[0].Length : 0; }\n        public int GetNumLanguages() { return mapLanguages.Count; }\n\n        public LocString GetLocalizedText(int rowIdx, int columnIdx, int keyColumnIdx = 0)\n        {\n            var keyStr = mapLanguages[DefaultLanguage].rows[rowIdx][keyColumnIdx];\n\n            LocString textData = new LocString();\n            foreach (var kvp in mapLanguages)\n            {\n                // not all locs are build on the same client version, but Ids are in sync\n                int langIdx = Array.IndexOf(LocalizationDB.Languages, kvp.Key);\n                if (rowIdx < kvp.Value.rows.Count)\n                {\n                    int locRowIdx = rowIdx;\n\n                    // but are the rows in sync too?\n                    var locKeyStr = kvp.Value.rows[rowIdx][keyColumnIdx];\n                    if (keyStr != locKeyStr)\n                    {\n                        throw new Exception(\"Unable to match key in localization csv!\");\n                    }\n\n                    textData.Text[langIdx] = kvp.Value.rows[locRowIdx][columnIdx].Trim();\n                }\n            }\n\n            return textData;\n        }\n\n        public static CsvLocalizedData LoadFrom(string path, int numRowsToSkip = 2)\n        {\n            CsvLocalizedData resultOb = new CsvLocalizedData();\n\n            if (File.Exists(path))\n            {\n                resultOb.mapLanguages[DefaultLanguage] = CsvData.LoadFrom(path, numRowsToSkip);\n            }\n\n            foreach (string lang in LocalizationDB.Languages)\n            {\n                string locPath = path.Replace(\".csv\", \".\" + lang + \".csv\");\n                if (File.Exists(locPath))\n                {\n                    resultOb.mapLanguages.Add(lang, CsvData.LoadFrom(locPath, numRowsToSkip));\n                }\n            }\n\n            if (resultOb.mapLanguages.ContainsKey(DefaultLanguage))\n            {\n                resultOb.data = resultOb.mapLanguages[DefaultLanguage];\n            }\n\n            return resultOb;\n        }\n    }\n\n    public class CsvData\n    {\n        public List<string[]> rows = new List<string[]>();\n\n        public static CsvData LoadFrom(string path, int numRowsToSkip = 2)\n        {\n            var resultOb = new CsvData();\n            int lineIdx = 0;\n            int skipCounter = numRowsToSkip;\n\n            using (StreamReader reader = new StreamReader(path))\n            {\n                string header = reader.ReadLine();\n                int numCols = header.Split(',').Length;\n                lineIdx++;\n\n                while (!reader.EndOfStream)\n                {\n                    string row = reader.ReadLine();\n                    lineIdx++;\n\n                    if (skipCounter > 0)\n                    {\n                        skipCounter--;\n                        continue;\n                    }\n\n                    if (row.Length > 0)\n                    {\n                        // simple multiline check - expect to see even number of \" chars, concat next row if not\n                        int numStringSep = CountStringSep(row);\n                        while ((numStringSep % 2) != 0)\n                        {\n                            string nextRow = reader.ReadLine();\n                            lineIdx++;\n\n                            row += \" \" + nextRow;\n                            numStringSep = CountStringSep(row);\n                        }\n\n                        string[] cols = SplitRow(row);\n                        if (cols.Length != numCols)\n                        {\n                            throw new Exception(\"Column count mismatch at '\" + path + \"' line:\" + lineIdx);\n                        }\n\n                        resultOb.rows.Add(cols);\n                    }\n                }\n            }\n\n            return resultOb;\n        }\n\n        private static int CountStringSep(string csvRow)\n        {\n            int total = 0;\n            for (int idx = 0; idx < csvRow.Length; idx++)\n            {\n                if (csvRow[idx] == '\\\"')\n                {\n                    total++;\n                }\n            }\n\n            return total;\n        }\n\n        private static string[] SplitRow(string csvRow)\n        {\n            List<string> tokens = new List<string>();\n            int sepIdx = csvRow.IndexOf(',');\n            int lastSep = -1;\n            string prevToken = \"\";\n            bool waitingForString = false;\n\n            while (sepIdx >= 0)\n            {\n                string token = (sepIdx < 1) ? \"\" : csvRow.Substring(lastSep + 1, sepIdx - lastSep - 1);\n                lastSep = sepIdx;\n\n                if (waitingForString)\n                {\n                    prevToken += \",\" + token;\n\n                    if (token.EndsWith(\"\\\"\"))\n                    {\n                        waitingForString = false;\n                        prevToken = prevToken.Substring(1, prevToken.Length - 2);\n\n                        tokens.Add(prevToken);\n                    }\n                }\n                else\n                {\n                    if (token.Length > 0 && token[0] == '\"' && !token.EndsWith(\"\\\"\"))\n                    {\n                        prevToken = token;\n                        waitingForString = true;\n                    }\n                    else\n                    {\n                        if (token.Length > 0 && token[0] == '\"')\n                        {\n                            token = (token.Length > 2) ? token.Substring(1, token.Length - 2) : \"\";\n                        }\n\n                        tokens.Add(token);\n                    }\n                }\n\n                sepIdx = csvRow.IndexOf(',', lastSep + 1);\n            }\n\n            string lastToken = (lastSep < 0) ? csvRow : csvRow.Substring(lastSep + 1);\n            if (lastToken.Length > 0 && lastToken[0] == '\"')\n            {\n                lastToken = lastToken.Substring(1, lastToken.Length - 2);\n            }\n\n            tokens.Add(lastToken);\n            return tokens.ToArray();\n        }\n    }\n}\n\n#endif // DEBUG\n"
  },
  {
    "path": "sources/utils/datamine/GameData.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\nusing System.Globalization;\n\n#if DEBUG\n\nnamespace FFTriadBuddy.Datamine\n{\n    public class GameData\n    {\n        public int Id = 0;\n        public virtual bool IsRawDataValid(CsvLocalizedData rawData) { return false; }\n        public virtual void Parse(CsvLocalizedData rawData, int rowIdx) { }\n        public virtual bool IsValid() { return Id > 0; }\n        public virtual bool Link(GameDataLists lists) { return false; }\n    }\n\n\n    public class GameDataRule : GameData\n    {\n        public LocString Name;\n\n        public override string ToString() { return Id + \": \" + Name; }\n        public override bool IsRawDataValid(CsvLocalizedData rawData) { return rawData.GetNumColumns() == 8; }\n\n        public override void Parse(CsvLocalizedData rawData, int rowIdx)\n        {\n            string[] defRow = rawData.data.rows[rowIdx];\n\n            Id = int.Parse(defRow[0]);\n            if (Id > 0)\n            {\n                Name = rawData.GetLocalizedText(rowIdx, 1);\n            }\n        }\n\n        public override bool Link(GameDataLists lists)\n        {\n            // no child nodes\n            return true;\n        }\n    }\n\n    public class GameDataCardType : GameData\n    {\n        public LocString Type;\n\n        public override string ToString() { return Id + \": \" + Type; }\n        public override bool IsRawDataValid(CsvLocalizedData rawData) { return rawData.GetNumColumns() == 2; }\n\n        public override void Parse(CsvLocalizedData rawData, int rowIdx)\n        {\n            string[] defRow = rawData.data.rows[rowIdx];\n\n            Id = int.Parse(defRow[0]);\n            if (Id > 0)\n            {\n                Type = rawData.GetLocalizedText(rowIdx, 1);\n            }\n        }\n\n        public override bool Link(GameDataLists lists)\n        {\n            // no child nodes\n            return true;\n        }\n    }\n\n    public class GameDataCardName : GameData\n    {\n        public LocString Name;\n\n        public override string ToString() { return Id + \": \" + Name; }\n        public override bool IsRawDataValid(CsvLocalizedData rawData) { return rawData.GetNumColumns() == 10; }\n\n        public override void Parse(CsvLocalizedData rawData, int rowIdx)\n        {\n            string[] defRow = rawData.data.rows[rowIdx];\n\n            Id = int.Parse(defRow[0]);\n            if (Id > 0)\n            {\n                Name = rawData.GetLocalizedText(rowIdx, 1);\n            }\n        }\n\n        public override bool Link(GameDataLists lists)\n        {\n            // no child nodes\n            return true;\n        }\n    }\n\n    public class GameDataCard : GameData\n    {\n        public int rarityIdx;\n        public string cardType;\n        public int sideTop;\n        public int sideBottom;\n        public int sideLeft;\n        public int sideRight;\n        public int sortOrder;\n        public int uiGroup;\n\n        public Dictionary<string, int[]> locSides = new Dictionary<string, int[]>();\n\n        public GameDataCardType LinkedType;\n        public GameDataCardName LinkedName;\n\n        public override string ToString() { return string.Format(\"{0}: [{1}-{2}-{3}-{4}]\", Id, sideTop, sideLeft, sideBottom, sideRight); }\n        public override bool IsRawDataValid(CsvLocalizedData rawData) { return rawData.GetNumColumns() == 17; }\n\n        public override void Parse(CsvLocalizedData rawData, int rowIdx)\n        {\n            string[] defRow = rawData.data.rows[rowIdx];\n\n            Id = int.Parse(defRow[0]);\n            if (Id > 0)\n            {\n                sideTop = int.Parse(defRow[2]);\n                sideBottom = int.Parse(defRow[3]);\n                sideLeft = int.Parse(defRow[5]);\n                sideRight = int.Parse(defRow[4]);\n\n                cardType = defRow[7];\n\n                int rarityStrLen = defRow[6].Length;\n                char lastRarityChar = (rarityStrLen > 0) ? defRow[6][rarityStrLen - 1] : '0';\n                rarityIdx = lastRarityChar - '0';\n\n                sortOrder = int.Parse(defRow[10]);\n                uiGroup = int.Parse(defRow[11]);\n\n                foreach (var kvp in rawData.mapLanguages)\n                {\n                    if (kvp.Key != CsvLocalizedData.DefaultLanguage)\n                    {\n                        string[] locRow = (rowIdx < kvp.Value.rows.Count) ? kvp.Value.rows[rowIdx] : null;\n                        if (locRow != null)\n                        {\n                            int[] sides = new int[] {\n                                int.Parse(locRow[2]), // top\n                                int.Parse(locRow[5]), // left\n                                int.Parse(locRow[3]), // bottom\n                                int.Parse(locRow[4])  // right\n                            };\n\n                            bool isValid = (sides[0] > 0) && (sides[1] > 0) && (sides[2] > 0) && (sides[3] > 0);\n                            if (isValid)\n                            {\n                                locSides.Add(kvp.Key, sides);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        public bool HasMatchingLocSide(int[] sideNums)\n        {\n            return (sideNums[0] == sideTop) && (sideNums[1] == sideLeft) && (sideNums[2] == sideBottom) && (sideNums[3] == sideRight);\n        }\n\n        public override bool Link(GameDataLists lists)\n        {\n            LinkedType = lists.cardTypes.Find(x => (x.Type.GetCodeName() == cardType));\n            // null is allowed LinkType value\n\n            LinkedName = lists.cardNames.Find(x => (x.Id == Id));\n            if (LinkedName == null)\n            {\n                Logger.WriteLine(\"FAILED link: GameDataCard, Id:{1}, no matching Name\", Id);\n                return false;\n            }\n\n            return true;\n        }\n    }\n\n    public class GameDataNpcTriadId : GameData\n    {\n        public static readonly string TriadIdPattern = \"TripleTriad#\";\n\n        public int TriadId;\n\n        public GameDataNpcLocation LinkedLocation;\n        public GameDataNpcName LinkedName;\n        public GameDataNpc LinkedNpc;\n\n        public override string ToString() { return Id + \": \" + TriadId; }\n        public override bool IsRawDataValid(CsvLocalizedData rawData) { return rawData.GetNumColumns() > 8; }\n        public override bool IsValid() { return TriadId != 0; }\n\n        public override void Parse(CsvLocalizedData rawData, int rowIdx)\n        {\n            string[] defRow = rawData.data.rows[rowIdx];\n\n            Id = int.Parse(defRow[0]);\n            TriadId = 0;\n\n            for (int colIdx = 3; colIdx < defRow.Length; colIdx++)\n            {\n                if (defRow[colIdx].StartsWith(TriadIdPattern))\n                {\n                    TriadId = int.Parse(defRow[colIdx].Substring(TriadIdPattern.Length));\n                    break;\n                }\n            }\n        }\n\n        public override bool Link(GameDataLists lists)\n        {\n            LinkedNpc = lists.npcs.Find(x => (x.Id == TriadId));\n            if (LinkedNpc == null)\n            {\n                Logger.WriteLine(\"FAILED link: GameDataNpcTriadId, NpcBaseId:{0}, TriadId:{1}, no matching NPC\", Id, TriadId);\n                return false;\n            }\n\n            LinkedLocation = lists.npcLocations.Find(x => (x.Id == Id));\n            if (LinkedLocation == null)\n            {\n                Logger.WriteLine(\"Warning: GameDataNpcTriadId, NpcBaseId:{0}, no matching Location\", Id);\n                //Logger.WriteLine(\"FAILED link: GameDataNpcTriadId, NpcBaseId:{0}, no matching Location\", Id);\n                //return false;\n            }\n            else\n            {\n                LinkedLocation.LinkedOwner = this;\n            }\n\n            LinkedName = lists.npcNames.Find(x => (x.Id == Id));\n            if (LinkedName == null)\n            {\n                Logger.WriteLine(\"FAILED link: GameDataNpcTriadId, NpcBaseId:{0}, no matching Name\", Id);\n                return false;\n            }\n\n            return true;\n        }\n    }\n\n    public class GameDataNpcName : GameData\n    {\n        public LocString Name;\n\n        public override string ToString() { return Id + \": \" + Name; }\n        public override bool IsRawDataValid(CsvLocalizedData rawData) { return rawData.GetNumColumns() == 12; }\n        public override bool IsValid() { return Name != null; }\n\n        public override void Parse(CsvLocalizedData rawData, int rowIdx)\n        {\n            string[] defRow = rawData.data.rows[rowIdx];\n\n            Id = int.Parse(defRow[0]);\n            if (defRow[1].Length > 0)\n            {\n                Name = rawData.GetLocalizedText(rowIdx, 1);\n            }\n        }\n\n        public override bool Link(GameDataLists lists)\n        {\n            // no child nodes\n            return true;\n        }\n    }\n\n    public class GameDataNpcLocation : GameData\n    {\n        public static readonly string NpcId = \"ENpcBase#\";\n        public static readonly string NpcType = \"8\";\n\n        public string MapCode;\n        public float ScaledPosX;\n        public float ScaledPosZ;\n\n        public GameDataMap LinkedMap;\n        public GameDataNpcTriadId LinkedOwner;\n\n        public override string ToString() { return Id + \": \" + MapCode; }\n        public override bool IsRawDataValid(CsvLocalizedData rawData) { return rawData.GetNumColumns() == 11; }\n        public override bool IsValid() { return !string.IsNullOrEmpty(MapCode); }\n\n        public override void Parse(CsvLocalizedData rawData, int rowIdx)\n        {\n            string[] defRow = rawData.data.rows[rowIdx];\n\n            if (defRow[6] == NpcType && defRow[7].StartsWith(NpcId))\n            {\n                Id = int.Parse(defRow[7].Substring(NpcId.Length));\n                ScaledPosX = float.Parse(defRow[1], CultureInfo.InvariantCulture);\n                ScaledPosZ = float.Parse(defRow[3], CultureInfo.InvariantCulture);\n                MapCode = defRow[8];\n            }\n        }\n\n        public override bool Link(GameDataLists lists)\n        {\n            if (LinkedOwner != null && LinkedOwner.LinkedNpc != null)\n            {\n                LinkedMap = lists.maps.Find(x => (x.MapCode == MapCode));\n                if (LinkedMap == null)\n                {\n                    Logger.WriteLine(\"FAILED link: GameDataNpcLocation, NpcBaseId:{0}, MapCode:{1}, no matching Map\", Id, MapCode);\n                    return false;\n                }\n            }\n\n            return true;\n        }\n    }\n\n    public class GameDataNpc : GameData\n    {\n        public List<string> CardsFixed;\n        public List<string> CardsVariable;\n        public List<string> Rules;\n        public List<string> Rewards;\n\n        public Dictionary<string, string[]> locCards = new Dictionary<string, string[]>();\n\n        public GameDataNpcTriadId LinkedNpcId;\n        public List<GameDataCard> LinkedCardsFixed;\n        public List<GameDataCard> LinkedCardsVariable;\n        public List<GameDataRule> LinkedRules;\n        public List<GameDataCard> LinkedRewards;\n\n        public override string ToString() { return string.Format(\"{0}: {1}\", Id.ToString(), LinkedNpcId != null && LinkedNpcId.LinkedName != null ? LinkedNpcId.LinkedName.Name.GetCodeName() : \"\"); }\n        public override bool IsRawDataValid(CsvLocalizedData rawData) { return rawData.GetNumColumns() == 31; }\n        public override bool IsValid() { return (CardsFixed.Count > 0) || (CardsVariable.Count > 0); }\n\n        public override void Parse(CsvLocalizedData rawData, int rowIdx)\n        {\n            string[] defRow = rawData.data.rows[rowIdx];\n\n            Id = int.Parse(defRow[0]);\n            if (Id > 0)\n            {\n                CardsFixed = new List<string>();\n                for (int idx = 1; idx <= 5; idx++)\n                {\n                    if (defRow[idx].Length > 0 && defRow[idx] != \"0\")\n                    {\n                        CardsFixed.Add(defRow[idx]);\n                    }\n                }\n\n                CardsVariable = new List<string>();\n                for (int idx = 6; idx <= 10; idx++)\n                {\n                    if (defRow[idx].Length > 0 && defRow[idx] != \"0\")\n                    {\n                        CardsVariable.Add(defRow[idx]);\n                    }\n                }\n\n                Rules = new List<string>();\n                for (int idx = 11; idx <= 12; idx++)\n                {\n                    if (defRow[idx].Length > 0)\n                    {\n                        Rules.Add(defRow[idx]);\n                    }\n                }\n\n                Rewards = new List<string>();\n                for (int idx = 27; idx <= 30; idx++)\n                {\n                    if (defRow[idx].Length > 0)\n                    {\n                        Rewards.Add(defRow[idx]);\n                    }\n                }\n\n                foreach (var kvp in rawData.mapLanguages)\n                {\n                    if (kvp.Key != CsvLocalizedData.DefaultLanguage)\n                    {\n                        string[] locRow = (rowIdx < kvp.Value.rows.Count) ? kvp.Value.rows[rowIdx] : null;\n                        if (locRow != null)\n                        {\n                            List<string> cardNames = new List<string>();\n                            for (int idx = 1; idx <= 10; idx++)\n                            {\n                                if (locRow[idx].Length > 0 && locRow[idx] != \"0\")\n                                {\n                                    cardNames.Add(locRow[idx]);\n                                }\n                            }\n\n                            if (cardNames.Count > 0)\n                            {\n                                locCards.Add(kvp.Key, cardNames.ToArray());\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        public bool HasMatchingCards(string[] cards)\n        {\n            if (CardsFixed.Count + CardsVariable.Count != cards.Length)\n            {\n                return false;\n            }\n\n            int testIdx = 0;\n            for (int srcIdx = 0; srcIdx < CardsFixed.Count; srcIdx++, testIdx++)\n            {\n                if (CardsFixed[srcIdx] != cards[testIdx])\n                {\n                    return false;\n                }\n            }\n\n            for (int srcIdx = 0; srcIdx < CardsVariable.Count; srcIdx++, testIdx++)\n            {\n                if (CardsVariable[srcIdx] != cards[testIdx])\n                {\n                    return false;\n                }\n            }\n\n            return true;\n        }\n\n        public override bool Link(GameDataLists lists)\n        {\n            LinkedNpcId = lists.npcTriadIds.Find(x => (x.TriadId == Id));\n            if (LinkedNpcId == null)\n            {\n                // removed NPC? ignore and fail this entire entry\n                Logger.WriteLine(\"Warning: GameDataNpc, TriadId:{0}, no matching NPC\", Id);\n                return true;\n            }\n\n            LinkedCardsFixed = new List<GameDataCard>();\n            foreach (var cardName in CardsFixed)\n            {\n                var LinkedCard = lists.cards.Find(x => (x.LinkedName.Name.GetCodeName() == cardName));\n                if (LinkedCard == null)\n                {\n                    Logger.WriteLine(\"FAILED link: GameDataNpc, TriadId:{0}, Card:{1}, no matching card name\", Id, cardName);\n                    return false;\n                }\n\n                LinkedCardsFixed.Add(LinkedCard);\n            }\n\n            LinkedCardsVariable = new List<GameDataCard>();\n            foreach (var cardName in CardsVariable)\n            {\n                var LinkedCard = lists.cards.Find(x => (x.LinkedName.Name.GetCodeName() == cardName));\n                if (LinkedCard == null)\n                {\n                    Logger.WriteLine(\"FAILED link: GameDataNpc, TriadId:{0}, Card:{1}, no matching card name\", Id, cardName);\n                    return false;\n                }\n\n                LinkedCardsVariable.Add(LinkedCard);\n            }\n\n            LinkedRules = new List<GameDataRule>();\n            foreach (var ruleName in Rules)\n            {\n                var LinkedRule = lists.rules.Find(x => (x.Name.GetCodeName() == ruleName));\n                if (LinkedRule == null)\n                {\n                    Logger.WriteLine(\"FAILED link: GameDataNpc, TriadId:{0}, Rule:{1}, no matching rule name\", Id, ruleName);\n                    return false;\n                }\n\n                LinkedRules.Add(LinkedRule);\n            }\n\n            LinkedRewards = new List<GameDataCard>();\n            foreach (var itemName in Rewards)\n            {\n                var LinkedReward = lists.cards.Find(x => (x.LinkedName.Name.GetCodeName() == itemName));\n                if (LinkedReward == null && itemName.EndsWith(\" Card\"))\n                {\n                    var itemName2 = itemName.Substring(0, itemName.Length - 5);\n\n                    LinkedReward = lists.cards.Find(x => (x.LinkedName.Name.GetCodeName() == itemName2));\n                    if (LinkedReward == null)\n                    {\n                        var itemName3 = \"The \" + itemName2;\n                        LinkedReward = lists.cards.Find(x => (x.LinkedName.Name.GetCodeName() == itemName3));\n                    }\n                }\n\n                if (LinkedReward == null)\n                {\n                    Logger.WriteLine(\"FAILED link: GameDataNpc, TriadId:{0}, Reward:{1}, no matching item name\", Id, itemName);\n                    return false;\n                }\n\n                LinkedRewards.Add(LinkedReward);\n            }\n\n            return true;\n        }\n    }\n\n    public class GameDataMap : GameData\n    {\n        public string Name;\n        public string MapCode;\n        public int Scale;\n        public int OffsetX;\n        public int OffsetY;\n\n        public GameDataPlaceName LinkedPlace;\n\n        public void GetCoords(float ScaledX, float ScaledY, out int MapX, out int MapY)\n        {\n            MapX = CovertCoordTo2d(ScaledX, OffsetX);\n            MapY = CovertCoordTo2d(ScaledY, OffsetY);\n        }\n\n        private int CovertCoordTo2d(float Coord, float Offset)\n        {\n            float useScale = Scale / 100.0f;\n            float useValue = (Coord + Offset) * useScale;\n            return (int)Math.Round(((41.0 / useScale) * ((useValue + 1024.0) / 2048.0)) + 1);\n        }\n\n        public override string ToString() { return MapCode + \": \" + Name; }\n        public override bool IsRawDataValid(CsvLocalizedData rawData) { return rawData.GetNumColumns() == 22; }\n        public override bool IsValid() { return !string.IsNullOrEmpty(Name); }\n\n        public override void Parse(CsvLocalizedData rawData, int rowIdx)\n        {\n            string[] defRow = rawData.data.rows[rowIdx];\n\n            MapCode = defRow[7];\n            if (MapCode.Length > 0 && !MapCode.StartsWith(\"default\"))\n            {\n                Name = defRow[12];\n                Scale = int.Parse(defRow[8]);\n                OffsetX = int.Parse(defRow[9]);\n                OffsetY = int.Parse(defRow[10]);\n            }\n        }\n\n        public override bool Link(GameDataLists lists)\n        {\n            LinkedPlace = lists.placeNames.Find(x => (x.Name.GetCodeName() == Name));\n            if (LinkedPlace == null)\n            {\n                Logger.WriteLine(\"FAILED link: GameDataMap, MapCode:{0}, Name:{1}, no matching place name\", MapCode, Name);\n                return false;\n            }\n\n            return true;\n        }\n    }\n\n    public class GameDataPlaceName : GameData\n    {\n        public LocString Name;\n\n        public override string ToString() { return Name.ToString(); }\n        public override bool IsRawDataValid(CsvLocalizedData rawData) { return rawData.GetNumColumns() == 13; }\n        public override bool IsValid() { return Name != null; }\n\n        public override void Parse(CsvLocalizedData rawData, int rowIdx)\n        {\n            string[] defRow = rawData.data.rows[rowIdx];\n\n            if (defRow[1].Length > 0)\n            {\n                Name = rawData.GetLocalizedText(rowIdx, 1);\n            }\n        }\n\n        public override bool Link(GameDataLists lists)\n        {\n            // no child nodes\n            return true;\n        }\n    }\n\n    public class GameDataTournamentName : GameData\n    {\n        public LocString Name;\n\n        public override string ToString() { return Id + \": \" + Name; }\n        public override bool IsRawDataValid(CsvLocalizedData rawData) { return rawData.GetNumColumns() == 2; }\n        public override bool IsValid() { return Name != null; }\n\n        public override void Parse(CsvLocalizedData rawData, int rowIdx)\n        {\n            string[] defRow = rawData.data.rows[rowIdx];\n\n            Id = int.Parse(defRow[0]);\n            if (defRow[1].Length > 0)\n            {\n                Name = rawData.GetLocalizedText(rowIdx, 1);\n            }\n        }\n\n        public override bool Link(GameDataLists lists)\n        {\n            // no child nodes\n            return true;\n        }\n    }\n\n    public class GameDataTournament : GameData\n    {\n        public List<int> RuleIds;\n\n        public List<GameDataRule> LinkedRules;\n\n        public override string ToString() { return Id + \": \" + RuleIds.Count; }\n        public override bool IsRawDataValid(CsvLocalizedData rawData) { return rawData.GetNumColumns() == 5; }\n\n        public override void Parse(CsvLocalizedData rawData, int rowIdx)\n        {\n            string[] defRow = rawData.data.rows[rowIdx];\n\n            Id = int.Parse(defRow[0]);\n            if (Id > 0)\n            {\n                RuleIds = new List<int>();\n                for (int idx = 1; idx <= 4; idx++)\n                {\n                    if (defRow[idx] != \"0\")\n                    {\n                        RuleIds.Add(int.Parse(defRow[idx]));\n                    }\n                }\n            }\n        }\n\n        public override bool Link(GameDataLists lists)\n        {\n            LinkedRules = new List<GameDataRule>();\n            foreach (var ruleId in RuleIds)\n            {\n                var LinkedRule = lists.rules.Find(x => (x.Id == ruleId));\n                if (LinkedRule == null)\n                {\n                    Logger.WriteLine(\"FAILED link: GameDataTournament, Id:{0}, ruleId:{1}, no matching rule Id\", Id, ruleId);\n                    return false;\n                }\n\n                LinkedRules.Add(LinkedRule);\n            }\n\n            return true;\n        }\n    }\n\n    public class GameDataLists\n    {\n        public List<GameDataRule> rules;\n        public List<GameDataCardType> cardTypes;\n        public List<GameDataCardName> cardNames;\n        public List<GameDataCard> cards;\n        public List<GameDataNpcTriadId> npcTriadIds;\n        public List<GameDataNpcName> npcNames;\n        public List<GameDataNpcLocation> npcLocations;\n        public List<GameDataNpc> npcs;\n        public List<GameDataMap> maps;\n        public List<GameDataPlaceName> placeNames;\n        public List<GameDataTournamentName> tournamentNames;\n        public List<GameDataTournament> tournaments;\n\n        public void Load(string folderPath)\n        {\n            Logger.WriteLine(\"Loading csv files...\");\n            rules = LoadGameData<GameDataRule>(folderPath, \"TripleTriadRule\");\n            cardTypes = LoadGameData<GameDataCardType>(folderPath, \"TripleTriadCardType\");\n            cardNames = LoadGameData<GameDataCardName>(folderPath, \"TripleTriadCard\");\n            cards = LoadGameData<GameDataCard>(folderPath, \"TripleTriadCardResident\");\n\n            npcTriadIds = LoadGameData<GameDataNpcTriadId>(folderPath, \"ENpcBase\");\n            npcNames = LoadGameData<GameDataNpcName>(folderPath, \"ENpcResident\");\n            npcLocations = LoadGameData<GameDataNpcLocation>(folderPath, \"Level\");\n            npcs = LoadGameData<GameDataNpc>(folderPath, \"TripleTriad\");\n            maps = LoadGameData<GameDataMap>(folderPath, \"Map\");\n            placeNames = LoadGameData<GameDataPlaceName>(folderPath, \"PlaceName\");\n\n            tournamentNames = LoadGameData<GameDataTournamentName>(folderPath, \"TripleTriadCompetition\");\n            tournaments = LoadGameData<GameDataTournament>(folderPath, \"TripleTriadTournament\");\n        }\n\n        public bool Link()\n        {\n            Logger.WriteLine(\"Linking database entries...\");\n\n            bool result = true;\n            result = result && LinkGameData(rules);\n            result = result && LinkGameData(cardTypes);\n            result = result && LinkGameData(cardNames);\n            result = result && LinkGameData(cards);\n\n            result = result && LinkGameData(npcTriadIds);\n            result = result && LinkGameData(npcNames);\n            result = result && LinkGameData(npcs);\n            result = result && LinkGameData(npcLocations); // must be after npcs\n            result = result && LinkGameData(maps);\n            result = result && LinkGameData(placeNames);\n\n            result = result && LinkGameData(tournamentNames);\n            result = result && LinkGameData(tournaments);\n\n            return result;\n        }\n\n        private List<T> LoadGameData<T>(string folderPath, string pathNoExt) where T : GameData, new()\n        {\n            var rawData = CsvLocalizedData.LoadFrom(folderPath + pathNoExt + \".csv\");\n            int numRows = rawData.GetNumRows();\n\n            if (numRows == 0)\n            {\n                Logger.WriteLine(\"FAILED to load {0}: empty\", pathNoExt);\n                return null;\n            }\n\n            T testOb = new T();\n            if (!testOb.IsRawDataValid(rawData))\n            {\n                Logger.WriteLine(\"FAILED to load {0}: layout mismatch!\", pathNoExt);\n                return null;\n            }\n\n            var resultList = new List<T>();\n            for (int idx = 0; idx < numRows; idx++)\n            {\n                T entryOb = new T();\n                entryOb.Parse(rawData, idx);\n\n                if (entryOb.IsValid())\n                {\n                    resultList.Add(entryOb);\n                }\n            }\n\n            Logger.WriteLine(\">> loaded {0}, entries: {1}\", pathNoExt, resultList.Count);\n            return resultList;\n        }\n\n        private bool LinkGameData<T>(List<T> list) where T : GameData\n        {\n            foreach (var entry in list)\n            {\n                if (!entry.Link(this))\n                {\n                    Logger.WriteLine(\"FAILED to link!\");\n                    return false;\n                }\n            }\n\n            return true;\n        }\n    }\n}\n\n#endif // DEBUG\n"
  },
  {
    "path": "sources/utils/tlsh/BucketSize.cs",
    "content": "/*\n * Ported from: https://github.com/trendmicro/tlsh\n * Source: https://github.com/morganabel/TlshSharp/tree/master/TLSHSharp\n */\n\n/*\n * TLSH is provided for use under two licenses: Apache OR BSD.\n * Users may opt to use either license depending on the license\n * restictions of the systems with which they plan to integrate\n * the TLSH code.\n */\n\n/* ==============\n * Apache License\n * ==============\n * Copyright 2017 Trend Micro Incorporated\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/* ===========\n * BSD License\n * ===========\n * Copyright (c) 2017, Trend Micro Incorporated\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n * 3. Neither the name of the copyright holder nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\nnamespace Palit.TLSHSharp\n{\n    public enum BucketSize\n    {\n        Buckets128 = 128,\n        Buckets256 = 256\n    }\n}\n"
  },
  {
    "path": "sources/utils/tlsh/ChecksumSize.cs",
    "content": "/*\n * Ported from: https://github.com/trendmicro/tlsh\n * Source: https://github.com/morganabel/TlshSharp/tree/master/TLSHSharp\n */\n\n/*\n * TLSH is provided for use under two licenses: Apache OR BSD.\n * Users may opt to use either license depending on the license\n * restictions of the systems with which they plan to integrate\n * the TLSH code.\n */\n\n/* ==============\n * Apache License\n * ==============\n * Copyright 2017 Trend Micro Incorporated\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/* ===========\n * BSD License\n * ===========\n * Copyright (c) 2017, Trend Micro Incorporated\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n * 3. Neither the name of the copyright holder nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\nnamespace Palit.TLSHSharp\n{\n    public enum ChecksumSize\n    {\n        Checksum1Byte = 1,\n        Checksum3Bytes = 3\n    }\n}"
  },
  {
    "path": "sources/utils/tlsh/TlshBuilder.cs",
    "content": "/*\n * Ported from: https://github.com/trendmicro/tlsh\n * Source: https://github.com/morganabel/TlshSharp/tree/master/TLSHSharp\n */\n\n/*\n * TLSH is provided for use under two licenses: Apache OR BSD.\n * Users may opt to use either license depending on the license\n * restictions of the systems with which they plan to integrate\n * the TLSH code.\n */\n\n/* ==============\n * Apache License\n * ==============\n * Copyright 2017 Trend Micro Incorporated\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/* ===========\n * BSD License\n * ===========\n * Copyright (c) 2017, Trend Micro Incorporated\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n * 3. Neither the name of the copyright holder nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\nusing System;\nusing System.Threading.Tasks;\n\nnamespace Palit.TLSHSharp\n{\n    public class TlshBuilder\n    {\n        private const int CSlidingWindowSize = 5;\n        private const int CBuckets = 256;\n\n        /// <summary>\n        /// Minimum length of input accepted by non-forced TLSH hash.\n        /// </summary>\n        private const int CMinDataLength = 256;\n\n        /// <summary>\n        /// Absolute minimum length of input accepted.\n        /// </summary>\n        private const int CMinForceDataLength = 50;\n\n        private readonly uint[] accumulatorBuckets;\n        private readonly int[] slideWindow;\n        private int dataLength;\n\n        private readonly int bucketCount;\n        private readonly int checksumLength;\n        private int checksum;\n        private readonly int[] checksumArray = null;\n        private readonly int codeSize;\n\n        public TlshBuilder() : this(BucketSize.Buckets128, ChecksumSize.Checksum1Byte) { }\n\n        public TlshBuilder(BucketSize bucketSize, ChecksumSize checksumSize)\n        {\n            bucketCount = (int)bucketSize;\n            checksumLength = (int)checksumSize;\n\n            // Each bucket => 2 bits of output code.\n            codeSize = bucketCount >> 2;\n\n            slideWindow = new int[CSlidingWindowSize];\n            accumulatorBuckets = new uint[CBuckets];\n\n            if (checksumLength > 1)\n            {\n                checksumArray = new int[checksumLength];\n            }\n        }\n\n        public void LoadFromString(string input)\n        {\n            var buffer = new byte[1024];\n            using (var s = TlshUtilities.GenerateStreamFromString(input))\n            {\n                var bytesRead = s.Read(buffer, 0, buffer.Length);\n                while (bytesRead > 0)\n                {\n                    Update(buffer, 0, bytesRead);\n                    bytesRead = s.Read(buffer, 0, buffer.Length);\n                }\n            }\n        }\n\n        public async Task LoadFromStringAsync(string input)\n        {\n            var buffer = new byte[1024];\n            using (var s = TlshUtilities.GenerateStreamFromString(input))\n            {\n                var bytesRead = await s.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);\n                while (bytesRead > 0)\n                {\n                    Update(buffer, 0, bytesRead);\n                    bytesRead = await s.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);\n                }\n            }\n        }\n\n        public void Update(byte[] data)\n        {\n            Update(data, 0, data.Length);\n        }\n\n        public void Update(byte[] data, int offset, int byteArrDataLength)\n        {\n            const int RNG_SIZE = CSlidingWindowSize;\n\n            // Indexes into the sliding window. They cycle like\n            // 0 4 3 2 1\n            // 1 0 4 3 2\n            // 2 1 0 4 3\n            // 3 2 1 0 4\n            // 4 3 2 1 0\n            // 0 4 3 2 1\n            // and so on\n            int j = dataLength % RNG_SIZE;\n            int j_1 = (j - 1 + RNG_SIZE) % RNG_SIZE;\n            int j_2 = (j - 2 + RNG_SIZE) % RNG_SIZE;\n            int j_3 = (j - 3 + RNG_SIZE) % RNG_SIZE;\n            int j_4 = (j - 4 + RNG_SIZE) % RNG_SIZE;\n\n            int fedLength = dataLength;\n\n            for (int i = offset; i < offset + byteArrDataLength; i++, fedLength++)\n            {\n                slideWindow[j] = data[i] & 0xFF;\n\n                if (fedLength >= 4)\n                {\n                    // only calculate when input >= 5 bytes\n\n                    checksum = TlshUtilities.PearsonHash(0, slideWindow[j], slideWindow[j_1], checksum);\n                    if (checksumLength > 1)\n                    {\n                        checksumArray[0] = checksum;\n                        for (int k = 1; k < checksumLength; k++)\n                        {\n                            // use calculated 1 byte checksums to expand the total checksum to 3 bytes\n                            checksumArray[k] = TlshUtilities.PearsonHash(checksumArray[k - 1], slideWindow[j],\n                                    slideWindow[j_1], checksumArray[k]);\n                        }\n                    }\n\n                    int r;\n                    r = TlshUtilities.PearsonHash(2, slideWindow[j], slideWindow[j_1], slideWindow[j_2]);\n                    accumulatorBuckets[r]++;\n                    r = TlshUtilities.PearsonHash(3, slideWindow[j], slideWindow[j_1], slideWindow[j_3]);\n                    accumulatorBuckets[r]++;\n                    r = TlshUtilities.PearsonHash(5, slideWindow[j], slideWindow[j_2], slideWindow[j_3]);\n                    accumulatorBuckets[r]++;\n                    r = TlshUtilities.PearsonHash(7, slideWindow[j], slideWindow[j_2], slideWindow[j_4]);\n                    accumulatorBuckets[r]++;\n                    r = TlshUtilities.PearsonHash(11, slideWindow[j], slideWindow[j_1], slideWindow[j_4]);\n                    accumulatorBuckets[r]++;\n                    r = TlshUtilities.PearsonHash(13, slideWindow[j], slideWindow[j_3], slideWindow[j_4]);\n                    accumulatorBuckets[r]++;\n                }\n                // rotate the sliding window indexes\n                int j_tmp = j_4;\n                j_4 = j_3;\n                j_3 = j_2;\n                j_2 = j_1;\n                j_1 = j;\n                j = j_tmp;\n            }\n            dataLength += byteArrDataLength;\n        }\n\n        private uint[] FindQuartiles()\n        {\n            var bucketCopy = new uint[bucketCount];\n            Array.Copy(accumulatorBuckets, bucketCopy, bucketCount);\n            var quartile = bucketCount >> 2;\n            var p1 = quartile - 1;\n            var p2 = p1 + quartile;\n            var p3 = p2 + quartile;\n            var end = p3 + quartile;\n\n            Array.Sort(bucketCopy);\n\n            return new[] { bucketCopy[p1], bucketCopy[p2], bucketCopy[p3] };\n        }\n\n        public TlshHash GetHash(bool force)\n        {\n            if (!IsValid(force))\n            {\n                throw new InvalidOperationException(\"TLSH not valid. Either not enough data or data has too little variance\");\n            }\n\n            uint q1, q2, q3;\n            uint[] quartiles = FindQuartiles();\n            q1 = quartiles[0];\n            q2 = quartiles[1];\n            q3 = quartiles[2];\n\n            var tmp_code = new int[codeSize];\n            for (int i = 0; i < codeSize; i++)\n            {\n                int h = 0;\n                for (int j = 0; j < 4; j++)\n                {\n                    var k = accumulatorBuckets[4 * i + j];\n                    if (q3 < k)\n                    {\n                        h += 3 << (j * 2);\n                    }\n                    else if (q2 < k)\n                    {\n                        h += 2 << (j * 2);\n                    }\n                    else if (q1 < k)\n                    {\n                        h += 1 << (j * 2);\n                    }\n                }\n                tmp_code[i] = h;\n            }\n\n            int lvalue = TlshUtilities.LengthCapture(dataLength);\n            int q1ratio = (int)((q1 * 100.0f) / q3) & 0xF;\n            int q2ratio = (int)((q2 * 100.0f) / q3) & 0xF;\n\n            if (checksumLength == 1)\n            {\n                return new TlshHash(new int[] { checksum }, lvalue, q1ratio, q2ratio, tmp_code);\n            }\n            else\n            {\n                var checksumArrayCopy = new int[checksumArray.Length];\n                Array.Copy(checksumArray, checksumArrayCopy, checksumArray.Length);\n                return new TlshHash(checksumArrayCopy, lvalue, q1ratio, q2ratio, tmp_code);\n            }\n        }\n\n        /// <summary>\n        /// Resets the TlshBuilder so it can process another input.\n        /// </summary>\n        public void Reset()\n        {\n            Array.Clear(accumulatorBuckets, 0, accumulatorBuckets.Length);\n            Array.Clear(slideWindow, 0, slideWindow.Length);\n            checksum = 0;\n            if (null != checksumArray)\n            {\n                Array.Clear(checksumArray, 0, checksumArray.Length);\n            }\n            dataLength = 0;\n        }\n\n        /// <summary>\n        /// Determines if enough data has been processed by TlshBuilder to produce valid output hash.\n        /// </summary>\n        /// <param name=\"force\"></param>\n        /// <returns></returns>\n        public bool IsValid(bool force)\n        {\n            // Quick return false if length not right.\n            if (dataLength < CMinForceDataLength || (!force && dataLength < CMinDataLength))\n            {\n                return false;\n            }\n\n            // >=50% of buckets must be set to be valid.\n            var nonZeroBucketCount = 0;\n            foreach (var bucket in accumulatorBuckets)\n            {\n                if (bucket > 0) nonZeroBucketCount++;\n\n                // Exit early when bucket count found to be high enough.\n                if (nonZeroBucketCount >= (bucketCount >> 1)) return true;\n            }\n\n            return false;\n        }\n    }\n}"
  },
  {
    "path": "sources/utils/tlsh/TlshHash.cs",
    "content": "/*\n * Ported from: https://github.com/trendmicro/tlsh\n * Source: https://github.com/morganabel/TlshSharp/tree/master/TLSHSharp\n */\n\n/*\n * TLSH is provided for use under two licenses: Apache OR BSD.\n * Users may opt to use either license depending on the license\n * restictions of the systems with which they plan to integrate\n * the TLSH code.\n */\n\n/* ==============\n * Apache License\n * ==============\n * Copyright 2017 Trend Micro Incorporated\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/* ===========\n * BSD License\n * ===========\n * Copyright (c) 2017, Trend Micro Incorporated\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n * 3. Neither the name of the copyright holder nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\nusing System;\nusing System.Text;\n\nnamespace Palit.TLSHSharp\n{\n    public class TlshHash\n    {\n        private const int CRangeLValue = 256;\n        private const int CRangeQRatio = 16;\n\n        private readonly int[] checksum; // 1 or 3 bytes\n        private readonly int Lvalue; // 1 byte\n        private readonly int Q1ratio; // 4 bits\n        private readonly int Q2ratio; // 4 bits\n        private readonly int[] codes; // 32/64 bytes\n\n        public static TlshHash FromTlshStr(string tlshStr)\n        {\n            int[] checksum = null;\n            int[] tmp_code = null;\n            foreach (BucketSize bucketSize in Enum.GetValues(typeof(BucketSize)))\n            {\n                foreach (ChecksumSize checksumOption in Enum.GetValues(typeof(ChecksumSize)))\n                {\n                    if (tlshStr.Length == HashStringLength(bucketSize, checksumOption))\n                    {\n                        checksum = new int[(int)checksumOption];\n                        tmp_code = new int[(int)bucketSize / 4];\n                    }\n                }\n            }\n            if (checksum == null)\n            {\n                throw new ArgumentException(\"Invalid hash string, length does not match any known encoding\");\n            }\n\n            var offset = 0;\n            for (int k = 0; k < checksum.Length; k++)\n            {\n                checksum[k] = TlshUtilities.FromHexSwapped(tlshStr, offset);\n                offset += 2;\n            }\n\n            var Lvalue = TlshUtilities.FromHexSwapped(tlshStr, offset);\n            offset += 2;\n\n            var qRatios = TlshUtilities.FromHex(tlshStr, offset);\n            offset += 2;\n\n            for (int i = 0; i < tmp_code.Length; i++)\n            {\n                // un-reverse the code during encoding\n                tmp_code[tmp_code.Length - i - 1] = TlshUtilities.FromHex(tlshStr, offset);\n                offset += 2;\n            }\n\n            return new TlshHash(checksum, Lvalue, qRatios >> 4, qRatios & 0xF, tmp_code);\n        }\n\n        public TlshHash(int[] checksum, int lvalue, int q1ratio, int q2ratio, int[] codes)\n        {\n            this.checksum = checksum;\n            Lvalue = lvalue;\n            Q1ratio = q1ratio;\n            Q2ratio = q2ratio;\n            this.codes = codes;\n        }\n\n        public int TotalDiff(TlshHash otherHash, bool lengthDiff)\n        {\n            if (checksum.Length != otherHash.checksum.Length || codes.Length != otherHash.codes.Length)\n            {\n                throw new ArgumentException(\"Given TLSH structure was created with different options from this hash and cannot be compared\");\n            }\n\n            var diff = 0;\n\n            if (lengthDiff)\n            {\n                var ldiff = TlshUtilities.ModDiff(Lvalue, otherHash.Lvalue, CRangeLValue);\n                if (ldiff == 0)\n                    diff = 0;\n                else if (ldiff == 1)\n                    diff = 1;\n                else\n                    diff += ldiff * 12;\n            }\n\n            var q1diff = TlshUtilities.ModDiff(Q1ratio, otherHash.Q1ratio, CRangeQRatio);\n            if (q1diff <= 1)\n                diff += q1diff;\n            else\n                diff += (q1diff - 1) * 12;\n\n            var q2diff = TlshUtilities.ModDiff(Q2ratio, otherHash.Q2ratio, CRangeQRatio);\n            if (q2diff <= 1)\n                diff += q2diff;\n            else\n                diff += (q2diff - 1) * 12;\n\n            for (int k = 0; k < checksum.Length; k++)\n            {\n                if (checksum[k] != otherHash.checksum[k])\n                {\n                    diff++;\n                    break;\n                }\n            }\n\n            diff += TlshUtilities.HashDistance(codes, otherHash.codes);\n\n            return diff;\n        }\n\n        public override string ToString()\n        {\n            return GetEncoded();\n        }\n\n        public string GetEncoded()\n        {\n            // The C++ code reverses the order of some of the fields before\n            // converting to hex, so copy that behaviour.\n            var sb = new StringBuilder(HashStringLength());\n\n            for (int k = 0; k < checksum.Length; k++)\n            {\n                TlshUtilities.ToHexSwapped(checksum[k], sb);\n            }\n            TlshUtilities.ToHexSwapped(Lvalue, sb);\n            TlshUtilities.ToHex(Q1ratio << 4 | Q2ratio, sb);\n            for (int i = 0; i < codes.Length; i++)\n            {\n                // reverse the code during encoding\n                TlshUtilities.ToHex(codes[codes.Length - 1 - i], sb);\n            }\n\n            return sb.ToString();\n        }\n\n        private static int HashStringLength(BucketSize bucketSize, ChecksumSize checksumSize)\n        {\n            return ((int)bucketSize / 2) + ((int)checksumSize * 2) + 4;\n        }\n\n        private int HashStringLength()\n        {\n            // extra 4 characters come from length and Q1 and Q2 ratio.\n            return (codes.Length * 2) + (checksum.Length * 2) + 4;\n        }\n    }\n}"
  },
  {
    "path": "sources/utils/tlsh/TlshUtilities.cs",
    "content": "/*\n * Ported from: https://github.com/trendmicro/tlsh\n * Source: https://github.com/morganabel/TlshSharp/tree/master/TLSHSharp\n */\n\n/*\n * TLSH is provided for use under two licenses: Apache OR BSD.\n * Users may opt to use either license depending on the license\n * restictions of the systems with which they plan to integrate\n * the TLSH code.\n */\n\n/* ==============\n * Apache License\n * ==============\n * Copyright 2017 Trend Micro Incorporated\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/* ===========\n * BSD License\n * ===========\n * Copyright (c) 2017, Trend Micro Incorporated\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice, this\n *    list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n * 3. Neither the name of the copyright holder nor the names of its contributors\n *    may be used to endorse or promote products derived from this software without\n *    specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\nusing System;\nusing System.IO;\nusing System.Text;\n\nnamespace Palit.TLSHSharp\n{\n    public static class TlshUtilities\n    {\n        /** Natural logarithm of 1.5 */\n        private const double CNLog1_5 = 0.4054651;\n        /** Natural logarithm of 1.3 */\n        private const double CNLog1_3 = 0.26236426;\n        /** Natural logarithm of 1.1 */\n        private const double CNLog1_1 = 0.095310180;\n\n        /// <summary>\n        /// Pearsons sample random table.\n        /// </summary>\n        private static readonly int[] ValueTable = {\n        1, 87, 49, 12, 176, 178, 102, 166, 121, 193, 6, 84, 249, 230, 44, 163,\n        14, 197, 213, 181, 161, 85, 218, 80, 64, 239, 24, 226, 236, 142, 38, 200,\n        110, 177, 104, 103, 141, 253, 255, 50, 77, 101, 81, 18, 45, 96, 31, 222,\n        25, 107, 190, 70, 86, 237, 240, 34, 72, 242, 20, 214, 244, 227, 149, 235,\n        97, 234, 57, 22, 60, 250, 82, 175, 208, 5, 127, 199, 111, 62, 135, 248,\n        174, 169, 211, 58, 66, 154, 106, 195, 245, 171, 17, 187, 182, 179, 0, 243,\n        132, 56, 148, 75, 128, 133, 158, 100, 130, 126, 91, 13, 153, 246, 216, 219,\n        119, 68, 223, 78, 83, 88, 201, 99, 122, 11, 92, 32, 136, 114, 52, 10,\n        138, 30, 48, 183, 156, 35, 61, 26, 143, 74, 251, 94, 129, 162, 63, 152,\n        170, 7, 115, 167, 241, 206, 3, 150, 55, 59, 151, 220, 90, 53, 23, 131,\n        125, 173, 15, 238, 79, 95, 89, 16, 105, 137, 225, 224, 217, 160, 37, 123,\n        118, 73, 2, 157, 46, 116, 9, 145, 134, 228, 207, 212, 202, 215, 69, 229,\n        27, 188, 67, 124, 168, 252, 42, 4, 29, 108, 21, 247, 19, 205, 39, 203,\n        233, 40, 186, 147, 198, 192, 155, 33, 164, 191, 98, 204, 165, 180, 117, 76,\n        140, 36, 210, 172, 41, 54, 159, 8, 185, 232, 113, 196, 231, 47, 146, 120,\n        51, 65, 28, 144, 254, 221, 93, 189, 194, 139, 112, 43, 71, 109, 184, 209\n        };\n\n        /// <summary>\n        /// Person hash function. Input must be 0-255, output will be 0-255\n        /// </summary>\n        /// <param name=\"salt\"></param>\n        /// <param name=\"i\"></param>\n        /// <param name=\"j\"></param>\n        /// <param name=\"k\"></param>\n        /// <returns></returns>\n        public static int PearsonHash(int salt, int i, int j, int k)\n        {\n            var h = ValueTable[salt];\n            h = ValueTable[h ^ i];\n            h = ValueTable[h ^ j];\n            return ValueTable[h ^ k];\n        }\n\n        /// <summary>\n        /// Computes length portion of TLSH\n        /// </summary>\n        /// <param name=\"len\"></param>\n        /// <returns></returns>\n        public static int LengthCapture(int len)\n        {\n            int i;\n            if (len <= 656)\n            {\n                i = (int)Math.Floor(Math.Log(len) / CNLog1_5);\n            }\n            else if (len <= 3199)\n            {\n                i = (int)Math.Floor((Math.Log(len) / CNLog1_3) - 8.72777);\n            }\n            else\n            {\n                i = (int)Math.Floor((Math.Log(len) / CNLog1_1) - 62.5472);\n            }\n\n            return i & 0xFF;\n        }\n\n        public static int ModDiff(int x, int y, int r)\n        {\n            var dl = 0;\n            var dr = 0;\n            if (y > x)\n            {\n                dl = y - x;\n                dr = x + r - y;\n            }\n            else\n            {\n                dl = x - y;\n                dr = y + r - x;\n            }\n            return (dl > dr ? dr : dl);\n        }\n\n        /// <summary>\n        /// Used to calculate the hash distance between hashcodes.\n        /// </summary>\n        /// <param name=\"x\"></param>\n        /// <param name=\"y\"></param>\n        /// <returns></returns>\n        public static int HashDistance(int[] x, int[] y)\n        {\n            var diff = 0;\n            for (int i = 0; i < x.Length; i++)\n            {\n                diff += DiffTable.BitPairsDiffTable[x[i], y[i]];\n            }\n            return diff;\n        }\n\n        /// <summary>\n        /// Turns a string into a stream. \n        /// Note: Stream writer does not need to be disposed. There is also an overload for .net4.5 that leaves stream open after streamwriter disposed.\n        /// </summary>\n        /// <param name=\"s\"></param>\n        /// <returns></returns>\n        public static Stream GenerateStreamFromString(string s)\n        {\n            MemoryStream stream = new MemoryStream();\n            StreamWriter writer = new StreamWriter(stream);\n            writer.Write(s);\n            writer.Flush();\n            stream.Position = 0;\n            return stream;\n        }\n\n        private static readonly char[] CHexChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };\n\n        public static void ToHex(int src, StringBuilder dest)\n        {\n            dest.Append(CHexChars[(src >> 4) & 0xF]);\n            dest.Append(CHexChars[src & 0xF]);\n        }\n\n        public static void ToHexSwapped(int src, StringBuilder dest)\n        {\n            dest.Append(CHexChars[src & 0xF]);\n            dest.Append(CHexChars[(src >> 4) & 0xF]);\n        }\n\n        public static int FromHex(String src, int offset)\n        {\n            var result = HexCharToInt(src[offset]) << 4;\n            result |= HexCharToInt(src[offset + 1]);\n            return result;\n        }\n\n        public static int FromHexSwapped(String src, int offset)\n        {\n            var result = HexCharToInt(src[offset + 1]) << 4;\n            result |= HexCharToInt(src[offset]);\n            return result;\n        }\n\n        private static int HexCharToInt(char hexChar)\n        {\n            if ('0' <= hexChar && hexChar <= '9')\n            {\n                return hexChar - '0';\n            }\n            else if ('A' <= hexChar && hexChar <= 'F')\n            {\n                return hexChar - 'A' + 10;\n            }\n            else if ('a' <= hexChar && hexChar <= 'f')\n            {\n                return hexChar - 'a' + 10;\n            }\n            else\n            {\n                throw new ArgumentOutOfRangeException(\"Invalid hex character '\" + hexChar + \"'\");\n            }\n        }\n\n        /// <summary>\n        /// Contained in a private static class to prevent initialization until needed.\n        /// </summary>\n        private static class DiffTable\n        {\n            internal static readonly int[,] BitPairsDiffTable = GenerateTable();\n\n            private static int[,] GenerateTable()\n            {\n                var result = new int[256, 256];\n                for (int i = 0; i < 256; i++)\n                {\n                    for (int j = 0; j < 256; j++)\n                    {\n                        var x = i;\n                        var y = j;\n                        var diff = 0;\n                        var d = Math.Abs((x % 4) - (y % 4));\n                        diff += (d == 3 ? 6 : d);\n                        x /= 4;\n                        y /= 4;\n                        d = Math.Abs((x % 4) - (y % 4));\n                        diff += (d == 3 ? 6 : d);\n                        x /= 4;\n                        y /= 4;\n                        d = Math.Abs((x % 4) - (y % 4));\n                        diff += (d == 3 ? 6 : d);\n                        x /= 4;\n                        y /= 4;\n                        d = Math.Abs((x % 4) - (y % 4));\n                        diff += (d == 3 ? 6 : d);\n                        result[i, j] = diff;\n                    }\n                }\n                return result;\n            }\n        }\n    }\n}"
  },
  {
    "path": "sources/utils-shared/AssetManager.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.IO.Compression;\n\nnamespace MgAl2O4.Utils\n{\n    public class AssetManager\n    {\n        private ZipArchive assetArchive;\n        private Stream resourceReader;\n\n        private static AssetManager instance = new AssetManager();\n\n        public static AssetManager Get()\n        {\n            return instance;\n        }\n\n        public bool Init(byte[] zipContent)\n        {\n            bool bResult = false;\n            try\n            {\n                resourceReader = new MemoryStream(zipContent);\n                assetArchive = new ZipArchive(resourceReader);\n                bResult = assetArchive.Entries.Count > 0;\n            }\n            catch (Exception ex)\n            {\n                Logger.WriteLine(\"Can't access embedded assets! \" + ex);\n            }\n\n            return bResult;\n        }\n\n        public void Close()\n        {\n            assetArchive.Dispose();\n            assetArchive = null;\n        }\n\n        public string CreateFilePath(string relativeFilePath)\n        {\n            string currentDirName = Environment.CurrentDirectory;\n            string[] devIgnorePatterns = new string[] { @\"sources\\bin\\Debug\", @\"sources\\bin\\Release\" };\n            foreach (string pattern in devIgnorePatterns)\n            {\n                if (currentDirName.EndsWith(pattern))\n                {\n                    currentDirName = currentDirName.Remove(currentDirName.Length - pattern.Length);\n                    break;\n                }\n            }\n\n            return string.IsNullOrEmpty(relativeFilePath) ? currentDirName : Path.Combine(currentDirName, relativeFilePath);\n        }\n\n        public Stream GetAsset(string path)\n        {\n            path = path.Replace(\"/\", \"\\\\\");\n            foreach (ZipArchiveEntry entry in assetArchive.Entries)\n            {\n                string compareName = entry.FullName.Replace(\"/\", \"\\\\\");\n\n                if (compareName.Equals(path, StringComparison.InvariantCultureIgnoreCase))\n                {\n                    return entry.Open();\n                }\n            }\n\n            return null;\n        }\n\n        public List<string> ListAssets()\n        {\n            List<string> assetPaths = new List<string>();\n            foreach (ZipArchiveEntry entry in assetArchive.Entries)\n            {\n                assetPaths.Add(entry.FullName);\n            }\n\n            return assetPaths;\n        }\n    }\n}\n"
  },
  {
    "path": "sources/utils-shared/GithubUpdater.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.IO;\nusing System.IO.Compression;\nusing System.Net;\nusing System.Reflection;\nusing System.Text.RegularExpressions;\n\nnamespace MgAl2O4.Utils\n{\n    public class GithubUpdater\n    {\n        const string UpdateFileNamePart = \"temp-update-v\";\n        const string repoLink = \"https://github.com/MgAl2O4/FFTriadBuddy/\";\n\n        public static bool FindAndApplyUpdates()\n        {\n            string updateFilePath = FindPendingUpdateFile();\n            bool needsUpdate = !string.IsNullOrEmpty(updateFilePath);\n            if (needsUpdate)\n            {\n                ApplyUpdate(updateFilePath);\n            }\n\n            return needsUpdate;\n        }\n\n        public static bool FindAndDownloadUpdates(out string statusMsg)\n        {\n            bool bFoundUpdate = false;\n            try\n            {\n                Version version = Assembly.GetEntryAssembly().GetName().Version;\n                int currentVersion = version.Major;\n                int onlineVersion = FindOnlineVersion(out string downloadLink);\n\n                if (onlineVersion > currentVersion)\n                {\n                    DownloadUpdate(onlineVersion, downloadLink);\n                    statusMsg = \"downloaded update file, version: \" + onlineVersion;\n                    bFoundUpdate = true;\n                }\n                else\n                {\n                    statusMsg = \"program is up to date, online version: \" + onlineVersion;\n                }\n            }\n            catch (Exception ex)\n            {\n                statusMsg = \"failed! \" + ex;\n            }\n\n            return bFoundUpdate;\n        }\n\n        private static int FindOnlineVersion(out string downloadLink)\n        {\n            int latestVersion = 0;\n            downloadLink = \"\";\n\n            WebRequest ReqTree = WebRequest.Create(repoLink + \"releases/latest/\");\n            ReqTree.Timeout = -1;\n\n            WebResponse RespTree = ReqTree.GetResponse();\n            var respAbsUri = RespTree.ResponseUri.AbsoluteUri;\n            var matchPattern = \"/releases/tag/v\";\n            int patternPos = respAbsUri.IndexOf(matchPattern);\n            if (patternPos != 0)\n            {\n                var versionStr = respAbsUri.Substring(patternPos + matchPattern.Length);\n                latestVersion = int.Parse(versionStr);\n\n                downloadLink = $\"releases/download/v{versionStr}/release-v{versionStr}.zip\";\n            }\n\n            return latestVersion;\n        }\n\n        private static void DownloadUpdate(int version, string downloadLink)\n        {\n            string filePath = UpdateFileNamePart + version + \".zip\";\n\n            WebRequest ReqUpdate = WebRequest.Create(repoLink + downloadLink);\n            WebResponse RespUpdate = ReqUpdate.GetResponse();\n            using (Stream dataStream = RespUpdate.GetResponseStream())\n            {\n                FileStream outputFile = new FileStream(filePath, FileMode.Create);\n                dataStream.CopyTo(outputFile);\n                outputFile.Close();\n            }\n        }\n\n        private static void ApplyUpdate(string updateFilePath)\n        {\n            List<string> updateBatchLines = new List<string>();\n\n            string updateExecutableName = \"\";\n            ZipArchive zipArchive = ZipFile.OpenRead(updateFilePath);\n            foreach (ZipArchiveEntry entry in zipArchive.Entries)\n            {\n                if (entry.FullName.EndsWith(\".exe\"))\n                {\n                    updateExecutableName = entry.FullName;\n                }\n            }\n\n            updateBatchLines.Add(\"@echo off\");\n            updateBatchLines.Add(\"echo Waiting for program to finish...\");\n            updateBatchLines.Add(\":loop\");\n            updateBatchLines.Add(\"tasklist | find /i \\\"\" + updateExecutableName + \"\\\" >nul 2>&1\");\n            updateBatchLines.Add(\"if errorlevel 1 ( goto update ) else (\");\n            updateBatchLines.Add(\"  timeout /T 1 /Nobreak\");\n            updateBatchLines.Add(\"  goto loop\");\n            updateBatchLines.Add(\")\");\n            updateBatchLines.Add(\":update\");\n            updateBatchLines.Add(\"echo Updating...\");\n\n            foreach (ZipArchiveEntry entry in zipArchive.Entries)\n            {\n                string newFileName = entry.FullName + \".new\";\n                updateBatchLines.Add(\"move /y \" + newFileName + \" \" + entry.FullName);\n\n                if (File.Exists(newFileName)) { File.Delete(newFileName); }\n                entry.ExtractToFile(newFileName);\n            }\n\n            zipArchive.Dispose();\n            File.Delete(updateFilePath);\n\n            string updateBatchFile = updateFilePath.Replace(\".zip\", \".bat\");\n            updateBatchLines.Add(\"start \" + updateExecutableName);\n            updateBatchLines.Add(\"del /q \" + updateBatchFile);\n\n            File.WriteAllLines(updateBatchFile, updateBatchLines);\n\n            ProcessStartInfo processInfo = new ProcessStartInfo(\"cmd.exe\", \"/c \" + updateBatchFile)\n            {\n                CreateNoWindow = true,\n                UseShellExecute = false\n            };\n\n            Process.Start(processInfo);\n        }\n\n        private static string FindPendingUpdateFile()\n        {\n            string updatePath = null;\n\n            Version version = Assembly.GetEntryAssembly().GetName().Version;\n            int updateVersion = version.Major;\n            Logger.WriteLine(\"Update version check! current:\" + updateVersion);\n\n            try\n            {\n                string[] files = Directory.GetFiles(\".\", UpdateFileNamePart + \"*.zip\");\n                foreach (string path in files)\n                {\n                    string versionStr = Path.GetFileNameWithoutExtension(path).Substring(UpdateFileNamePart.Length);\n                    if (!string.IsNullOrEmpty(versionStr))\n                    {\n                        int versionNum = int.Parse(versionStr);\n                        if (versionNum > updateVersion)\n                        {\n                            Logger.WriteLine(\">> found '\" + path + \"', version: \" + versionNum);\n                            updateVersion = versionNum;\n                            updatePath = path;\n                        }\n                    }\n                }\n            }\n            catch (Exception ex)\n            {\n                Logger.WriteLine(\"Update failed: \" + ex);\n            }\n\n            return updatePath;\n        }\n    }\n}\n"
  },
  {
    "path": "sources/utils-shared/JsonParser.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Globalization;\nusing System.Text;\n\nnamespace MgAl2O4.Utils\n{\n    // really trying to avoid 3rd party dependency dll files...\n    // api replies are simple enough and always valid\n\n    public class JsonParser\n    {\n        public abstract class Value\n        {\n            public override string ToString() { return \"\"; }\n            public virtual string ToExportString() { return ToString(); }\n            public static implicit operator string(Value v) => v.ToString();\n        }\n\n        public class NullValue : Value\n        {\n            public NullValue() { }\n            public override string ToString() { return \"null\"; }\n        }\n\n        public class BoolValue : Value\n        {\n            public bool bFlag;\n\n            public BoolValue(bool bFlag) { this.bFlag = bFlag; }\n            public override string ToString() { return bFlag ? \"true\" : \"false\"; }\n            public static implicit operator bool(BoolValue v) => v.bFlag;\n            public static BoolValue Empty = new BoolValue(false);\n        }\n\n        public class IntValue : Value\n        {\n            public int Number;\n\n            public IntValue(int Num) { Number = Num; }\n            public override string ToString() { return Number.ToString(); }\n            public static implicit operator int(IntValue v) => v.Number;\n            public static IntValue Empty = new IntValue(0);\n        }\n\n        public class FloatValue : Value\n        {\n            public float Number;\n\n            public FloatValue(float Num) { Number = Num; }\n            public override string ToString() { return Number.ToString(); }\n            public static implicit operator float(FloatValue v) => v.Number;\n            public static FloatValue Empty = new FloatValue(0);\n        }\n\n        public class StringValue : Value\n        {\n            public string Str = \"\";\n\n            public StringValue(string Str) { this.Str = Str; }\n            public override string ToString() { return Str; }\n            public override string ToExportString() { return \"\\\"\" + Str + \"\\\"\"; }\n            public static implicit operator string(StringValue v) => (v != null) ? v.Str : null;\n            public static StringValue Empty = new StringValue(\"\");\n        }\n\n        public abstract class ContainerValue : Value\n        {\n            public abstract void Add(string key, Value value);\n        }\n\n        public class ObjectValue : ContainerValue\n        {\n            public Dictionary<string, Value> entries = new Dictionary<string, Value>();\n\n            public override void Add(string key, Value value)\n            {\n                entries.Add(key, value);\n            }\n\n            public override string ToString()\n            {\n                string desc = \"{\";\n                foreach (KeyValuePair<string, Value> kvp in entries)\n                {\n                    desc += \"\\\"\" + kvp.Key + \"\\\":\" + kvp.Value + \", \";\n                }\n\n                if (desc.Length > 1)\n                {\n                    desc = desc.Remove(desc.Length - 2, 2);\n                    desc += \"}\";\n                }\n\n                return desc;\n            }\n\n            public Value this[string key] { get => entries[key]; }\n            public Value this[string key, Value defaultValue] { get => entries.ContainsKey(key) ? entries[key] : defaultValue; }\n            public static ObjectValue Empty = new ObjectValue();\n        }\n\n        public class ArrayValue : ContainerValue\n        {\n            public List<Value> entries = new List<Value>();\n\n            public override void Add(string key, Value value)\n            {\n                entries.Add(value);\n            }\n\n            public override string ToString()\n            {\n                string desc = \"[\";\n                foreach (Value item in entries)\n                {\n                    desc += item + \", \";\n                }\n\n                if (desc.Length > 1)\n                {\n                    desc = desc.Remove(desc.Length - 2, 2);\n                    desc += \"]\";\n                }\n\n                return desc;\n            }\n\n            public Value this[int key] { get => entries[key]; }\n            public static ArrayValue Empty = new ArrayValue();\n        }\n\n        public static ObjectValue ParseJson(string jsonStr)\n        {\n            if (jsonStr != null)\n            {\n                jsonStr = jsonStr.Replace('\\n', ' ');\n                jsonStr = jsonStr.Replace('\\r', ' ');\n                jsonStr = jsonStr.Replace('\\t', ' ');\n            }\n\n            if (string.IsNullOrEmpty(jsonStr))\n            {\n                return null;\n            }\n\n            List<Tuple<string, ContainerValue>> containerStack = new List<Tuple<string, ContainerValue>>();\n            ContainerValue activeContainer = null;\n            Value activeValue = null;\n            string activeKey = \"\";\n            string strCurrent = \"\";\n\n            bool bHasOpenedString = false;\n            bool bHasStringValue = false;\n            for (int Idx = 0; Idx < jsonStr.Length; Idx++)\n            {\n                if (jsonStr[Idx] == '\"')\n                {\n                    bHasOpenedString = !bHasOpenedString;\n                    bHasStringValue = true;\n                    continue;\n                }\n\n                if (bHasOpenedString)\n                {\n                    strCurrent += jsonStr[Idx];\n                    continue;\n                }\n\n                if (jsonStr[Idx] == ',' || jsonStr[Idx] == ']' || jsonStr[Idx] == '}')\n                {\n                    if (activeValue == null)\n                    {\n                        strCurrent = strCurrent.Trim();\n                        if (strCurrent.Length > 0)\n                        {\n                            try\n                            {\n                                if (bHasStringValue)\n                                {\n                                    activeValue = new StringValue(strCurrent);\n                                }\n                                else if (strCurrent.Equals(\"null\", StringComparison.InvariantCultureIgnoreCase))\n                                {\n                                    activeValue = new NullValue();\n                                }\n                                else if (strCurrent.Equals(\"true\", StringComparison.InvariantCultureIgnoreCase))\n                                {\n                                    activeValue = new BoolValue(true);\n                                }\n                                else if (strCurrent.Equals(\"false\", StringComparison.InvariantCultureIgnoreCase))\n                                {\n                                    activeValue = new BoolValue(false);\n                                }\n                                else if (strCurrent.Contains(\".\"))\n                                {\n                                    activeValue = new FloatValue(float.Parse(strCurrent, CultureInfo.InvariantCulture));\n                                }\n                                else\n                                {\n                                    activeValue = new IntValue(int.Parse(strCurrent));\n                                }\n                            }\n                            catch (Exception ex)\n                            {\n                                int snipStart = Math.Max(0, Idx - 20);\n                                int snipEnd = Math.Min(Idx + 20, jsonStr.Length - 1);\n\n                                Logger.WriteLine(\"Json parsing failed: unknown value '{0}'! (pos:{1}, container:{2}) snip[{3}...{4}]:'{5}', ex:{6}\",\n                                    strCurrent,\n                                    Idx,\n                                    containerStack.Count > 0 ? containerStack[containerStack.Count - 1].Item1 : \"??\",\n                                    snipStart, snipEnd, jsonStr.Substring(snipStart, snipEnd - snipStart),\n                                    ex);\n                            }\n                        }\n                    }\n\n                    if (activeContainer != null && activeValue != null)\n                    {\n                        bool needsKey = activeContainer is JsonParser.ObjectValue;\n                        if (needsKey && string.IsNullOrEmpty(activeKey))\n                        {\n                            int snipStart = Math.Max(0, Idx - 20);\n                            int snipEnd = Math.Min(Idx + 20, jsonStr.Length - 1);\n\n                            Logger.WriteLine(\"Json parsing failed: Key is missing! (pos:{0}, container:{1}, value:{2}) snip[{3}...{4}]:'{5}'\",\n                                Idx,\n                                containerStack.Count > 0 ? containerStack[containerStack.Count - 1].Item1 : \"??\",\n                                activeValue.ToString(),\n                                snipStart, snipEnd, jsonStr.Substring(snipStart, snipEnd - snipStart));\n                        }\n                        else\n                        {\n                            activeContainer.Add(activeKey, activeValue);\n                        }\n                    }\n\n                    activeValue = null;\n                    activeKey = null;\n                    strCurrent = \"\";\n                    bHasStringValue = false;\n                }\n\n                switch (jsonStr[Idx])\n                {\n                    case '{':\n                        activeContainer = new ObjectValue();\n                        containerStack.Add(new Tuple<string, ContainerValue>(activeKey, activeContainer));\n                        activeKey = null;\n                        break;\n\n                    case '[':\n                        activeContainer = new ArrayValue();\n                        containerStack.Add(new Tuple<string, ContainerValue>(activeKey, activeContainer));\n                        activeKey = null;\n                        break;\n\n                    case '}':\n                    case ']':\n                        activeValue = activeContainer;\n                        activeKey = containerStack[containerStack.Count - 1].Item1;\n                        containerStack.RemoveAt(containerStack.Count - 1);\n                        activeContainer = (containerStack.Count == 0) ? null : containerStack[containerStack.Count - 1].Item2;\n                        break;\n\n                    case ':':\n                        activeKey = strCurrent.Trim();\n                        strCurrent = \"\";\n                        bHasStringValue = false;\n                        break;\n\n                    case ',':\n                        break;\n\n                    default:\n                        strCurrent += jsonStr[Idx];\n                        break;\n                }\n            }\n\n            return activeValue as ObjectValue;\n        }\n    }\n\n    public class JsonWriter\n    {\n        private StringBuilder builder = new StringBuilder();\n        private Stack<char> containerStack = new Stack<char>();\n        private bool bNeedsSeparator = false;\n\n        public override string ToString()\n        {\n            return builder.ToString();\n        }\n\n        public void WriteRawValue(string value, string key = null)\n        {\n            WriteKey(key);\n            if (bNeedsSeparator) { builder.Append(','); bNeedsSeparator = false; }\n            builder.Append(value);\n            bNeedsSeparator = true;\n        }\n\n        public void WriteString(string value, string key = null)\n        {\n            WriteRawValue(\"\\\"\" + value + \"\\\"\", key);\n        }\n\n        public void WriteBool(bool value, string key = null)\n        {\n            WriteRawValue(value ? \"true\" : \"false\", key);\n        }\n\n        public void WriteInt(int value, string key = null)\n        {\n            WriteRawValue(value.ToString(), key);\n        }\n\n        public void WriteFloat(float value, string key = null)\n        {\n            var strValue = value.ToString();\n\n            if (strValue.Contains(\",\"))\n            {\n                strValue = strValue.Replace(',', '.');\n            }\n\n            var sep = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;\n            if (sep != \".\" && sep != \",\")\n            {\n                strValue = strValue.Replace(sep, \".\");\n            }\n\n            WriteRawValue(strValue, key);\n        }\n\n        public void WriteNull(string key = null)\n        {\n            WriteRawValue(\"null\", key);\n        }\n\n        public void WriteObjectStart(string key = null)\n        {\n            WriteKey(key);\n            if (bNeedsSeparator) { builder.Append(','); bNeedsSeparator = false; }\n\n            builder.Append('{');\n            containerStack.Push('}');\n        }\n\n        public void WriteObjectEnd()\n        {\n            builder.Append(containerStack.Pop());\n            bNeedsSeparator = true;\n        }\n\n        public void WriteArrayStart(string key = null)\n        {\n            WriteKey(key);\n            if (bNeedsSeparator) { builder.Append(','); bNeedsSeparator = false; }\n\n            builder.Append('[');\n            containerStack.Push(']');\n        }\n\n        public void WriteArrayEnd()\n        {\n            builder.Append(containerStack.Pop());\n            bNeedsSeparator = true;\n        }\n\n        private void WriteKey(string key = null)\n        {\n            if (bNeedsSeparator) { builder.Append(','); bNeedsSeparator = false; }\n            if (!string.IsNullOrEmpty(key))\n            {\n                builder.Append('\\\"');\n                builder.Append(key);\n                builder.Append(\"\\\":\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/utils-shared/LocResourceManager.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Globalization;\nusing System.IO;\nusing System.Reflection;\nusing System.Xml;\n\nnamespace MgAl2O4.Utils\n{\n    public class LocResourceManager\n    {\n        private static LocResourceManager instance = new LocResourceManager();\n        private Dictionary<string, string> strings = new Dictionary<string, string>();\n\n        private Dictionary<string, string> mapCultureAssets = new Dictionary<string, string>();\n        private string neutralCultureAsset;\n\n        private readonly string neutralCultureCode = \"en\"; // actual language used in culture neutral file: loc/strings.resx\n        private string userCultureCode;\n        public string UserCultureCode => string.IsNullOrEmpty(userCultureCode) ? neutralCultureCode : userCultureCode;\n\n        public readonly string[] SupportedCultureCodes;\n\n        public LocResourceManager()\n        {\n            var listAssets = AssetManager.Get().ListAssets();\n            foreach (var assetPath in listAssets)\n            {\n                if (assetPath.EndsWith(\".resx\"))\n                {\n                    string fileName = Path.GetFileName(assetPath).Replace(\".resx\", \"\");\n                    int sepPos = fileName.IndexOf('.');\n                    if (sepPos > 0)\n                    {\n                        string locCode = fileName.Substring(sepPos + 1);\n                        mapCultureAssets.Add(locCode, assetPath);\n                    }\n                    else\n                    {\n                        // default to neutral culture resx\n                        neutralCultureAsset = assetPath;\n                    }\n                }\n            }\n\n            var allCodes = new List<string>();\n            allCodes.Add(neutralCultureCode);\n            allCodes.AddRange(mapCultureAssets.Keys);\n            allCodes.Sort();\n\n            SupportedCultureCodes = allCodes.ToArray();\n        }\n\n        public static LocResourceManager Get()\n        {\n            return instance;\n        }\n\n        public string FindString(string key, bool defaultToNull = false)\n        {\n            string resultStr = null;\n            if (!strings.TryGetValue(key, out resultStr))\n            {\n                if (!defaultToNull)\n                {\n                    resultStr = string.Format(\"--LOC:{0}--\", key);\n                }\n            }\n\n            return resultStr;\n        }\n\n        public void SetCurrentUserLanguage(CultureInfo cultureInfo, Type stringContainerType)\n        {\n            string firstKnownMatch = null;\n\n            var loadList = new List<string>();\n            while (cultureInfo != null && cultureInfo != CultureInfo.InvariantCulture && cultureInfo.Name.Length > 0)\n            {\n                if (mapCultureAssets.TryGetValue(cultureInfo.Name, out string assetPath))\n                {\n                    Logger.WriteLine(\"Found localization resource for: {0}\", cultureInfo.Name);\n                    loadList.Add(assetPath);\n\n                    if (firstKnownMatch == null)\n                    {\n                        firstKnownMatch = cultureInfo.Name;\n                    }\n                }\n\n                cultureInfo = cultureInfo.Parent;\n            }\n\n            // always include neutral culture for fallback\n            loadList.Add(neutralCultureAsset);\n\n            LoadResourceHierarchy(loadList);\n            CopyToStringContainer(stringContainerType);\n\n            userCultureCode = firstKnownMatch;\n        }\n\n        public void LoadResourceHierarchy(List<string> paths)\n        {\n            strings.Clear();\n            foreach (var path in paths)\n            {\n                LoadResource(path);\n            }\n        }\n\n        public void LoadResource(string path)\n        {\n            try\n            {\n                XmlDocument xdoc = new XmlDocument();\n                xdoc.Load(AssetManager.Get().GetAsset(path));\n\n                foreach (XmlNode testNode in xdoc.DocumentElement.ChildNodes)\n                {\n                    XmlElement testElem = testNode as XmlElement;\n                    if (testElem != null && testElem.Name == \"data\")\n                    {\n                        try\n                        {\n                            string keyStr = testElem.GetAttribute(\"name\");\n                            if (!strings.ContainsKey(keyStr))\n                            {\n                                foreach (XmlNode innerNode in testElem.ChildNodes)\n                                {\n                                    XmlElement valueElem = innerNode as XmlElement;\n                                    if (valueElem != null)\n                                    {\n                                        strings.Add(keyStr, valueElem.InnerText);\n                                        break;\n                                    }\n                                }\n                            }\n                        }\n                        catch (Exception ex)\n                        {\n                            Logger.WriteLine(\"Loading failed! Exception:\" + ex);\n                        }\n                    }\n                }\n            }\n            catch (Exception ex)\n            {\n                Logger.WriteLine(\"Loading failed! Exception:\" + ex);\n            }\n        }\n\n        public void CopyToStringContainer(Type stringContainerType)\n        {\n            var allFields = stringContainerType.GetFields(BindingFlags.Public | BindingFlags.Static);\n            foreach (var field in allFields)\n            {\n                if (field.FieldType == typeof(string))\n                {\n                    field.SetValue(null, FindString(field.Name));\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "sources/utils-shared/Logger.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Reflection;\n\nnamespace MgAl2O4.Utils\n{\n    public class Logger\n    {\n        private static StreamWriter logWriter;\n        private static StreamWriter logWriterDefault;\n\n        private static bool isSuperVerbose = false;\n\n        public static void Initialize(string[] Args)\n        {\n            foreach (string cmdArg in Args)\n            {\n                if (cmdArg == \"-log\")\n                {\n                    logWriter = new StreamWriter(\"debugLog.txt\");\n                }\n                else if (cmdArg == \"-verbose\")\n                {\n                    isSuperVerbose = true;\n                }\n            }\n\n            try\n            {\n                string appName = Assembly.GetEntryAssembly().GetName().Name;\n                string settingsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), appName);\n                Directory.CreateDirectory(settingsPath);\n\n                logWriterDefault = new StreamWriter(Path.Combine(settingsPath, \"outputLog.txt\"));\n            }\n            catch (Exception) { }\n        }\n\n        public static void Close()\n        {\n            logWriterDefault.Close();\n        }\n\n        public static bool IsActive()\n        {\n            return logWriter != null;\n        }\n\n        public static bool IsSuperVerbose()\n        {\n            return isSuperVerbose;\n        }\n\n        public static void WriteLine(string str)\n        {\n            if (isSuperVerbose)\n            {\n                str = DateTime.Now.ToString(\"hh:mm:ss.fff\") + \": \" + str;\n            }\n\n            Console.WriteLine(str);\n\n            if (logWriter != null)\n            {\n                logWriter.WriteLine(str);\n                logWriter.Flush();\n            }\n\n            if (logWriterDefault != null)\n            {\n                logWriterDefault.WriteLine(str);\n            }\n        }\n\n        public static void WriteLine(string fmt, params object[] args)\n        {\n            WriteLine(string.Format(fmt, args));\n        }\n    }\n}\n"
  },
  {
    "path": "sources/utils-shared/MLDataExporter.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Text;\n\nnamespace MgAl2O4.Utils\n{\n    public class MLDataExporter\n    {\n        private StringBuilder exportSB = new StringBuilder();\n        public string exportPath;\n\n        public void StartDataExport(string logDesc)\n        {\n            Logger.WriteLine(\"Starting ML export for: {0}...\", logDesc);\n\n            exportSB.Clear();\n            exportSB.Append(\"{\\\"dataset\\\":[\");\n        }\n\n        public void FinishDataExport(string fileName)\n        {\n            var exportJson = exportSB.ToString();\n            exportSB.Clear();\n\n            exportJson = exportJson.Remove(exportJson.Length - 1, 1);\n            exportJson += \"\\n]}\";\n\n            try\n            {\n                Directory.CreateDirectory(exportPath);\n                File.WriteAllText(Path.Combine(exportPath, fileName), exportJson);\n            }\n            catch (Exception ex)\n            {\n                Logger.WriteLine(\"Failed! Exception: {0}\", ex);\n            }\n        }\n\n        public void ExportValues(float[] values, int classId)\n        {\n            exportSB.Append(\"\\n{\\\"input\\\":[\");\n            exportSB.Append(string.Join(\",\", values));\n            exportSB.Append(\"], \\\"output\\\":\");\n            exportSB.Append(classId);\n            exportSB.Append(\"},\");\n        }\n\n        public void ExportValuesWithContext(float[] values, int classId, int contextId)\n        {\n            exportSB.Append(\"\\n{\\\"input\\\":[\");\n            exportSB.Append(string.Join(\",\", values));\n            exportSB.Append(\"], \\\"output\\\":\");\n            exportSB.Append(classId);\n            exportSB.Append(\", \\\"ctx\\\":\");\n            exportSB.Append(contextId);\n            exportSB.Append(\"},\");\n        }\n    }\n}\n"
  },
  {
    "path": "sources/utils-shared/MLUtils.cs",
    "content": "﻿using System;\n\nnamespace MgAl2O4.Utils\n{\n    public class MLUtils\n    {\n        public static void CalcNetworkLayer(float[] input, float[] output, float[] weights, float[] biases)\n        {\n            for (int idxO = 0; idxO < output.Length; idxO++)\n            {\n                output[idxO] = biases[idxO];\n                for (int idxI = 0; idxI < input.Length; idxI++)\n                {\n                    int midx = (idxI * output.Length) + idxO;\n                    output[idxO] += input[idxI] * weights[midx];\n                }\n            }\n        }\n\n        public static void ApplySigmoid(float[] arr)\n        {\n            for (int idx = 0; idx < arr.Length; idx++)\n            {\n                arr[idx] = (float)(1 / (1 + Math.Exp(-arr[idx])));\n            }\n        }\n\n        public static void ApplyRelu(float[] arr)\n        {\n            for (int idx = 0; idx < arr.Length; idx++)\n            {\n                if (arr[idx] < 0)\n                {\n                    arr[idx] = 0.0f;\n                }\n            }\n        }\n\n        public static void ApplySoftmax(float[] arr)\n        {\n            float maxV = arr[0];\n            for (int idx = 1; idx < arr.Length; idx++)\n            {\n                if (maxV < arr[idx])\n                {\n                    maxV = arr[idx];\n                }\n            }\n\n            float sumV = 0.0f;\n            for (int idx = 0; idx < arr.Length; idx++)\n            {\n                arr[idx] = (float)Math.Exp(arr[idx] - maxV);\n                sumV += arr[idx];\n            }\n\n            if (sumV == 0.0) { sumV = 0.001f; }\n            for (int idx = 0; idx < arr.Length; idx++)\n            {\n                arr[idx] /= sumV;\n            }\n        }\n\n        public static int PickHighestProbability(float[] arr, out float Pct)\n        {\n            Pct = arr[0];\n            int bestIdx = 0;\n\n            for (int idx = 1; idx < arr.Length; idx++)\n            {\n                if (Pct < arr[idx])\n                {\n                    Pct = arr[idx];\n                    bestIdx = idx;\n                }\n            }\n\n            return bestIdx;\n        }\n    }\n}\n"
  },
  {
    "path": "sources/vision/ImageUtils.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\nusing System.Drawing;\nusing System.Drawing.Imaging;\n\nnamespace FFTriadBuddy\n{\n    public struct FastPixelHSV\n    {\n        private byte HuePack;\n        private byte SaturationPack;\n        public byte Value;\n        public byte Monochrome;\n\n        public byte RawR;\n        public byte RawG;\n        public byte RawB;\n        public byte HasHSL;\n\n        public FastPixelHSV(byte colorR, byte colorG, byte colorB)\n        {\n            HuePack = 0;\n            SaturationPack = 0;\n            Value = 0;\n            Monochrome = 0;\n            RawR = colorR;\n            RawG = colorG;\n            RawB = colorB;\n            HasHSL = 0;\n        }\n\n        public FastPixelHSV(bool patternMatch)\n        {\n            HuePack = 0;\n            SaturationPack = 0;\n            Value = 0;\n            Monochrome = patternMatch ? (byte)255 : (byte)0;\n            RawR = Monochrome;\n            RawG = Monochrome;\n            RawB = Monochrome;\n            HasHSL = 1;\n        }\n\n        public int GetHue()\n        {\n            return ((int)(SaturationPack & 0x80) << 1) | HuePack;\n        }\n\n        public int GetSaturation()\n        {\n            return SaturationPack & 0x7f;\n        }\n\n        public int GetValue()\n        {\n            return Value;\n        }\n\n        public int GetMonochrome()\n        {\n            return Monochrome;\n        }\n\n        public void SetHSV(int hue, int saturation, int value)\n        {\n            HuePack = (byte)(hue & 0xff);\n            SaturationPack = (byte)((saturation & 0x7f) | ((hue & 0x100) >> 1));\n            Value = (byte)value;\n\n            ImageUtils.HsvToRgb(hue, saturation, value, out int colorR, out int colorG, out int colorB);\n            Monochrome = (byte)Math.Round((0.2125 * colorR) + (0.7154 * colorG) + (0.0721 * colorB));\n            RawR = (byte)colorR;\n            RawG = (byte)colorG;\n            RawB = (byte)colorB;\n            HasHSL = 1;\n        }\n\n        public void ExpandHSV()\n        {\n            float LinearR = (RawR / 255f);\n            float LinearG = (RawG / 255f);\n            float LinearB = (RawB / 255f);\n\n            float MinRG = (LinearR < LinearG) ? LinearR : LinearG;\n            float MaxRG = (LinearR > LinearG) ? LinearR : LinearG;\n            float MinV = (MinRG < LinearB) ? MinRG : LinearB;\n            float MaxV = (MaxRG > LinearB) ? MaxRG : LinearB;\n            float DeltaV = MaxV - MinV;\n\n            float H = 0;\n            float S = 0;\n            float L = (float)((MaxV + MinV) / 2.0f);\n\n            if (DeltaV != 0)\n            {\n                if (L < 0.5f)\n                {\n                    S = (float)(DeltaV / (MaxV + MinV));\n                }\n                else\n                {\n                    S = (float)(DeltaV / (2.0f - MaxV - MinV));\n                }\n\n                if (LinearR == MaxV)\n                {\n                    H = (LinearG - LinearB) / DeltaV;\n                }\n                else if (LinearG == MaxV)\n                {\n                    H = 2f + (LinearB - LinearR) / DeltaV;\n                }\n                else if (LinearB == MaxV)\n                {\n                    H = 4f + (LinearR - LinearG) / DeltaV;\n                }\n            }\n\n            int HueV = (int)(H * 60f);\n            if (HueV < 0) HueV += 360;\n            int SaturationV = (int)(S * 100f);\n            int LightV = (int)(L * 100f);\n\n            HuePack = (byte)(HueV & 0xff);\n            SaturationPack = (byte)((SaturationV & 0x7f) | ((HueV & 0x100) >> 1));\n            Value = (byte)LightV;\n            Monochrome = (byte)((0.2125 * RawR) + (0.7154 * RawG) + (0.0721 * RawB));\n            HasHSL = 1;\n        }\n\n        public override string ToString()\n        {\n            return \"H:\" + GetHue() + \", S:\" + GetSaturation() + \", V:\" + GetValue() + \", M:\" + GetMonochrome();\n        }\n    };\n\n    public abstract class FastPixelMatch\n    {\n        public abstract bool IsMatching(FastPixelHSV pixel);\n    }\n\n    public class FastPixelMatchHSV : FastPixelMatch\n    {\n        private short HueMin;\n        private short HueMax;\n        private byte SaturationMin;\n        private byte SaturationMax;\n        private byte ValueMin;\n        private byte ValueMax;\n\n        public FastPixelMatchHSV(short hueMin, short hueMax, byte saturationMin, byte saturationMax, byte valueMin, byte valueMax)\n        {\n            HueMin = hueMin;\n            HueMax = hueMax;\n            SaturationMin = saturationMin;\n            SaturationMax = saturationMax;\n            ValueMin = valueMin;\n            ValueMax = valueMax;\n        }\n\n        public override bool IsMatching(FastPixelHSV pixel)\n        {\n            int Hue = pixel.GetHue();\n            int Saturation = pixel.GetSaturation();\n            return (Hue >= HueMin) && (Hue <= HueMax) &&\n                (Saturation >= SaturationMin) && (Saturation <= SaturationMax) &&\n                (pixel.Value >= ValueMin) && (pixel.Value <= ValueMax);\n        }\n\n        public override string ToString()\n        {\n            return \"Hue:\" + HueMin + \"..\" + HueMax +\n                \", Saturation:\" + SaturationMin + \"..\" + SaturationMax +\n                \", Value:\" + ValueMin + \"..\" + ValueMax;\n        }\n    }\n\n    public class FastPixelMatchMono : FastPixelMatch\n    {\n        private byte MonoMin;\n        private byte MonoMax;\n\n        public FastPixelMatchMono(byte monoMin, byte monoMax)\n        {\n            MonoMin = monoMin;\n            MonoMax = monoMax;\n        }\n\n        public override bool IsMatching(FastPixelHSV pixel)\n        {\n            return (pixel.Monochrome >= MonoMin) && (pixel.Monochrome <= MonoMax);\n        }\n\n        public override string ToString()\n        {\n            return \"Mono:\" + MonoMin + \"..\" + MonoMax;\n        }\n    }\n\n    public class FastPixelMatchHueMono : FastPixelMatch\n    {\n        private short HueMin;\n        private short HueMax;\n        private byte MonoMin;\n        private byte MonoMax;\n\n        public FastPixelMatchHueMono(short hueMin, short hueMax, byte monoMin, byte monoMax)\n        {\n            HueMin = hueMin;\n            HueMax = hueMax;\n            MonoMin = monoMin;\n            MonoMax = monoMax;\n        }\n\n        public override bool IsMatching(FastPixelHSV pixel)\n        {\n            if ((pixel.Monochrome >= MonoMin) && (pixel.Monochrome <= MonoMax))\n            {\n                int Hue = pixel.GetHue();\n                return (Hue >= HueMin) && (Hue <= HueMax);\n            }\n\n            return false;\n        }\n\n        public override string ToString()\n        {\n            return \"Hue:\" + HueMin + \"..\" + HueMax + \", Mono:\" + MonoMin + \"..\" + MonoMax;\n        }\n    }\n\n    public class FastBitmapHSV\n    {\n        public FastPixelHSV[] Pixels;\n        public int Width;\n        public int Height;\n\n        public FastPixelHSV GetPixel(int X, int Y)\n        {\n            int idx = X + (Y * Width);\n            if (Pixels[idx].HasHSL == 0)\n            {\n                Pixels[idx].ExpandHSV();\n            }\n\n            return Pixels[idx];\n        }\n\n        public FastPixelHSV GetPixelRaw(int X, int Y)\n        {\n            return Pixels[X + (Y * Width)];\n        }\n\n        public void SetPixel(int X, int Y, FastPixelHSV pixel)\n        {\n            Pixels[X + (Y * Width)] = pixel;\n        }\n\n        public void SetPixel(int Idx, FastPixelHSV pixel)\n        {\n            Pixels[Idx] = pixel;\n        }\n\n        public override string ToString()\n        {\n            return \"FastBitmap \" + Width + \"x\" + Height;\n        }\n    }\n\n    public class ImageUtils\n    {\n        public struct HashPreview\n        {\n            public Rectangle bounds;\n            public float[] hashValues;\n        }\n\n        public static FastBitmapHSV ConvertToFastBitmap(Bitmap image)\n        {\n            FastBitmapHSV result = new FastBitmapHSV();\n            result.Width = image.Width;\n            result.Height = image.Height;\n            result.Pixels = new FastPixelHSV[image.Width * image.Height];\n\n            unsafe\n            {\n                BitmapData bitmapData = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, image.PixelFormat);\n                int bytesPerPixel = Image.GetPixelFormatSize(image.PixelFormat) / 8;\n                int bytesPerRow = bitmapData.Width * bytesPerPixel;\n\n                for (int IdxY = 0; IdxY < image.Height; IdxY++)\n                {\n                    byte* pixels = (byte*)bitmapData.Scan0 + (IdxY * bitmapData.Stride);\n                    int IdxPixel = IdxY * image.Width;\n                    for (int IdxByte = 0; IdxByte < bytesPerRow; IdxByte += bytesPerPixel)\n                    {\n                        result.SetPixel(IdxPixel, new FastPixelHSV(pixels[IdxByte + 2], pixels[IdxByte + 1], pixels[IdxByte]));\n                        IdxPixel++;\n                    }\n                }\n\n                image.UnlockBits(bitmapData);\n            }\n\n            return result;\n        }\n\n        public static Bitmap ConvertToBitmap(FastBitmapHSV bitmap)\n        {\n            Bitmap bmp = new Bitmap(bitmap.Width, bitmap.Height);\n            unsafe\n            {\n                BitmapData bitmapData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat);\n                int bytesPerPixel = Image.GetPixelFormatSize(bmp.PixelFormat) / 8;\n                int bytesPerRow = bitmapData.Width * bytesPerPixel;\n\n                for (int IdxY = 0; IdxY < bmp.Height; IdxY++)\n                {\n                    byte* pixels = (byte*)bitmapData.Scan0 + (IdxY * bitmapData.Stride);\n                    for (int IdxByte = 0; IdxByte < bytesPerRow; IdxByte += bytesPerPixel)\n                    {\n                        FastPixelHSV writePx = bitmap.GetPixel(IdxByte / bytesPerPixel, IdxY);\n                        Color writeColor = Color.FromArgb(writePx.Monochrome, writePx.Monochrome, writePx.Monochrome);\n                        pixels[IdxByte + 3] = writeColor.A;\n                        pixels[IdxByte + 2] = writeColor.R;\n                        pixels[IdxByte + 1] = writeColor.G;\n                        pixels[IdxByte + 0] = writeColor.B;\n                    }\n                }\n\n                bmp.UnlockBits(bitmapData);\n            }\n\n            return bmp;\n        }\n\n        public static void DrawDebugShapes(Bitmap bitmap, List<Rectangle> bounds)\n        {\n            if (bounds.Count > 0)\n            {\n                using (Graphics gBmp = Graphics.FromImage(bitmap))\n                {\n                    Pen boundsPen = new Pen(Color.Cyan);\n                    gBmp.DrawRectangles(boundsPen, bounds.ToArray());\n                }\n            }\n        }\n        public static void DrawDebugHashes(Bitmap bitmap, List<HashPreview> hashes)\n        {\n            if (hashes.Count > 0)\n            {\n                unsafe\n                {\n                    BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.WriteOnly, bitmap.PixelFormat);\n                    int bytesPerPixel = Image.GetPixelFormatSize(bitmap.PixelFormat) / 8;\n\n                    foreach (var hashPreview in hashes)\n                    {\n                        for (int IdxY = 0; IdxY < hashPreview.bounds.Height; IdxY++)\n                        {\n                            byte* pixels = (byte*)bitmapData.Scan0 + ((hashPreview.bounds.Y + IdxY) * bitmapData.Stride) + (hashPreview.bounds.X * bytesPerPixel);\n\n                            for (int IdxX = 0; IdxX < hashPreview.bounds.Width; IdxX++)\n                            {\n                                float hashValue = hashPreview.hashValues[IdxX + (IdxY * hashPreview.bounds.Width)];\n\n                                int IdxByte = IdxX * bytesPerPixel;\n                                pixels[IdxByte + 3] = 255;\n                                pixels[IdxByte + 2] = (byte)(255 * hashValue);\n                                pixels[IdxByte + 1] = 0;\n                                pixels[IdxByte + 0] = (byte)(255 * hashValue);\n                            }\n                        }\n                    }\n\n\n                    bitmap.UnlockBits(bitmapData);\n                }\n            }\n        }\n\n        public static void DrawDebugHash(Bitmap bitmap, HashPreview hashPreview, Color color)\n        {\n            unsafe\n            {\n                BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.WriteOnly, bitmap.PixelFormat);\n                int bytesPerPixel = Image.GetPixelFormatSize(bitmap.PixelFormat) / 8;\n\n                for (int IdxY = 0; IdxY < hashPreview.bounds.Height; IdxY++)\n                {\n                    byte* pixels = (byte*)bitmapData.Scan0 + ((hashPreview.bounds.Y + IdxY) * bitmapData.Stride) + (hashPreview.bounds.X * bytesPerPixel);\n\n                    for (int IdxX = 0; IdxX < hashPreview.bounds.Width; IdxX++)\n                    {\n                        float hashValue = hashPreview.hashValues[IdxX + (IdxY * hashPreview.bounds.Width)];\n\n                        int IdxByte = IdxX * bytesPerPixel;\n                        pixels[IdxByte + 3] = 255;\n                        pixels[IdxByte + 2] = (byte)(color.R * hashValue);\n                        pixels[IdxByte + 1] = (byte)(color.G * hashValue);\n                        pixels[IdxByte + 0] = (byte)(color.B * hashValue);\n                    }\n                }\n\n                bitmap.UnlockBits(bitmapData);\n            }\n        }\n\n        public static Color GetColorFromHSV(FastPixelHSV pixel)\n        {\n            int R = 0, G = 0, B = 0;\n            HsvToRgb(pixel.GetHue(), pixel.GetSaturation() / 100.0, pixel.GetValue() / 100.0, out R, out G, out B);\n            return Color.FromArgb(R, G, B);\n        }\n\n        public static void HsvToRgb(double h, double S, double V, out int r, out int g, out int b)\n        {\n            double H = h;\n            while (H < 0) { H += 360; };\n            while (H >= 360) { H -= 360; };\n            double R, G, B;\n            if (V <= 0)\n            { R = G = B = 0; }\n            else if (S <= 0)\n            {\n                R = G = B = V;\n            }\n            else\n            {\n                double hf = H / 60.0;\n                int i = (int)Math.Floor(hf);\n                double f = hf - i;\n                double pv = V * (1 - S);\n                double qv = V * (1 - S * f);\n                double tv = V * (1 - S * (1 - f));\n                switch (i)\n                {\n\n                    // Red is the dominant color\n\n                    case 0:\n                        R = V;\n                        G = tv;\n                        B = pv;\n                        break;\n\n                    // Green is the dominant color\n\n                    case 1:\n                        R = qv;\n                        G = V;\n                        B = pv;\n                        break;\n                    case 2:\n                        R = pv;\n                        G = V;\n                        B = tv;\n                        break;\n\n                    // Blue is the dominant color\n\n                    case 3:\n                        R = pv;\n                        G = qv;\n                        B = V;\n                        break;\n                    case 4:\n                        R = tv;\n                        G = pv;\n                        B = V;\n                        break;\n\n                    // Red is the dominant color\n\n                    case 5:\n                        R = V;\n                        G = pv;\n                        B = qv;\n                        break;\n\n                    // Just in case we overshoot on our math by a little, we put these here. Since its a switch it won't slow us down at all to put these here.\n\n                    case 6:\n                        R = V;\n                        G = tv;\n                        B = pv;\n                        break;\n                    case -1:\n                        R = V;\n                        G = pv;\n                        B = qv;\n                        break;\n\n                    // The color is not defined, we should throw an error.\n\n                    default:\n                        //LFATAL(\"i Value error in Pixel conversion, Value is %d\", i);\n                        R = G = B = V; // Just pretend its black/white\n                        break;\n                }\n            }\n            r = Math.Max(0, Math.Min(255, ((int)(R * 255.0))));\n            g = Math.Max(0, Math.Min(255, ((int)(G * 255.0))));\n            b = Math.Max(0, Math.Min(255, ((int)(B * 255.0))));\n        }\n\n        public static List<int> TraceLineSegments(FastBitmapHSV bitmap, int posX, int posY, int incX, int incY, int traceLen,\n            FastPixelMatch colorMatch, int minSegSize, int segLimit, bool bDebugMode = false)\n        {\n            FastPixelHSV[] streakBuffer = new FastPixelHSV[minSegSize];\n            int bufferIdx = 0;\n            for (int Idx = 0; Idx < streakBuffer.Length; Idx++) { streakBuffer[Idx] = new FastPixelHSV(255, 255, 255); }\n\n            List<int> result = new List<int>();\n            bool bWasMatch = false;\n\n            if (bDebugMode) { Logger.WriteLine(\"TraceLineSegments [\" + posX + \", \" + posY + \"] -> [\" + (posX + (incX * traceLen)) + \", \" + (posY + (incY * traceLen)) + \"]\"); }\n\n            for (int stepIdx = 0; stepIdx < traceLen; stepIdx++)\n            {\n                int scanX = posX + (stepIdx * incX);\n                int scanY = posY + (stepIdx * incY);\n                FastPixelHSV testPx = bitmap.GetPixel(scanX, scanY);\n\n                streakBuffer[bufferIdx] = testPx;\n                bufferIdx = (bufferIdx + 1) % minSegSize;\n\n                bool bBufferMatching = true;\n                for (int Idx = 0; Idx < streakBuffer.Length; Idx++)\n                {\n                    bBufferMatching = bBufferMatching && colorMatch.IsMatching(streakBuffer[Idx]);\n                }\n\n                if (bDebugMode) { Logger.WriteLine(\"  [\" + scanX + \", \" + scanY + \"] \" + testPx + \" => match:\" + colorMatch.IsMatching(testPx) + \", buffer:\" + bBufferMatching); }\n\n                if (bBufferMatching != bWasMatch)\n                {\n                    bWasMatch = bBufferMatching;\n\n                    int segPos = bBufferMatching ?\n                        (incX != 0) ? (scanX - (incX * minSegSize)) : (scanY - (incY * minSegSize)) :\n                        (incX != 0) ? scanX : scanY;\n\n                    result.Add(segPos);\n                    if (bDebugMode) { Logger.WriteLine(\"  >> mark segment:\" + segPos); }\n\n                    if (result.Count >= segLimit && segLimit > 0)\n                    {\n                        break;\n                    }\n                }\n            }\n\n            return result;\n        }\n\n        public static bool TraceLine(FastBitmapHSV bitmap, int posX, int posY, int incX, int incY, int traceLen, FastPixelMatch colorMatch, out Point posHit, bool bDebugMode = false)\n        {\n            if (bDebugMode) { Logger.WriteLine(\"TraceLine [\" + posX + \", \" + posY + \"] -> [\" + (posX + (incX * traceLen)) + \", \" + (posY + (incY * traceLen)) + \"]\"); }\n\n            for (int stepIdx = 0; stepIdx < traceLen; stepIdx++)\n            {\n                int scanX = posX + (stepIdx * incX);\n                int scanY = posY + (stepIdx * incY);\n                FastPixelHSV testPx = bitmap.GetPixel(scanX, scanY);\n                bool bIsMatching = colorMatch.IsMatching(testPx);\n\n                if (bDebugMode) { Logger.WriteLine(\"  [\" + scanX + \", \" + scanY + \"] \" + testPx + \" => match:\" + bIsMatching); }\n\n                if (bIsMatching)\n                {\n                    posHit = new Point(scanX, scanY);\n                    return true;\n                }\n            }\n\n            if (bDebugMode) { Logger.WriteLine(\"  >> failed\"); }\n            posHit = new Point(posX + (traceLen * incX), posY + (traceLen * incY));\n            return false;\n        }\n\n        public static List<Point> TraceSpansV(FastBitmapHSV bitmap, Rectangle box, FastPixelMatch colorMatch, int minSize, bool bDebugMode = false)\n        {\n            List<Point> result = new List<Point>();\n            int lastY = -1;\n            bool bHasMatch = false;\n            for (int IdxY = box.Top; IdxY <= box.Bottom; IdxY++)\n            {\n                bHasMatch = false;\n                for (int IdxX = box.Left; IdxX <= box.Right; IdxX++)\n                {\n                    FastPixelHSV testPx = bitmap.GetPixel(IdxX, IdxY);\n                    bHasMatch = colorMatch.IsMatching(testPx);\n                    if (bHasMatch)\n                    {\n                        if (bDebugMode) { Logger.WriteLine(\"[\" + IdxX + \", \" + IdxY + \"] \" + testPx + \" => match!\"); }\n                        break;\n                    }\n                }\n\n                if (lastY == -1 && bHasMatch)\n                {\n                    lastY = IdxY;\n                }\n                else if (lastY >= 0 && !bHasMatch)\n                {\n                    int spanSize = IdxY - lastY;\n                    if (spanSize > minSize)\n                    {\n                        if (bDebugMode) { Logger.WriteLine(\">> adding span: \" + lastY + \", size:\" + spanSize); }\n                        result.Add(new Point(lastY, spanSize));\n                    }\n\n                    lastY = -1;\n                }\n            }\n\n            if (lastY >= 0 && bHasMatch)\n            {\n                int spanSize = box.Bottom - lastY + 1;\n                if (spanSize > minSize)\n                {\n                    if (bDebugMode) { Logger.WriteLine(\">> adding span: \" + lastY + \", size:\" + spanSize); }\n                    result.Add(new Point(lastY, spanSize));\n                }\n            }\n\n            return result;\n        }\n\n        public static List<Point> TraceSpansH(FastBitmapHSV bitmap, Rectangle box, FastPixelMatch colorMatch, int minSize, bool bDebugMode = false)\n        {\n            List<Point> result = new List<Point>();\n            int lastX = -1;\n            bool bHasMatch = false;\n            for (int IdxX = box.Left; IdxX <= box.Right; IdxX++)\n            {\n                bHasMatch = false;\n                for (int IdxY = box.Top; IdxY <= box.Bottom; IdxY++)\n                {\n                    FastPixelHSV testPx = bitmap.GetPixel(IdxX, IdxY);\n                    bHasMatch = colorMatch.IsMatching(testPx);\n                    if (bHasMatch)\n                    {\n                        if (bDebugMode) { Logger.WriteLine(\"[\" + IdxX + \", \" + IdxY + \"] \" + testPx + \" => match!\"); }\n                        break;\n                    }\n                }\n\n                if (lastX == -1 && bHasMatch)\n                {\n                    lastX = IdxX;\n                }\n                else if (lastX >= 0 && !bHasMatch)\n                {\n                    int spanSize = IdxX - lastX;\n                    if (spanSize > minSize)\n                    {\n                        if (bDebugMode) { Logger.WriteLine(\">> adding span: \" + lastX + \", size:\" + spanSize); }\n                        result.Add(new Point(lastX, spanSize));\n                    }\n\n                    lastX = -1;\n                }\n            }\n\n            if (lastX >= 0 && bHasMatch)\n            {\n                int spanSize = box.Right - lastX + 1;\n                if (spanSize > minSize)\n                {\n                    if (bDebugMode) { Logger.WriteLine(\">> adding span: \" + lastX + \", size:\" + spanSize); }\n                    result.Add(new Point(lastX, spanSize));\n                }\n            }\n\n            return result;\n        }\n\n        public static Point TraceBoundsH(FastBitmapHSV bitmap, Rectangle box, FastPixelMatch colorMatch, int maxGapSize, bool bDebugMode = false)\n        {\n            int boxCenter = (box.Right + box.Left) / 2;\n\n            int minX = -1;\n            int gapStart = -1;\n            bool bPrevMatch = false;\n            for (int IdxX = box.Left; IdxX < boxCenter; IdxX++)\n            {\n                bool bHasMatch = false;\n                for (int IdxY = box.Top; IdxY <= box.Bottom; IdxY++)\n                {\n                    FastPixelHSV testPx = bitmap.GetPixel(IdxX, IdxY);\n                    bHasMatch = colorMatch.IsMatching(testPx);\n                    if (bHasMatch)\n                    {\n                        if (bDebugMode) { Logger.WriteLine(\"[\" + IdxX + \", \" + IdxY + \"] \" + testPx + \" => match!\"); }\n                        break;\n                    }\n                }\n\n                if (bHasMatch)\n                {\n                    int gapSize = IdxX - gapStart;\n                    if ((gapSize > maxGapSize && gapStart > 0) || (minX < 0))\n                    {\n                        minX = IdxX;\n                        gapStart = -1;\n                    }\n\n                    if (bDebugMode) { Logger.WriteLine(\">> gapSize:\" + gapSize + \", gapStart:\" + gapStart + \", bPrevMatch:\" + bPrevMatch + \" => minX:\" + minX); }\n                }\n                else\n                {\n                    if (bPrevMatch)\n                    {\n                        gapStart = IdxX;\n                        if (bDebugMode) { Logger.WriteLine(\">> gapStart:\" + gapStart); }\n                    }\n                }\n\n                bPrevMatch = bHasMatch;\n            }\n\n            if (minX >= 0)\n            {\n                int maxX = -1;\n                gapStart = -1;\n                bPrevMatch = false;\n                for (int IdxX = box.Right; IdxX > boxCenter; IdxX--)\n                {\n                    bool bHasMatch = false;\n                    for (int IdxY = box.Top; IdxY <= box.Bottom; IdxY++)\n                    {\n                        FastPixelHSV testPx = bitmap.GetPixel(IdxX, IdxY);\n                        bHasMatch = colorMatch.IsMatching(testPx);\n                        if (bHasMatch)\n                        {\n                            if (bDebugMode) { Logger.WriteLine(\"[\" + IdxX + \", \" + IdxY + \"] \" + testPx + \" => match!\"); }\n                            break;\n                        }\n                    }\n\n                    if (bHasMatch)\n                    {\n                        int gapSize = gapStart - IdxX;\n                        if ((gapSize > maxGapSize && gapStart > 0) || (maxX < 0))\n                        {\n                            maxX = IdxX;\n                            gapStart = -1;\n                        }\n\n                        if (bDebugMode) { Logger.WriteLine(\">> gapSize:\" + gapSize + \", gapStart:\" + gapStart + \", bPrevMatch:\" + bPrevMatch + \" => maxX:\" + maxX); }\n                    }\n                    else\n                    {\n                        if (bPrevMatch)\n                        {\n                            gapStart = IdxX;\n                            if (bDebugMode) { Logger.WriteLine(\">> gapStart:\" + gapStart); }\n                        }\n                    }\n\n                    bPrevMatch = bHasMatch;\n                }\n\n                if (maxX > minX)\n                {\n                    return new Point(minX, maxX - minX);\n                }\n                else\n                {\n                    if (bDebugMode) { Logger.WriteLine(\">> TraceBoundsH: no match on right side!\"); }\n                }\n            }\n            else\n            {\n                if (bDebugMode) { Logger.WriteLine(\">> TraceBoundsH: no match on left side!\"); }\n            }\n\n            return new Point();\n        }\n\n        public static float CountFillPct(FastBitmapHSV bitmap, Rectangle box, FastPixelMatch colorMatch)\n        {\n            int totalPixels = (box.Width + 1) * (box.Height + 1);\n            int matchPixels = 0;\n            for (int IdxY = box.Top; IdxY <= box.Bottom; IdxY++)\n            {\n                for (int IdxX = box.Left; IdxX <= box.Right; IdxX++)\n                {\n                    FastPixelHSV testPx = bitmap.GetPixel(IdxX, IdxY);\n                    matchPixels += colorMatch.IsMatching(testPx) ? 1 : 0;\n                }\n            }\n\n            return (float)matchPixels / totalPixels;\n        }\n\n        public static FastPixelHSV GetAverageColor(FastBitmapHSV bitmap, Rectangle bounds)\n        {\n            float scale = 1.0f / (bounds.Width * bounds.Height);\n            float accR = 0.0f;\n            float accG = 0.0f;\n            float accB = 0.0f;\n\n            for (int idxY = 0; idxY < bounds.Height; idxY++)\n            {\n                for (int idxX = 0; idxX < bounds.Width; idxX++)\n                {\n                    FastPixelHSV testPx = bitmap.GetPixelRaw(bounds.X + idxX, bounds.Y + idxY);\n                    accR += testPx.RawR;\n                    accG += testPx.RawG;\n                    accB += testPx.RawB;\n                }\n            }\n\n            FastPixelHSV avgPx = new FastPixelHSV((byte)(accR * scale), (byte)(accG * scale), (byte)(accB * scale));\n            avgPx.ExpandHSV();\n            return avgPx;\n        }\n\n        public static void FindColorRange(FastBitmapHSV bitmap, Rectangle box, out int minMono, out int maxMono)\n        {\n            minMono = 255;\n            maxMono = 0;\n            for (int IdxY = box.Top; IdxY <= box.Bottom; IdxY++)\n            {\n                for (int IdxX = box.Left; IdxX <= box.Right; IdxX++)\n                {\n                    FastPixelHSV testPx = bitmap.GetPixel(IdxX, IdxY);\n                    minMono = Math.Min(minMono, testPx.Monochrome);\n                    maxMono = Math.Max(maxMono, testPx.Monochrome);\n                }\n            }\n        }\n\n        public static bool CreateFloodFillBitmap(FastBitmapHSV srcBitmap, Point floodOrigin, Size floodExtent, FastPixelMatch colorMatch,\n            out FastBitmapHSV floodBitmap, out Rectangle floodBounds, bool bDebugMode = false)\n        {\n            List<Point> floodPoints = new List<Point>();\n            int minX = floodOrigin.X;\n            int maxX = floodOrigin.X;\n            int minY = floodOrigin.Y;\n            int maxY = floodOrigin.Y;\n            Rectangle boundRect = new Rectangle(floodOrigin.X - floodExtent.Width, floodOrigin.Y - floodExtent.Height, floodExtent.Width * 2, floodExtent.Height * 2);\n\n            Stack<Point> openList = new Stack<Point>();\n            openList.Push(floodOrigin);\n\n            while (openList.Count > 0)\n            {\n                Point testPoint = openList.Pop();\n                if (floodPoints.Contains(testPoint))\n                {\n                    continue;\n                }\n\n                FastPixelHSV testPx = srcBitmap.GetPixel(testPoint.X, testPoint.Y);\n                if (bDebugMode) { Logger.WriteLine(\"[\" + testPoint.X + \", \" + testPoint.Y + \"] \" + testPx + \", match:\" + colorMatch.IsMatching(testPx) + \", inBounds:\" + boundRect.Contains(testPoint)); }\n\n                if (colorMatch.IsMatching(testPx) && boundRect.Contains(testPoint))\n                {\n                    floodPoints.Add(testPoint);\n\n                    minX = Math.Min(minX, testPoint.X);\n                    maxX = Math.Max(maxX, testPoint.X);\n                    minY = Math.Min(minY, testPoint.Y);\n                    maxY = Math.Max(maxY, testPoint.Y);\n\n                    openList.Push(new Point(testPoint.X - 1, testPoint.Y));\n                    openList.Push(new Point(testPoint.X + 1, testPoint.Y));\n                    openList.Push(new Point(testPoint.X, testPoint.Y - 1));\n                    openList.Push(new Point(testPoint.X, testPoint.Y + 1));\n                    openList.Push(new Point(testPoint.X - 1, testPoint.Y - 1));\n                    openList.Push(new Point(testPoint.X + 1, testPoint.Y - 1));\n                    openList.Push(new Point(testPoint.X - 1, testPoint.Y + 1));\n                    openList.Push(new Point(testPoint.X + 1, testPoint.Y + 1));\n                }\n            }\n\n            floodBounds = new Rectangle(minX, minY, maxX - minX + 1, maxY - minY + 1);\n            if (floodPoints.Count > 0)\n            {\n                FastPixelHSV[] bitmapPixels = new FastPixelHSV[floodBounds.Width * floodBounds.Height];\n                for (int Idx = 0; Idx < bitmapPixels.Length; Idx++)\n                {\n                    bitmapPixels[Idx] = new FastPixelHSV(false);\n                }\n\n                foreach (Point p in floodPoints)\n                {\n                    int Idx = (p.X - minX) + ((p.Y - minY) * floodBounds.Width);\n                    bitmapPixels[Idx] = new FastPixelHSV(true);\n                }\n\n                floodBitmap = new FastBitmapHSV()\n                {\n                    Pixels = bitmapPixels,\n                    Width = floodBounds.Width,\n                    Height = floodBounds.Height,\n                };\n            }\n            else\n            {\n                floodBitmap = null;\n            }\n\n            return (floodBitmap != null);\n        }\n\n        public static float GetPixelFeaturesMono(FastPixelHSV testPx)\n        {\n            return testPx.GetMonochrome() / 255.0f;\n        }\n\n        public static float GetPixelFeaturesHue(FastPixelHSV testPx)\n        {\n            return testPx.GetHue() / 360.0f;\n        }\n\n        public static float GetPixelFeaturesHueMono(FastPixelHSV testPx)\n        {\n            const int hueSteps = 16;\n            const int monoSteps = 16;\n\n            const float monoScale = 1.0f / monoSteps;\n            const float hueScale = monoScale / hueSteps;\n\n            int hueV = testPx.GetHue() / (360 / hueSteps);\n            int monoV = testPx.GetMonochrome() / (256 / monoSteps);\n\n            float pixelV = (hueV * hueScale) + (monoV * monoScale);\n            return pixelV;\n        }\n\n        public static float GetPixelFeaturesM2V2(FastPixelHSV testPx)\n        {\n            float monoV = testPx.GetMonochrome() / 255.0f;\n            float valV = testPx.GetValue() / 100.0f;\n            float pixelV = monoV * monoV * valV * valV;\n            return pixelV;\n        }\n\n        public static float[] ExtractImageFeaturesScaled(FastBitmapHSV bitmap, Rectangle bounds, int destWidth, int destHeight, Func<FastPixelHSV, float> pxFunc)\n        {\n            float[] values = new float[destWidth * destHeight];\n\n            // scale to requested size\n            float scaleX = (float)destWidth / bounds.Width;\n            float scaleY = (float)destHeight / bounds.Height;\n            float endY = 0.0f;\n\n            for (int hashY = 0; hashY < destHeight; hashY++)\n            {\n                float startY = endY;\n                endY = (hashY + 1) / scaleY;\n                if (endY >= bounds.Height) { endY = bounds.Height - 0.00001f; }\n                float endX = 0.0f;\n\n                for (int hashX = 0; hashX < destWidth; hashX++)\n                {\n                    float startX = endX;\n                    endX = (hashX + 1) / scaleX;\n                    if (endX >= bounds.Width) { endX = bounds.Width - 0.00001f; }\n                    float sum = 0.0f;\n\n                    float sumDiv = 0.00001f;\n                    for (int srcY = (int)startY; srcY <= (int)endY; srcY++)\n                    {\n                        float partY = 1.0f;\n                        if (srcY == (int)startY) { partY -= startY - srcY; }\n                        if (srcY == (int)endY) { partY -= srcY + 1 - endY; }\n\n                        for (int srcX = (int)startX; srcX <= (int)endX; srcX++)\n                        {\n                            float partX = 1.0f;\n                            if (srcX == (int)startX) { partX -= startX - srcX; }\n                            if (srcX == (int)endX) { partX -= srcX + 1 - endX; }\n\n                            FastPixelHSV testPx = bitmap.GetPixel(bounds.Left + srcX, bounds.Top + srcY);\n                            float testPxValue = pxFunc(testPx);\n                            sum += testPxValue * partY * partX;\n                            sumDiv += partY * partX;\n                        }\n                    }\n\n                    values[hashX + (hashY * destWidth)] = sum / sumDiv;\n                }\n            }\n\n            return values;\n        }\n\n        public static void NormalizeImageFeatures(float[] values)\n        {\n            float maxV = 0.0f;\n            for (int idx = 0; idx < values.Length; idx++)\n            {\n                if (maxV < values[idx])\n                {\n                    maxV = values[idx];\n                }\n            }\n\n            if (maxV > 0.001)\n            {\n                for (int idx = 0; idx < values.Length; idx++)\n                {\n                    values[idx] /= maxV;\n                }\n            }\n        }\n\n        public static Bitmap CreatePreviewImage(Bitmap sourceImage, Rectangle bounds, Rectangle contextBounds)\n        {\n            if (contextBounds.IsEmpty || bounds.Contains(contextBounds))\n            {\n                return sourceImage.Clone(bounds, sourceImage.PixelFormat);\n            }\n\n            Bitmap previewBitmap = sourceImage.Clone(contextBounds, sourceImage.PixelFormat);\n            unsafe\n            {\n                BitmapData srcBitmapData = sourceImage.LockBits(contextBounds, ImageLockMode.ReadOnly, sourceImage.PixelFormat);\n                BitmapData dstBitmapData = previewBitmap.LockBits(new Rectangle(0, 0, previewBitmap.Width, previewBitmap.Height), ImageLockMode.WriteOnly, previewBitmap.PixelFormat);\n                int srcBytesPerPixel = Image.GetPixelFormatSize(sourceImage.PixelFormat) / 8;\n                int dstBytesPerPixel = Image.GetPixelFormatSize(previewBitmap.PixelFormat) / 8;\n\n                Rectangle relativeInnerBounds = new Rectangle(\n                    bounds.Left - contextBounds.Left,\n                    bounds.Top - contextBounds.Top,\n                    bounds.Width, bounds.Height);\n\n                for (int IdxY = 0; IdxY < contextBounds.Height; IdxY++)\n                {\n                    byte* srcPixels = (byte*)srcBitmapData.Scan0 + (IdxY * srcBitmapData.Stride);\n                    byte* dstPixels = (byte*)dstBitmapData.Scan0 + (IdxY * dstBitmapData.Stride);\n\n                    bool bInRangeY = (IdxY >= relativeInnerBounds.Top) && (IdxY <= relativeInnerBounds.Bottom);\n\n                    int SrcIdx = 0;\n                    int DstIdx = 0;\n                    for (int IdxPixel = 0; IdxPixel < contextBounds.Width; IdxPixel++)\n                    {\n                        bool bInRange = bInRangeY && (IdxPixel >= relativeInnerBounds.Left) && (IdxPixel <= relativeInnerBounds.Right);\n                        int ColorDiv = bInRange ? 1 : 4;\n                        byte DefColor = bInRange ? (byte)255 : (byte)127;\n\n                        for (int IdxByte = 0; IdxByte < dstBytesPerPixel; IdxByte++)\n                        {\n                            dstPixels[DstIdx + IdxByte] = (IdxByte < srcBytesPerPixel) ? (byte)(srcPixels[SrcIdx + IdxByte] / ColorDiv) : DefColor;\n                        }\n\n                        SrcIdx += srcBytesPerPixel;\n                        DstIdx += dstBytesPerPixel;\n                    }\n                }\n\n                sourceImage.UnlockBits(srcBitmapData);\n                previewBitmap.UnlockBits(dstBitmapData);\n            }\n\n            return previewBitmap;\n        }\n    }\n}\n"
  },
  {
    "path": "sources/vision/MLClassifierCactpot.cs",
    "content": "﻿using MgAl2O4.Utils;\n\nnamespace FFTriadBuddy\n{\n    public class MLClassifierCactpot\n    {\n        private float[] Layer0W = null;\n        private float[] Layer0B = null;\n        private float[] Layer1W = null;\n        private float[] Layer1B = null;\n\n        public int Calculate(float[] input, out float BestPct)\n        {\n            float[] Layer0Out = new float[Layer0B.Length];\n            MLUtils.CalcNetworkLayer(input, Layer0Out, Layer0W, Layer0B);\n            MLUtils.ApplyRelu(Layer0Out);\n\n            float[] Layer1Out = new float[Layer1B.Length];\n            MLUtils.CalcNetworkLayer(Layer0Out, Layer1Out, Layer1W, Layer1B);\n            MLUtils.ApplySoftmax(Layer1Out);\n\n            return MLUtils.PickHighestProbability(Layer1Out, out BestPct);\n        }\n\n        public void InitializeModel()\n        {\n            // AUTO GENERATED, see: ml/PatternMatch/ for details\n            Layer0W = new float[]{-0.148836f, 0.001810f, -0.035480f, -0.011770f, 0.127098f, 0.000168f, -0.028543f, -0.250295f, 0.014155f, 0.086286f, -0.111505f, -0.104999f, -0.128126f, -0.271857f, 0.069019f, -0.077900f, 0.073256f, 0.029110f, -0.001404f,\n0.284580f, 0.025371f, 0.490610f, 0.128125f, -0.726707f, 0.128055f, 0.147024f, -0.136818f, 0.164706f, -0.050757f, -0.142489f, 0.151578f, -0.053940f, -0.757073f, -0.143539f, -0.062807f, -0.861824f, -0.145208f, -0.052922f, 0.373143f, -0.116057f,\n0.162932f, -0.004120f, -0.082824f, -0.417979f, 0.146195f, 0.120325f, 0.003524f, 0.118183f, 0.093588f, 0.122865f, -0.119575f, -0.041645f, -0.012137f, -0.520122f, -0.018346f, -0.367067f, -0.162112f, -0.151609f, 0.459271f, 0.048215f, 0.058869f,\n-0.247747f, 1.085661f, 0.160025f, 0.156700f, -0.117316f, 0.079686f, 0.446072f, -0.154701f, 0.419381f, -0.133687f, 0.579379f, 0.157689f, 0.052807f, 0.041310f, -0.372738f, 0.003476f, 0.302432f, 0.169689f, -0.164221f, 0.204636f, -0.009758f, -0.164343f,\n-0.098683f, 0.125300f, -0.214501f, -0.074250f, 0.242174f, 0.356704f, -0.047323f, 0.013629f, -0.015503f, -0.122522f, 0.213863f, -0.007403f, 0.124441f, -0.009388f, -0.002061f, -0.170116f, 0.076895f, 0.049895f, 0.057702f, -0.112790f, -0.527472f,\n-0.015249f, 0.038490f, 0.102343f, 0.027386f, -0.054380f, 0.110237f, -0.168252f, 0.064623f, -0.253381f, 0.024830f, 0.125271f, 0.062100f, 0.107148f, 0.060526f, -0.305900f, 0.115117f, 0.134986f, -0.113297f, 0.134737f, -0.041737f, -0.151956f, -0.128707f,\n-0.144056f, 0.039057f, -0.114718f, 0.009105f, -0.013039f, 0.264323f, 0.255774f, 0.037195f, 0.153400f, -0.181377f, -0.101585f, 0.057504f, -0.273971f, -0.147892f, 0.036978f, 0.206594f, 0.231035f, -0.071398f, -0.000986f, -0.039513f, 0.196045f,\n-0.019866f, -0.162936f, 0.249586f, 0.012649f, -0.041808f, 0.057249f, 0.089758f, -0.158987f, 0.099499f, -0.113356f, 0.472357f, -0.016125f, -0.065958f, 0.456891f, 0.062195f, -0.129990f, 0.136314f, -0.011856f, 0.074741f, 0.011780f, 0.616919f, 0.314623f,\n0.130623f, -0.000559f, -0.059309f, -0.591579f, 0.036803f, -0.071682f, -0.095920f, -0.127330f, 0.116000f, 0.042274f, -0.256346f, 0.132242f, 0.128915f, 0.167759f, -0.170490f, 0.002993f, -0.054050f, -0.163584f, -0.024073f, 0.158482f, 0.110447f,\n0.074686f, -0.035399f, 0.080154f, -0.012143f, 0.236621f, 0.501833f, 0.004181f, -0.121697f, -0.323104f, -0.119211f, 0.126479f, -0.080101f, 0.133572f, -0.054720f, 0.086533f, 0.150513f, 0.004819f, 0.022105f, -0.077092f, 0.052109f, 0.006421f, -0.039956f,\n0.296758f, -0.263912f, -0.005844f, -0.394048f, -0.045871f, -0.092373f, -0.613878f, -0.071503f, 0.114870f, 0.481223f, -0.257388f, 0.067359f, -0.063299f, -0.050275f, -0.104590f, 0.150523f, 0.009908f, 0.658639f, -0.032422f, -0.199910f, 0.022716f,\n-0.116303f, -0.077361f, 0.269172f, 0.151031f, 0.295333f, -0.012580f, 0.056326f, 0.212129f, -0.132948f, -0.088280f, 0.106222f, -0.081136f, 0.470769f, -0.173808f, 0.400877f, 0.021685f, -0.117962f, 0.015271f, 0.118507f, -0.413070f, 0.173852f, 0.162290f,\n-0.141380f, 0.149568f, 0.093777f, -0.132218f, -0.217118f, -0.135645f, 0.136655f, -0.075876f, -0.215148f, -0.021403f, -0.012622f, -0.111477f, -0.030483f, -0.075104f, 0.071663f, 0.117305f, -0.123851f, 0.421467f, -0.023629f, 0.446490f, 0.288746f,\n0.020553f, 0.142239f, 0.172120f, 0.103923f, 0.089315f, -0.163775f, -0.066562f, 0.189047f, -0.063748f, 0.141825f, 0.071269f, -0.118907f, -0.047678f, -0.052944f, -0.129350f, 0.214430f, -0.272732f, 0.003436f, -0.148308f, -0.469684f, 0.125150f,\n-0.366516f, -0.366556f, 0.054917f, 0.147311f, 0.351804f, 0.137574f, 0.035092f, -0.021838f, -0.097741f, -0.363596f, 0.268896f, 0.023500f, 0.414379f, 0.041577f, -0.405895f, -0.090575f, -0.155140f, -0.062322f, 0.782183f, 0.137824f, 0.018487f, 0.017057f,\n-0.093773f, 0.224002f, 0.045487f, -0.090293f, 0.034617f, -0.099147f, 0.598094f, -0.008242f, 0.085594f, -0.054870f, -0.169870f, -0.025556f, 0.100640f, 0.058496f, 0.127142f, -0.130533f, 0.024174f, -0.090508f, 0.153518f, 0.050284f, -0.025169f,\n-0.097004f, 0.407615f, 0.021333f, -0.352393f, 0.124348f, 0.035813f, -0.034896f, -0.076182f, 0.080870f, 0.045944f, 0.100942f, 0.108769f, -0.082316f, -0.080431f, 0.105348f, -0.001988f, -0.112113f, 0.044853f, 0.039139f, 0.001696f, 0.000421f, -0.162063f,\n0.116475f, -0.286633f, -0.158945f, -0.152997f, -0.042100f, -0.027763f, -0.014702f, -0.168233f, 0.116382f, 0.536723f, -0.138208f, 0.477068f, -0.113750f, -0.092088f, 0.113949f, -0.127568f, 0.076600f, 0.075725f, 0.066313f, -0.327076f, 0.157934f,\n-0.115353f, 0.033402f, -0.126471f, 0.002747f, 0.134077f, 0.012079f, 0.186067f, -0.074669f, -0.082821f, 0.037349f, 0.093259f, 0.074751f, 0.220319f, -0.013374f, -0.130393f, -0.060378f, -0.178436f, 0.444838f, 0.085400f, -0.067095f, 0.023084f,\n-0.080814f, 0.146680f, -0.135767f, 0.119966f, 0.224296f, 0.085420f, 0.032143f, -0.067665f, -0.184364f, -0.189521f, 0.066744f, -0.060025f, 0.160495f, -0.117586f, 0.105223f, 0.053421f, 0.126436f, -0.085619f, -0.034311f, -0.229550f, 0.033173f,\n-0.146294f, -0.050130f, -0.109637f, -0.102572f, 0.117772f, 0.102304f, -0.150468f, 0.053094f, 0.028059f, 0.268969f, 0.135587f, 0.065041f, -0.147558f, -0.141446f, 0.092875f, 0.101667f, -0.170633f, 0.033463f, 0.079962f, -0.091250f, 0.049839f, 0.019707f,\n-0.101969f, 0.144165f, -0.007145f, -0.092250f, -0.134949f, -0.131902f, -0.137200f, -0.072880f, -0.304942f, 0.112851f, 0.053638f, -0.459621f, 0.122905f, 0.128627f, -0.223950f, 0.095597f, 0.076882f, 0.099669f, -0.106809f, 0.236577f, -0.103915f,\n0.033046f, 0.229409f, -0.067105f, -0.305191f, -0.041450f, -0.015384f, -0.107959f, 0.078903f, 0.076425f, -0.037953f, -0.129520f, 0.133761f, 0.260998f, -0.148308f, 0.014442f, 0.073631f, -0.057993f, -0.601055f, 0.160826f, 0.448495f, 0.513247f,\n-0.048620f, 0.004145f, 0.013948f, -0.019515f, 0.025111f, 0.154595f, -0.176598f, -0.085018f, 0.121616f, -0.168430f, -0.187231f, -0.093399f, 0.281566f, -0.042570f, -0.220149f, 0.082615f, 0.036097f, 0.062957f, 0.105572f, -0.033136f, 0.129975f,\n-0.100718f, -0.013419f, -0.017338f, 0.114140f, 0.270027f, 0.157966f, 0.086319f, 0.167353f, -0.233450f, 0.074768f, 0.112527f, 0.045718f, -0.110649f, 0.560077f, -0.057349f, 0.150256f, 0.028361f, -0.071597f, -0.071273f, 0.057973f, -0.115818f, 0.384584f,\n-0.130525f, 0.047667f, -0.023262f, -0.363749f, -0.052192f, -0.161420f, -0.309448f, -0.027138f, 0.023091f, 0.038323f, 0.073995f, 0.144373f, 0.101530f, -0.062342f, 0.035661f, 0.306415f, -0.102608f, 0.103066f, 0.083892f, -0.139287f, 0.123669f,\n0.061394f, -0.104498f, -0.078344f, 0.122020f, 0.188614f, 0.017854f, 0.095932f, 0.551130f, -0.021277f, 0.161466f, 0.004227f, 0.132084f, -1.153091f, 0.080963f, 0.709968f, 0.994551f, -0.099387f, 0.058451f, 0.128209f, -0.209202f, 0.850669f, 0.122961f,\n0.070701f, 0.121771f, -0.138798f, -0.029704f, 0.255669f, 0.067625f, 0.752463f, 0.019636f, -0.160729f, -0.001688f, -0.131349f, -0.150793f, 0.104009f, 0.075470f, -0.059542f, -0.001140f, 0.007801f, 0.457351f, 0.127583f, 0.075081f, -0.080786f, 0.063256f,\n-0.038021f, -0.643801f, -0.072415f, -0.013726f, -0.058595f, 0.041912f, 1.017395f, 0.089960f, -0.037357f, -0.163557f, -0.159772f, 0.076567f, -0.101057f, 0.071132f, 0.716598f, 0.557563f, 0.071835f, -0.112895f, -0.862291f, 0.049301f, -0.702352f,\n-0.994654f, -0.092943f, -0.110582f, -0.640671f, 0.782887f, 0.133990f, 0.141247f, 0.149006f, 0.241792f, 0.553601f, 0.159721f, 0.622164f, 0.118274f, -0.748765f, -0.071579f, 0.067140f, 0.132260f, 0.343834f, -0.041504f, 0.389793f, -0.106614f, 0.026885f,\n-0.090991f, -0.145692f, -0.045176f, -0.090005f, 0.081033f, 0.103462f, 0.119091f, -0.220087f, -0.053651f, 0.052419f, -0.166685f, -0.051050f, 0.199630f, -0.232641f, -0.009623f, 0.162772f, -0.039280f, 0.061339f, 0.135105f, 0.351830f, -0.053251f,\n0.282692f, -0.169388f, 0.010739f, -0.034473f, -0.078533f, 0.012727f, 0.167621f, -0.044175f, -0.028649f, -0.141263f, -0.051223f, -0.609649f, 0.010148f, 0.048730f, -0.342944f, 0.020293f, -0.157196f, 0.019589f, 0.095232f, -0.112527f, -0.062056f,\n-0.139281f, -0.525876f, -0.037400f, 0.068113f, 0.122972f, 0.058262f, 0.062585f, 0.087056f, -0.174938f, -0.036322f, 0.009337f, -0.434199f, 0.088936f, 0.354027f, -0.072104f, 0.280668f, 0.479508f, 0.121207f, -0.111163f, 0.163238f, 0.256512f, 0.109293f,\n-0.026329f, 0.141321f, 0.192631f, -0.233120f, -0.080389f, -0.234660f, -0.103089f, 0.589981f, -0.172486f, 0.139685f, 0.073946f, -0.431296f, -0.062352f, 0.260028f, -0.115543f, 0.051328f, 0.002308f, 0.078142f, 0.061968f, -0.055696f, 0.014657f,\n-0.056161f, 0.140904f, 0.086618f, 0.102515f, -0.167019f, 0.074046f, -0.096789f, -0.065529f, 0.188099f, -0.160621f, 0.142066f, 0.130452f, 0.014292f, 0.083002f, 0.006403f, -0.017349f, 0.076909f, -0.041834f, -0.182782f, 0.162785f, -0.024528f,\n-0.125108f, -0.062020f, -0.077745f, -0.022305f, 0.056481f, -0.102184f, -0.065414f, -0.117980f, -0.069529f, -0.161386f, 0.048018f, -0.076444f, 0.095393f, -0.085838f, 0.124700f, 0.064002f, -0.094433f, 0.198768f, 0.019332f, 0.139549f, 0.024428f,\n0.108036f, 0.087275f, -0.067253f, -0.074195f, 0.009840f, 0.257025f, -0.046358f, 0.134392f, 0.024147f, -0.078447f, -0.057212f, -0.082723f, 0.014802f, 0.006561f, 0.405380f, -0.201034f, 0.114995f, 0.013256f, -0.035844f, 0.052972f, -0.149488f,\n-0.032647f, 0.049393f, -0.072708f, 0.158245f, -0.166210f, -0.080394f, 0.004620f, 0.111792f, -0.144174f, 0.416804f, 0.163937f, -0.125015f, 0.255243f, 0.015747f, 0.023416f, 0.076495f, 0.081229f, -0.106272f, -0.095611f, 0.303514f, 0.146229f, -0.164938f,\n0.048115f, 0.013008f, -0.475584f, 0.007237f, -0.010743f, -0.136285f, -0.097601f, 0.002783f, 0.101261f, -0.293594f, -0.172330f, -0.051437f, -0.100438f, -0.118073f, -0.117600f, 0.044646f, -0.101947f, -0.066071f, 0.024562f, 0.087181f, 0.090501f,\n-0.056421f, -0.067365f, -0.128450f, 0.433785f, 0.459367f, -0.011379f, -0.016805f, 0.014907f, -0.078853f, -0.134704f, 0.018496f, 0.002946f, 0.065977f, 0.120450f, -0.131443f, 0.074451f, -0.019591f, -0.131197f, 0.006160f, -0.141797f, -0.052854f,\n0.233161f, -0.131266f, 0.139141f, -0.428294f, -0.169198f, 0.047646f, -0.601952f, -0.056234f, -0.166749f, 0.297232f, 0.167583f, -0.164707f, 0.104528f, -0.068357f, -0.214915f, 0.168562f, 0.058887f, 0.267327f, 0.053007f, -0.198510f, -0.089038f,\n0.078871f, 0.149912f, 0.176110f, -0.080590f, 0.095524f, -0.141530f, 0.042322f, 0.624806f, -0.087243f, -0.088263f, 0.110338f, 0.056220f, 0.202931f, 0.068083f, 0.207790f, 0.187821f, -0.098826f, -0.084415f, 0.134384f, -0.715917f, -0.041584f, -0.123758f,\n-0.046972f, 0.152787f, -0.172923f, 0.145933f, -0.473539f, 0.037699f, -0.063467f, 0.066738f, -0.338454f, -0.047638f, -0.169744f, -0.118696f, 0.006113f, 0.148756f, 0.027613f, -0.065875f, -0.095906f, -0.271642f, 0.005551f, 0.438331f, 0.299071f,\n-0.088466f, -0.155872f, 0.120238f, -0.164451f, -0.046195f, -0.147803f, -0.132986f, -0.520635f, -0.031220f, 0.132109f, 0.071583f, 0.153008f, -0.155681f, -0.095153f, 0.072374f, 0.431999f, -0.521054f, 0.428778f, -0.128214f, -0.538227f, 0.074164f,\n-0.075037f, -0.325877f, 0.086427f, 0.069619f, -0.024587f, 0.382323f, 0.027563f, -0.086896f, -0.044127f, -0.406671f, 0.400708f, -0.057638f, 0.917492f, 0.095256f, -0.218349f, 0.017999f, -0.160939f, -0.027284f, 0.538433f, -0.091936f, -0.128161f,\n0.114135f, 0.080688f, 0.398802f, 0.120000f, 0.087869f, 0.032750f, 0.025882f, 1.012946f, 0.014342f, -0.137711f, 0.161418f, 0.071792f, 0.106009f, 0.144291f, -0.116151f, 0.198763f, 0.079839f, 0.040713f, 0.146931f, 0.031247f, 0.014713f, -0.124883f,\n-0.034474f, -0.455926f, -0.118019f, -0.688973f, -0.066771f, 0.063196f, 0.082130f, -0.028046f, 0.169072f, -0.017085f, -0.099254f, 0.055106f, -0.487889f, -0.144153f, -0.016587f, -0.202748f, -0.007971f, -0.094075f, 0.374128f, -0.109800f, 0.060465f,\n-0.111424f, 0.012374f, -0.333468f, -0.052724f, -0.014583f, -0.024591f, -0.029210f, 0.010670f, 0.151506f, 0.092406f, 0.254859f, -0.265756f, 0.688423f, 0.101764f, -0.267464f, 0.102797f, -0.141325f, 0.213961f, 0.028041f, 0.020308f, -0.251879f,\n-0.011653f, -0.006491f, 0.131668f, 0.123454f, 0.071575f, -0.040194f, -0.161372f, 0.312866f, -0.133703f, 0.192548f, 0.061425f, 0.068618f, -0.160802f, 0.191874f, -0.075401f, -0.241889f, -0.114711f, -0.014321f, -0.099811f, 0.120257f, 0.092571f,\n-0.107185f, 0.070714f, 0.766522f, 0.071825f, -0.114610f, -0.086707f, 0.080709f, -0.161479f, -0.030927f, -0.232092f, -0.280256f, 0.151354f, 0.004896f, 0.141626f, 0.000908f, 0.128061f, -0.053404f, 0.008911f, -0.195060f, -0.103130f, -0.337959f,\n-0.063068f, -0.044015f, 0.115528f, 0.127316f, 0.012874f, -0.077067f, -0.044995f, 0.058978f, -0.053176f, -0.099894f, -0.055076f, 0.001667f, -0.140079f, 0.050857f, -0.116766f, -0.084398f, 0.139572f, 0.109340f, -0.041770f, -0.163862f, -0.013115f,\n-0.180835f, -0.082102f, 0.050836f, 0.139908f, 0.154665f, 0.002360f, -0.185721f, -0.246256f, 0.006792f, -0.170284f, -0.076642f, 0.036138f, 0.174224f, 0.190989f, -0.089529f, 0.035251f, -0.037423f, 0.079847f, 0.038408f, -0.103982f, 0.080020f, 0.084811f,\n-0.209464f, -0.116852f, 0.177970f, 0.062477f, 0.194754f, 0.156665f, 0.001977f, 0.071680f, -0.110515f, 0.052122f, -0.095115f, 0.055604f, -0.039808f, 0.091604f, 0.045868f, 0.049961f, -0.066868f, 0.086293f, 0.015356f, -0.155984f, 0.201356f, 0.392334f,\n0.022371f, 0.122782f, -0.089038f, -0.124427f, -0.141338f, 0.092704f, 0.126106f, 0.113577f, 0.132514f, -0.158207f, 0.022774f, 0.073017f, 0.387008f, -0.009227f, -0.173456f, -0.093683f, -0.095177f, -0.042157f, 0.009454f, 0.154806f, 0.081136f,\n-0.036133f, -0.109290f, -0.003587f, -0.131596f, 0.146312f, 0.305517f, 0.020770f, 0.116740f, -0.425870f, 0.134746f, 0.003494f, -0.171321f, -0.055383f, 0.224478f, 0.060072f, 0.036498f, -0.114673f, -0.095815f, -0.052160f, -0.079945f, 0.149528f,\n0.221712f, -0.251655f, -0.203482f, 0.075534f, -0.098562f, -0.082328f, 0.058345f, -0.439273f, -0.091345f, 0.135365f, -0.041966f, 0.183343f, 0.062329f, 0.094604f, 0.015156f, 0.108049f, -0.063814f, 0.025799f, 0.249007f, 0.072885f, -0.313112f, 0.108960f,\n-0.096886f, 0.137474f, -0.421337f, -0.079628f, -0.124827f, -0.078590f, -0.044217f, 0.049989f, -0.077788f, 0.145064f, -0.153204f, -0.087374f, -0.432480f, -0.159695f, 0.215605f, 0.398218f, 0.001630f, -0.145802f, -0.045920f, -0.061046f, 0.224066f,\n-0.166425f, -0.165346f, -0.094476f, -0.029165f, -0.098745f, 0.342541f, 0.113519f, 0.302257f, 0.087854f, 0.096251f, -0.111251f, 0.149689f, 0.045645f, -0.096547f, -0.131479f, -0.033124f, -0.007589f, 0.152001f, 0.165467f, -0.021905f, -0.172228f,\n0.090608f, 0.079961f, 0.148004f, 0.003280f, -0.167737f, 0.020994f, -0.078302f, 0.074477f, 0.345333f, -0.053625f, -0.052004f, 0.112596f, 0.088952f, 0.033337f, 0.146128f, 0.035675f, 0.479485f, 0.148424f, 0.268277f, -0.102930f, -0.085612f, 0.113127f,\n-0.311421f, -0.484715f, 0.149217f, 0.039086f, -0.044195f, 0.065659f, 0.013763f, -0.016033f, -0.144453f, 0.084163f, 0.429534f, 0.115301f, 0.055321f, 0.068267f, -0.332715f, -0.065415f, 0.156936f, 0.039448f, 0.347585f, 0.104171f, -0.220663f, -0.120661f,\n-0.116456f, -0.125303f, -0.129996f, -0.167559f, 0.050261f, -0.169440f, 0.103888f, 0.135801f, -0.004405f, 0.042182f, -0.055156f, 0.099527f, -0.068451f, -0.021683f, 0.021083f, -0.106013f, 0.078135f, -0.099096f, -0.166331f, 0.019271f, 0.421499f,\n0.037578f, 0.040151f, -0.122029f, 0.019827f, 0.117235f, -0.074026f, -0.128796f, -0.070399f, 0.041984f, -0.165512f, 0.016990f, 0.116739f, -0.148708f, 0.121320f, -0.037553f, -0.044046f, -0.143766f, -0.097421f, -0.286692f, 0.163677f, -0.134792f,\n0.058942f, -0.081469f, -0.067003f, -0.133501f, 0.091753f, -0.165557f, 0.167444f, 0.104927f, 0.071981f, -0.054942f, -0.171960f, -0.027951f, -0.285506f, 0.148285f, 0.092514f, 0.061882f, 0.203657f, 0.190598f, -0.015614f, 0.012274f, 0.435481f,\n-0.123458f, 0.150728f, 0.084038f, -0.031966f, 0.345302f, -0.595049f, 0.134373f, -0.505525f, -0.122193f, 0.293277f, 0.041180f, -0.068549f, 0.155678f, -0.067489f, -0.034278f, 0.393167f, -0.044111f, 0.087817f, -0.060227f, -0.031898f, -0.156090f,\n-0.071995f, -0.150549f, -0.098617f, 0.025352f, 0.078547f, 0.322344f, -0.104447f, 0.069818f, -0.044923f, -0.195541f, 0.365277f, -0.061954f, 0.035471f, 0.033783f, -0.146804f, 0.129797f, -0.146706f, -0.065794f, 0.429471f, 0.069278f, -0.335489f,\n-0.111900f, -0.087981f, -0.065048f, -0.072735f, -0.150915f, 0.032633f, -0.035798f, 0.044563f, 0.100720f, -0.060310f, 0.197232f, 0.209787f, 0.120762f, -0.156442f, 0.206506f, -0.007753f, -0.111657f, 0.095217f, -0.096147f, -0.065410f, 0.058013f,\n-0.015586f, -0.036821f, -0.124316f, 0.100966f, 0.109314f, 0.002890f, -0.155691f, 0.228206f, -0.481986f, -0.126386f, -0.245323f, -0.106494f, -0.268851f, -0.333531f, -0.125839f, 0.020992f, 0.137840f, -0.102976f, -0.161208f, 0.058988f, 0.087212f,\n-0.036496f, 0.061628f, -0.112754f, 0.105227f, -0.144101f, -0.133913f, -0.002274f, -0.020544f, -0.145182f, 0.419385f, -0.134278f, 0.069695f, -0.031774f, -0.073994f, 0.532956f, 0.021211f, -0.150487f, 0.029869f, 0.084379f, -0.359739f, -0.000462f,\n0.062020f, 0.483184f, 0.145211f, -0.138621f, -0.106948f, -0.179094f, 0.340851f, -0.008385f, -0.129621f, -0.050372f, -0.139052f, -0.105354f, -0.043474f, 0.135058f, 0.282473f, -0.082066f, -0.040037f, 0.115094f, 0.152379f, 0.143140f, -0.158989f,\n0.134903f, 0.109654f, -0.067137f, 0.006595f, -0.017005f, -0.079648f, 0.273643f, 0.091314f, 0.124504f, 0.112867f, 0.225896f, 0.117216f, -0.149761f, -0.084683f, -0.127109f, -0.451601f, 0.017983f, -0.075201f, 0.111431f, -0.101214f, -0.157522f,\n0.022912f, -0.160460f, -0.029159f, 0.114595f, -0.237998f, 0.084913f, -0.351957f, -0.100131f, -0.061396f, -0.634119f, -0.156983f, 0.144140f, -0.264092f, -0.029185f, -0.161444f, -0.107701f, -0.094892f, 0.084157f, 0.372720f, -0.137405f, -0.007289f,\n-0.130199f, -0.490400f, -0.139095f, 0.038766f, -0.158772f, -0.695538f, -0.107533f, -0.471869f, 0.130134f, -0.083445f, 0.448804f, 0.112924f, 0.151962f, 0.127035f, 0.162145f, -0.082959f, -0.119158f, 0.370456f, 0.716450f, 0.056955f, 0.070091f,\n-0.097891f, -0.374764f, 0.337549f, 0.014972f, 0.068333f, 0.074875f, 0.049131f, -0.079951f, -0.389992f, 0.077001f, -0.765352f, -0.124474f, 0.015324f, 0.081728f, -0.064235f, -0.013331f, -0.113057f, -0.085788f, -0.139981f, 0.040986f, 0.005562f,\n-0.123579f, 0.076125f, 0.367933f, 0.295364f, -0.143850f, 0.140098f, -0.103294f, -0.147278f, 0.053674f, -0.168288f, 0.046480f, -0.446222f, -0.004432f, -0.119595f, -0.070539f, -0.156122f, -0.020876f, 0.073478f, 0.061911f, 0.229414f, -0.820830f,\n1.331131f, -0.113310f, -0.353107f, 0.096966f, -0.159899f, 0.079418f, 0.133160f, 0.032422f, 0.003986f, 0.149458f, -0.106478f, -0.127960f, -0.024515f, -0.523447f, 0.719623f, 0.124406f, 0.508319f, -0.155762f, -0.026880f, -0.062484f, 0.056093f,\n-0.134999f, 0.392808f, 0.052837f, -0.016370f, 0.025282f, -0.070373f, -0.043504f, -0.105217f, -0.002758f, 0.086124f, -0.121948f, 0.383110f, -0.107281f, -0.099454f, 0.160896f, -0.074166f, 0.149454f, -0.029254f, -0.181820f, 0.481769f, 0.163544f,\n-0.014784f, -0.157386f, 0.096610f, 0.058399f, -0.533830f, -0.058430f, -1.109844f, -0.003729f, -0.054058f, 0.080206f, -0.154761f, 0.024757f, 0.106098f, -0.066746f, -0.167817f, -0.048070f, 0.121224f, -0.064463f, 0.111191f, -0.001891f, -0.038610f,\n-0.079226f, -0.109592f, 0.582364f, 0.103076f, 0.165272f, 0.079881f, 0.022423f, -0.409662f, 0.052190f, -0.039592f, -0.066931f, -0.001490f, 0.091812f, -0.082217f, -0.004302f, 0.134243f, 0.487339f, 0.583888f, 0.042998f, -0.310950f, 0.086450f,\n-0.189215f, 0.360739f, 0.043215f, 0.120281f, -0.516823f, 0.494207f, -0.041554f, -0.129984f, 0.125448f, -0.280568f, -0.270517f, 0.010651f, 0.236147f, 0.034326f, 0.050944f, -0.021192f, -0.105315f, -0.084693f, 0.091708f, -0.122024f, 0.174523f,\n-0.023302f, -0.091699f, -0.179708f, 0.073633f, -0.154761f, -0.106358f, -0.110772f, 0.364851f, -0.052526f, 0.100411f, 0.049855f, -0.072528f, 0.056245f, -0.063589f, -0.392660f, 0.014081f, -0.055969f, -0.125115f, -0.140116f, 0.074950f, -0.124180f,\n-0.539093f, -0.059380f, -0.362125f, -0.077065f, -0.373602f, 0.025233f, -0.100956f, 0.076076f, 0.134883f, 0.010677f, -0.161340f, 0.032608f, -0.014241f, 0.431217f, -0.110291f, 0.270170f, 0.176147f, -0.159396f, 0.038635f, -0.115248f, -0.008449f,\n0.014825f, -0.064693f, 0.081922f, -0.151410f, -0.086854f, -0.190872f, -0.079763f, -0.072590f, 0.045234f, -0.140660f, 0.004965f, -0.153139f, 0.437526f, 0.057495f, 0.040442f, -0.283745f, -0.069604f, -0.161540f, 0.261139f, -0.062821f, -0.173161f,\n-0.105615f, 0.278085f, 0.169434f, 0.040122f, 0.136477f, 0.222409f, -0.321230f, -0.089397f, 0.194192f, 0.038516f, 0.144668f, -0.123798f, 0.123102f, -0.124283f, 0.085819f, 0.123656f, 0.569495f, 0.017588f, 0.149883f, -0.178816f, -0.117798f, -0.112044f,\n0.049011f, -0.145505f, 0.223289f, -0.111369f, 0.228005f, 0.168802f, 0.127353f, -0.077476f, 0.058207f, -0.271142f, 0.000042f, -0.111323f, 0.161601f, -0.036357f, 0.066070f, -0.107929f, -0.191592f, -0.065071f, 0.254319f, 0.040081f, -0.143123f,\n-0.169242f, -0.073882f, 0.030062f, 0.052453f, 0.004064f, 0.010580f, 0.021010f, -0.043537f, 0.341639f, 0.005307f, 0.235393f, 0.214233f, 0.118283f, 0.054739f, -0.335013f, -0.076310f, -0.072810f, -0.152264f, 0.154212f, 0.211498f, 0.026046f, -0.140913f,\n-0.169165f, -0.162045f, -0.116416f, 0.108853f, 0.046560f, -0.080704f, 0.234902f, -0.273232f, 0.024406f, -0.312551f, 0.082010f, -0.024042f, 0.137064f, 0.064505f, 0.007372f, 0.176741f, 0.127197f, 0.067218f, 0.076229f, -0.167941f, 0.313170f, -0.071858f,\n-0.038637f, 0.178579f, 0.029364f, -0.046480f, 0.017444f, -0.000171f, -0.073356f, -0.455511f, 0.042232f, 0.426663f, 0.046554f, 0.054996f, 0.249063f, -0.045420f, 0.123362f, -0.015754f, -0.022979f, -0.302562f, -0.146254f, 0.088369f, 0.138191f,\n-0.018461f, -0.099005f, -0.104765f, -0.127253f, 0.260184f, -0.087966f, 0.057138f, -0.091954f, -0.101700f, -0.054075f, -0.040332f, 0.132170f, 0.101975f, 0.158653f, -0.043574f, -0.142399f, 0.151294f, -0.058061f, -0.131583f, -0.051972f, 0.070009f,\n-0.140219f, 0.070388f, 0.327012f, -0.105512f, 0.114815f, 0.187945f, 0.034698f, -0.101763f, -0.035299f, 0.070413f, 0.168846f, 0.095483f, 0.053648f, 0.425087f, -0.072380f, 0.138286f, 0.020839f, 0.061664f, 0.057701f, 0.165050f, -0.070280f, -0.057661f,\n0.132560f, 0.177721f, -0.126410f, -0.083931f, 0.017395f, -0.216080f, -0.531061f, -0.073363f, -0.169730f, -0.041381f, 0.151089f, -0.070621f, -0.090540f, -0.067724f, -0.002884f, 0.319461f, 0.145162f, 0.270630f, -0.049035f, -0.393502f, -0.072345f,\n-0.049158f, 0.140440f, 0.233024f, -0.171500f, -0.292169f, -0.147953f, 0.082039f, -0.342292f, -0.019127f, 0.008532f, 0.083828f, 0.001762f, 0.282891f, -0.173038f, -0.208080f, 0.162035f, -0.093647f, 0.139883f, 0.128450f, 0.033290f, 0.073918f,\n-0.113386f, -0.093163f, 0.022609f, -0.030956f, -0.001573f, 0.281923f, -0.027488f, 0.284068f, -0.149318f, 0.338009f, 0.013846f, 0.163348f, -0.162186f, 0.046474f, -0.125113f, -0.140596f, 0.128624f, -0.071194f, -0.077248f, 0.132872f, -0.099887f,\n-0.224119f, -0.182359f, -0.125974f, -0.020475f, -0.044605f, -0.005310f, -0.000685f, 0.134815f, 0.114414f, -0.160314f, 0.012245f, -0.106257f, 0.094494f, -0.053809f, -0.093200f, 0.012247f, -0.187432f, -0.085976f, -0.519260f, 0.074782f, 0.377516f,\n-0.109939f, 0.213262f, 0.009560f, 0.110002f, 0.025155f, 0.400825f, 0.169808f, -0.017278f, 0.000118f, 0.077236f, 0.470442f, -0.472674f, 0.126106f, -0.189577f, -0.159477f, 0.237238f, -0.163093f, -0.138080f, -0.147757f, 0.032109f, -0.141090f, 0.571073f,\n-0.113005f, 0.114778f, 0.435818f, 0.155930f, 0.025591f, -0.069271f, 0.120627f, -0.180568f, -0.012279f, 0.054364f, 0.436687f, -0.054329f, 0.056488f, -0.095836f, -0.210637f, 0.538632f, -0.006704f, -0.163858f, -0.163280f, 0.123801f, -0.113514f,\n0.026548f, 0.066109f, 0.740344f, 0.001375f, -0.232738f, 0.020025f, 0.157111f, 0.069172f, -0.006791f, -0.094491f, 0.105191f, -0.104870f, 0.136970f, 0.060956f, -0.011343f, 0.109039f, 0.014102f, 0.091913f, -0.103732f, 0.210728f, -0.133250f, -0.139566f,\n-0.022337f, 0.039239f, -0.485028f, -0.089507f, -0.129580f, 0.055667f, 0.072480f, -0.073799f, 0.159580f, 0.005221f, -0.145680f, 0.268550f, -0.297793f, -0.035668f, -0.492925f, 0.014411f, -0.476597f, -0.155030f, 0.004717f, 0.022631f, -0.279132f,\n-0.024670f, 0.039364f, -0.123174f, -0.133564f, -0.001015f, 0.348985f, -0.161701f, 0.150984f, 0.127786f, -0.417136f, -0.073112f, 0.135069f, 0.007518f, 0.331102f, 0.054347f, 0.007123f, 0.163584f, 0.103508f, 0.627412f, 0.074505f, -0.037470f, -0.051545f,\n-0.007426f, -0.300165f, -0.140414f, 0.113561f, 0.702923f, 0.038423f, 0.138301f, 0.088878f, -0.185977f, 0.491496f, -0.050341f, -0.045296f, -0.015157f, 0.012287f, -0.127326f, 0.196347f, -0.177294f, 0.034753f, -0.083661f, -0.258118f, 0.023619f,\n0.167684f, -0.061178f, -0.097658f, -0.097144f, 0.022127f, -0.109684f, -0.101982f, -0.413491f, -0.065330f, 0.533639f, 0.191693f, 0.116822f, 0.020173f, 0.474517f, -0.045179f, 0.076180f, 0.027113f, 0.113810f, -0.633476f, -0.168341f, -0.120486f,\n0.151832f, -0.069968f, -0.158653f, 0.028837f, -0.091857f, -0.035466f, -0.228159f, 0.039915f, 0.113541f, -0.193096f, 0.069286f, 0.100747f, -0.210891f, 0.152006f, -0.063618f, -0.126264f, 0.360080f, 0.085751f, 0.028748f, 0.169151f, -0.257029f,\n0.592399f, -0.090922f, 0.183011f, -0.020801f, -0.257977f, 0.058412f, 0.002021f, 0.026070f, -0.915129f, 0.090197f, -0.209977f, -0.044413f, 0.104471f, 0.455922f, -0.134529f, 0.024534f, -0.100801f, 0.014177f, -0.178730f, 0.026066f, 0.369863f, 0.233169f,\n-0.060522f, 0.048196f, 0.113039f, -0.179789f, 0.341904f, 0.131052f, 0.048337f, 0.042840f, 0.085630f, -0.031744f, -0.313469f, -0.109798f, -0.657992f, -0.157232f, -0.158016f, -0.000048f, -0.040705f, 0.126311f, -0.099403f, -0.045312f, -0.143099f,\n-0.052682f, 0.051030f, -0.143303f, 0.080271f, 0.116274f, 0.132407f, -0.046214f, -0.143315f, 0.235719f, -0.082457f, -0.037511f, 0.047010f, -0.062885f, -0.216775f, -0.175346f, 0.116209f, 0.036618f, 0.072213f, -0.106243f, -0.015816f, -0.087816f,\n-0.139886f, -0.603712f, 1.325964f, 0.149010f, -0.445424f, -0.114130f, -0.147954f, 0.115907f, 0.101781f, 0.118146f, -0.304906f, -0.054012f, -0.009626f, 0.102966f, -0.111737f, -0.605747f, 0.987970f, 0.120163f, 0.262959f, -0.004217f, -0.178354f,\n-0.160225f, -0.096438f, 0.113622f, 0.613517f, 0.023257f, -0.538975f, -0.131767f, -0.170904f, 0.169648f, 0.147672f, -0.009779f, -0.049782f, 0.123369f, 0.323311f, 0.060325f, -0.480444f, -0.069396f, -0.138072f, -0.152312f, 0.073700f, 0.153718f,\n0.219065f, 0.084153f, 0.093141f, 0.026634f, 0.000441f, 0.045744f, -0.379903f, 0.093652f, -1.051609f, -0.152786f, -0.373372f, -0.142919f, 0.108503f, -0.123787f, -0.026670f, -0.082982f, -0.143678f, -0.168368f, 0.002461f, 0.266970f, 0.002614f,\n-0.082317f, 0.042837f, -0.089156f, -0.078263f, 0.570824f, 0.060730f, -0.124344f, 0.003481f, -0.087769f, -0.558649f, -0.040396f, -0.142895f, 0.048965f, 0.027817f, 0.150704f, 0.120420f, -0.104648f, -0.011023f, 0.907217f, 0.867730f, -0.136402f,\n-0.166905f, -0.046222f, -0.311857f, -0.161174f, -0.002290f, -0.007462f, -0.706382f, 0.504578f, -0.138541f, -0.053063f, -0.101035f, -0.094647f, -0.427038f, -0.140563f, 0.322107f, -0.081013f, -0.039292f, -0.121355f, -0.015225f, -0.142815f, -0.333554f,\n-0.140529f, 0.231628f, -0.153636f, 0.031996f, -0.414466f, 0.136110f, 0.083514f, -0.108884f, 0.003074f, 0.179673f, 0.066921f, -0.369530f, 0.006179f, -0.088661f, 0.123816f, -0.144746f, 0.214920f, -0.168654f, -0.070161f, -0.028043f, -0.135118f,\n0.014214f, 0.020116f, -0.352207f, 0.094303f, -0.422361f, 0.091959f, 0.028854f, -0.129350f, -0.167675f, 0.121243f, 0.030356f, 0.044633f, -0.151356f, -0.042953f, 0.043404f, 0.667255f, -0.052417f, -0.079720f, -0.071738f, 0.042276f, -0.052754f,\n0.018668f, -0.136456f, -0.008325f, 0.017467f, -0.153573f, -0.039296f, 0.134968f, 0.004906f, 0.035056f, -0.157920f, 0.133841f, 0.136530f, 0.003774f, -0.511667f, 0.609123f, 0.146812f, -0.009436f, -0.007592f, -0.092982f, 0.141482f, 0.164010f,\n-0.064682f, 0.128672f, -0.137066f, 0.055048f, -0.097512f, -0.025941f, -0.063717f, 0.174696f, -0.492312f, 0.156968f, 0.201514f, -0.132744f, -0.009976f, -0.081807f, 0.015546f, -0.042307f, 0.141955f, 0.067751f, 0.595505f, -0.117887f, -0.170256f,\n-0.310675f, -0.015707f, -0.141037f, 0.058946f, 0.075578f, 0.207438f, 0.137956f, 0.078874f, 0.157556f, -0.051514f, -0.119515f, 0.091530f, -0.309926f, 0.202823f, 0.117151f, 0.039718f, 0.025502f, -0.027567f, 0.053592f, -0.242565f, 0.080688f, 0.213243f,\n-0.121112f, -0.152341f, -0.167526f, -0.016101f, 0.134742f, -0.077674f, -0.077765f, 0.047294f, -0.066510f, 0.003700f, 0.298056f, 0.163235f, 0.003842f, 0.005194f, 0.100072f, -0.125001f, -0.103156f, 0.022933f, -0.098519f, 0.029839f, -0.173024f,\n0.478308f, 0.028552f, -0.150936f, 0.047084f, -0.139993f, 0.009493f, -0.154580f, 0.003759f, -0.347559f, 0.146132f, -0.512538f, 0.151467f, -0.304451f, -0.160458f, -0.001051f, 0.345649f, -0.105485f, -0.123919f, 0.028398f, -0.040302f, -0.033899f,\n0.118791f, -0.146617f, 0.368539f, 0.042992f, -0.017669f, -0.145768f, 0.158116f, 0.057914f, 0.067012f, -0.163185f, 0.035066f, -0.623885f, -0.162916f, 0.350589f, -0.058156f, 0.091857f, -0.102273f, -0.149376f, 0.088682f, -0.003570f, -0.165455f,\n-0.113019f, -0.103839f, -0.094995f, 0.094719f, -0.021577f, 0.101813f, 0.082491f, -0.172912f, 0.112808f, -0.068340f, 0.032662f, 0.133009f, -0.169258f, 0.096267f, 0.038775f, -0.008383f, 0.127121f, -0.052374f, 0.069456f, 0.154868f, -0.107669f,\n-0.100308f, -0.102274f, 0.157119f, 0.123780f, -0.088823f, 0.014459f, 0.179380f, -0.044973f, -0.079588f, 0.222260f, -0.067454f, 0.017224f, -0.153995f, 0.064714f, 0.017000f, -0.007195f, 0.054129f, 0.522684f, -0.147785f, 0.149830f, 0.039921f,\n-0.033299f, 0.068283f, -0.151635f, 0.072839f, 0.008312f, 0.131800f, -0.136173f, 0.128376f, -0.119247f, 0.018333f, 0.170721f, -0.116247f, 0.068241f, 0.078489f, 0.229831f, -0.100226f, -0.065685f, 0.093055f, -0.004337f, 0.217237f, 0.129073f, -0.030457f,\n-0.004111f, -0.042865f, -0.150333f, -0.160776f, 0.118082f, 0.111552f, 0.187862f, 0.016370f, 0.038258f, -0.025658f, -0.009074f, -0.452372f, 0.085359f, -0.051035f, 0.045284f, -0.151007f, 0.243967f, -0.096897f, -0.101862f, 0.301188f, 0.161834f,\n-0.073083f, 0.079863f, 0.354516f, 0.458620f, -0.037743f, 0.101605f, 0.094953f, -0.009749f, 0.049382f, 0.123109f, -0.116558f, 0.229798f, 0.124581f, 0.235922f, -0.044262f, 0.048998f, 0.160120f, -0.000793f, -0.004461f, 0.094203f, -0.162480f, 0.132211f,\n0.273052f, 0.072399f, -0.299208f, -0.291610f, 0.095120f, -0.074227f, 0.473762f, -0.037859f, -0.163206f, -0.116413f, -0.108000f, 0.269017f, 0.071384f, 0.052415f, -0.043084f, 0.019529f, 0.127816f, -0.121811f, -0.146942f, -0.104406f, -0.236722f,\n-0.589232f, -0.145135f, 0.212102f, 0.099030f, 0.014432f, -0.033746f, -0.054699f, -0.176518f, 0.046512f, 0.468163f, 0.146058f, -0.007111f, -0.048010f, 0.691838f, 0.044003f, -0.057466f, -0.239789f, -0.003641f, 0.185895f, 0.062927f, -0.124898f,\n-0.164415f, 0.401225f, 0.039683f, 0.627026f, -0.164440f, -0.107103f, 0.566111f, 0.169329f, 0.114474f, 0.096844f, 0.096894f, -0.392902f, 0.027740f, 0.325572f, 0.288950f, -0.076294f, 0.119774f, 0.057030f, -0.198331f, 0.647177f, -0.018043f, -0.100375f,\n-0.085087f, 0.108660f, 0.034147f, 0.115156f, -0.172577f, 0.896898f, -0.156600f, -0.582351f, -0.109620f, -0.123238f, -0.168651f, -0.166124f, 0.085831f, 0.041410f, 0.115600f, -0.014229f, -0.084019f, -0.073108f, 0.363561f, 0.237468f, 0.050182f,\n-0.148106f, 0.417851f, -0.046269f, 0.027449f, 0.099627f, 0.032114f, -0.615915f, 0.120680f, 0.054411f, -0.037581f, 0.042084f, -0.013706f, -0.068614f, 0.142762f, 0.008444f, 0.528850f, -0.361972f, 0.117758f, -0.568542f, -0.058473f, -0.239424f,\n-0.101133f, 0.130474f, -0.057384f, -0.501813f, -0.044295f, -0.161194f, -0.009232f, 0.043690f, -0.278498f, 0.829179f, -0.161414f, 0.060314f, -0.023334f, -0.300017f, 0.093820f, -0.161368f, 0.148739f, 0.388074f, 0.011091f, -0.279935f, -0.114458f,\n-0.094893f, 0.734748f, 0.010483f, -0.083255f, 0.020336f, -0.041870f, -0.749709f, -0.019712f, 0.352524f, 0.652827f, -0.041850f, 0.094603f, 0.031807f, -0.249486f, 0.803137f, -0.019536f, -0.020633f, -0.107474f, 0.126278f, 0.118835f, 0.192521f,\n0.076757f, 0.269821f, 0.053430f, -0.504428f, 0.063198f, 0.158682f, 0.124148f, 0.073553f, 0.058003f, 0.112186f, -0.068733f, 0.040591f, -0.268658f, -0.141202f, 0.375210f, 0.224853f, 0.097684f, -0.159653f, 0.117426f, 0.018837f, -0.061325f, 0.104983f,\n-0.005157f, -0.407671f, 0.029703f, -0.029967f, -0.085119f, -0.100156f, 0.042507f, -0.064070f, 0.137887f, 0.251164f, -0.563845f, 0.577287f, 0.109158f, -0.350871f, -0.123721f, -0.210464f, -0.014920f, -0.017104f, -0.061154f, -0.464556f, 0.596852f,\n0.086619f, 0.021180f, -0.025875f, -0.164815f, 0.826914f, 0.052363f, 0.033378f, -0.148840f, -0.339211f, -0.008111f, -0.159366f, 0.067907f, -1.006988f, 0.121628f, -0.176931f, 0.130143f, -0.141140f, 0.685846f, 0.035558f, 0.025677f, 0.029592f, 0.082848f,\n-0.219833f, -0.088595f, 0.653246f, 0.388578f, -0.020609f, -0.117953f, 0.038038f, -0.333424f, 0.767915f, -0.055367f, -0.147554f, 0.109978f, 0.065864f, 0.016961f, -0.275486f, 0.046816f, -0.346847f, 0.112329f, -0.748794f, -0.080241f, -0.080249f,\n0.138166f, -0.147917f, -0.147388f, -0.169077f, 0.119335f, 0.027813f, 0.171843f, 0.150522f, 0.205633f, 0.214043f, 0.128893f, -0.057208f, 0.189427f, -0.043739f, 0.120082f, -0.018350f, 0.044829f, -0.274958f, -0.052640f, 0.110109f, 0.148636f, 0.065665f,\n0.145428f, -0.029912f, -0.047863f, -0.202876f, -0.247568f, 1.698114f, -0.081644f, -0.638900f, 0.144372f, -0.688696f, -0.024747f, 0.053138f, 0.013207f, -0.254103f, 0.015008f, 0.020295f, 0.120475f, -0.133153f, -0.543501f, 1.126725f, -0.143200f,\n0.267321f, -0.148738f, -0.590485f, -0.172378f, 0.145606f, 0.080752f, 0.764861f, 0.102741f, -0.408996f, 0.084350f, 0.063139f, 0.657575f, -0.082147f, 0.022744f, -0.073827f, -0.020195f, 0.249218f, 0.043978f, -0.073700f, 0.065336f, -0.071055f, 0.036901f,\n0.104928f, -0.128098f, 0.019770f, -0.079223f, 0.006578f, 0.100657f, -0.167440f, -0.073747f, -0.343965f, 0.020209f, -0.950195f, 0.146152f, -0.593919f, 0.096362f, 0.164376f, -0.072134f, 0.062879f, 0.082246f, -0.036666f, -0.008845f, -0.064210f,\n0.429268f, -0.085895f, -0.021133f, 0.210742f, 0.048179f, 0.120720f, 0.716922f, -0.159015f, -0.125648f, -0.145247f, -0.088018f, -0.511748f, 0.028754f, -0.083628f, 0.094273f, -0.048749f, 0.184734f, 0.101060f, 0.074264f, -0.056150f, 1.141975f,\n0.430241f, -0.046046f, -0.571575f, 0.048020f, -0.288284f, -0.639105f, 0.091197f, 0.028928f, -0.696120f, 0.231657f, -0.084639f, -0.067886f, -0.035756f, -0.004527f, -0.335572f, -0.153794f, 0.757070f, -0.117611f, -0.467940f, -0.103039f, -0.103553f,\n-0.120036f, -0.531039f, -0.158753f, 0.416764f, 0.133581f, 0.076294f, -0.099359f, -0.081991f, 0.081921f, 0.043035f, 0.037874f, 0.209453f, -0.038818f, 0.080326f, 0.523153f, 0.031827f, 0.032209f, -0.035362f, 0.062873f, 0.011176f, -0.081295f, 0.107198f,\n-0.054592f, -0.103741f, 0.092557f, -0.787826f, -0.127914f, -0.221936f, 0.153877f, -0.336282f, 0.096047f, 0.064954f, -0.101807f, -0.041426f, 0.082767f, -0.079749f, -0.153365f, -0.115981f, 0.428894f, 0.152231f, 0.128068f, 0.157421f, 0.027988f,\n-0.163620f, -0.171847f, -0.174592f, -0.115252f, 0.139168f, -0.071016f, 0.037663f, -0.171857f, 0.059389f, 0.165246f, -0.095436f, 0.019741f, -0.159972f, 0.007135f, -0.631000f, 0.566420f, -0.153736f, -0.112924f, -0.363351f, -0.174787f, -0.239674f,\n-0.256750f, -0.018808f, -0.055374f, -0.000867f, -0.153551f, 0.077040f, 0.147215f, -0.076751f, -0.213987f, -0.418588f, 0.138517f, 0.306758f, 0.040965f, -0.178398f, -0.024884f, -0.144426f, 0.063501f, 0.055918f, 0.086801f, 0.539355f, -0.041095f,\n0.012865f, 0.088389f, 0.120501f, 0.040006f, -0.036238f, 0.090239f, 0.075366f, 0.123500f, 0.605882f, 0.115954f, 0.001299f, -0.053092f, -0.069533f, -0.369319f, 0.415318f, -0.167637f, 0.116195f, -0.044374f, 0.065874f, -0.170093f, -0.533485f, -0.035072f,\n0.187120f, -0.084238f, -0.103695f, 0.130285f, -0.029196f, -0.163984f, -0.054597f, -0.002110f, 0.048979f, 0.093742f, 0.109221f, 0.157160f, -0.019841f, 0.585723f, 0.359630f, -0.025389f, 0.150497f, -0.782638f, -0.167678f, 0.001582f, 0.103569f,\n-0.141407f, 0.460502f, 0.119320f, 0.042935f, -0.100444f, 0.038400f, 0.004049f, 0.119109f, -0.033639f, -0.597616f, 0.198878f, -0.220911f, -0.061810f, -0.557619f, 0.084703f, -0.046840f, 0.039904f, 0.119073f, -0.168356f, 0.233212f, -0.074305f,\n-0.031350f, -0.089420f, -0.026947f, 0.158903f, -0.112281f, -0.144235f, 0.272715f, 0.062862f, 0.078373f, -0.074763f, 0.066858f, -0.031093f, -0.346507f, -0.021897f, 0.292377f, 0.023524f, -0.144174f, -0.224725f, -0.158017f, 0.016127f, 0.122012f,\n0.007373f, 0.252506f, -0.025738f, 0.134534f, 0.349378f, 0.019212f, -0.024763f, -0.022350f, 0.005761f, 0.049733f, -0.106563f, 0.032363f, -0.168799f, 0.003948f, 0.102217f, 0.235649f, -0.056019f, 0.118926f, -0.124257f, 0.003291f, 0.167071f, 0.061045f,\n0.133432f, -0.082531f, -0.069294f, -0.069181f, 0.100869f, 0.101386f, 0.243162f, -0.151558f, 0.117501f, 0.061018f, -0.046692f, 0.160849f, -0.057864f, 0.005474f, -0.122942f, 0.146653f, -0.076644f, 0.521701f, 0.149915f, -0.087980f, 0.015380f, 0.027321f,\n0.017076f, -0.007476f, -0.000950f, 0.006473f, 0.062178f, -0.083945f, 0.066058f, -0.115211f, -0.036604f, -0.110019f, -0.287599f, -0.129378f, -0.168725f, 0.257992f, 0.040253f, 0.127591f, -0.077279f, 0.018561f, 0.505032f, 0.063436f, 0.018506f,\n0.077139f, 0.002776f, 0.188980f, 0.007456f, -0.045526f, 0.076588f, -0.070342f, -0.115467f, 0.189335f, -0.080171f, -0.078445f, -0.420111f, 0.004251f, 0.055376f, -0.057615f, 0.113220f, 0.346724f, -0.022493f, -0.148396f, 0.416750f, -0.051978f,\n0.099682f, -0.067118f, 0.400519f, 0.464674f, 0.090728f, -0.076864f, 0.050349f, -0.033515f, -0.161200f, 0.455358f, -0.150210f, 0.229902f, -0.086606f, 0.062729f, 0.027924f, 0.023196f, 0.095699f, 0.020356f, 0.048311f, -0.038373f, 0.055533f, 0.129554f,\n0.126297f, 0.059725f, -0.296448f, -0.510940f, 0.021831f, 0.093295f, 0.485364f, -0.107690f, -0.008360f, 0.117178f, -0.072953f, 0.648871f, 0.023086f, -0.073700f, -0.056431f, -0.074809f, -0.082234f, -0.014711f, 0.104776f, 0.096983f, -0.112161f,\n-0.426364f, -0.108190f, 0.080947f, 0.146988f, -0.289722f, -0.032518f, -0.037979f, -0.184752f, -0.189345f, 0.442178f, 0.131891f, -0.057426f, -0.086838f, 0.883071f, 0.037525f, 0.011685f, 0.155975f, -0.103391f, 0.185069f, 0.036150f, -0.124046f,\n0.137470f, 0.365352f, 0.016401f, 0.980922f, -0.131651f, 0.017175f, 0.419363f, 0.132695f, -0.118819f, -0.133846f, 0.126294f, -0.683254f, -0.105660f, 0.206871f, -0.179785f, 0.017213f, 0.040866f, -0.120351f, -0.081869f, 0.582800f, 0.059952f, -0.150234f,\n-0.160520f, -0.019825f, -0.048062f, -0.039351f, 0.009468f, 0.802466f, 0.043823f, -0.222200f, -0.157807f, 0.103610f, -0.089748f, -0.100691f, -0.089900f, 0.130443f, 0.136835f, -0.068991f, -0.187560f, 0.103001f, 0.245257f, -0.030085f, -0.070817f,\n0.088070f, 0.450220f, 0.061659f, -0.141909f, 0.081200f, 0.086240f, -0.270144f, -0.018111f, 0.093617f, 0.141073f, 0.168613f, 0.084214f, -0.101400f, 0.137343f, 0.192008f, 0.317805f, -0.355875f, 0.008646f, -0.271067f, -0.008741f, -0.027153f, -0.128651f,\n0.151773f, 0.016122f, -0.383502f, -0.109359f, 0.009416f, -0.020242f, -0.157412f, -0.287941f, 1.186303f, 0.033041f, -0.113112f, -0.128888f, -0.117959f, -0.129750f, 0.065577f, 0.063318f, 0.255455f, 0.080658f, -0.596319f, 0.053566f, 0.031630f,\n0.564476f, -0.083940f, 0.063286f, 0.100268f, -0.149732f, -0.789700f, -0.055600f, 0.219504f, 0.220875f, -0.040876f, -0.074913f, -0.061162f, -0.033993f, 0.814527f, -0.082447f, 0.039929f, 0.094216f, -0.166768f, 0.082425f, -0.066950f, 0.034819f,\n0.135940f, 0.051423f, -0.072925f, 0.134492f, 0.080265f, -0.159730f, 0.023092f, 0.130097f, 0.135800f, -0.129663f, -0.114562f, -0.339336f, -0.000449f, 0.340487f, 0.134674f, -0.178578f, -0.156599f, -0.102286f, -0.033821f, -0.113248f, 0.016756f,\n-0.035736f, -0.412982f, -0.038017f, 0.005451f, -0.062712f, -0.158724f, -0.156605f, -0.053631f, 0.070632f, 0.571470f, -0.853330f, 0.838756f, 0.065541f, 0.000751f, -0.161728f, -0.143429f, 0.678534f, 0.025460f, -0.075708f, -0.115297f, 0.131817f,\n-0.066534f, 0.121776f, -0.113445f, -0.580766f, 0.855394f, -0.128056f, -0.056542f, -0.081533f, -0.183085f, -0.044458f, -0.021071f, -0.031042f, -1.024087f, 0.078131f, -0.274691f, 0.112429f, 0.084153f, 0.450108f, 0.048553f, -0.142348f, -0.152670f,\n-0.135127f, -0.781403f, -0.036859f, 0.205259f, 0.242341f, 0.053142f, -0.069415f, -0.109172f, -0.288352f, 0.623963f, -0.054877f, -0.078051f, -0.025451f, 0.079627f, 0.125397f, -0.401682f, -0.171239f, -0.486951f, 0.059756f, -0.758271f, -0.103615f,\n-0.011778f, 0.152598f, -0.123006f, 0.122764f, -0.117871f, -0.169435f, 0.009893f, 0.081133f, -0.030584f, -0.058718f, 0.163656f, -0.098025f, 0.085024f, 0.414222f, -0.157594f, 0.155649f, -0.028914f, 0.133610f, -0.349256f, -0.068178f, 0.113006f,\n-0.034771f, 0.028493f, -0.086326f, 0.015289f, 0.100011f, 0.655896f, 0.459318f, 0.963922f, 0.008514f, -0.441763f, -0.091275f, -0.509994f, 0.190613f, -0.109977f, -0.033133f, -0.217770f, 0.146626f, -0.095607f, -0.053073f, -0.069577f, -0.502989f,\n1.028227f, -0.123606f, 0.249476f, -0.043873f, -0.409329f, 0.131884f, -0.104158f, -0.005947f, 0.860846f, -0.082917f, -0.347950f, -0.046321f, -0.110153f, 0.343418f, 0.003657f, 0.154021f, -0.036835f, 0.023096f, -0.441811f, -0.042191f, 0.324237f,\n-0.296836f, 0.068507f, -0.050237f, -0.166837f, -0.294672f, -0.049346f, 0.033493f, -0.085767f, -0.045283f, -0.072146f, -0.055325f, -0.691353f, -0.118801f, -1.079242f, 0.106984f, -0.656738f, -0.010821f, -0.153080f, -0.124655f, 0.092292f, -0.053075f,\n-0.174524f, 0.102052f, -0.150383f, 0.375674f, -0.151638f, 0.174635f, 0.006832f, 0.144676f, -0.102629f, 1.403694f, 0.111652f, -0.105439f, -0.166360f, 0.026544f, -0.428851f, -0.113309f, 0.094131f, 0.094945f, -0.002490f, 0.060766f, -0.119734f,\n-0.101165f, 0.024804f, 0.997733f, -0.215095f, -0.048356f, -0.346362f, 0.112575f, -0.319617f, -0.488425f, 0.004830f, -0.086731f, -0.390031f, -0.366725f, 0.063450f, 0.045969f, -0.073262f, -0.244642f, -0.546636f, -0.099286f, 0.114208f, -0.051682f,\n-0.527341f, -0.136376f, -0.067193f, 0.162818f, -0.730688f, 0.145329f, 0.524391f, -0.033892f, 0.157337f, 0.361053f, 0.002959f, 0.035340f, 0.018849f, -0.036438f, -0.080764f, 0.122336f, 0.453754f, 0.471661f, 0.126390f, -0.062218f, 0.044074f, -0.425214f,\n0.493568f, 0.095449f, -0.106695f, 0.093271f, 0.055956f, -0.117137f, -1.096167f, 0.081608f, -0.182974f, 0.122893f, -0.477817f, -0.046533f, -0.092695f, -0.120076f, -0.077382f, -0.054471f, 0.148556f, -0.126461f, 0.127338f, 0.156802f, 0.064797f,\n0.589236f, 0.474090f, -0.099415f, -0.047770f, -0.669864f, -0.117163f, -0.125554f, 0.027365f, 0.142276f, -0.180519f, 0.031162f, -0.062544f, -0.047126f, -0.161063f, 0.001176f, 0.011114f, -0.048168f, -0.577212f, 0.367234f, -0.376465f, -0.021729f,\n-0.683176f, -0.060300f, -0.388610f, -0.067045f, 0.081409f, -0.012328f, 0.067155f, -0.404023f, -0.031494f, -0.082913f, 0.000460f, -0.652640f, -0.589619f, -0.165222f, 0.075795f, -0.071951f, -0.348838f, -0.084208f, -0.056296f, -0.119871f, 0.042434f,\n0.028135f, 0.452468f, 0.110283f, 0.098015f, 0.174794f, 0.110937f, -0.061428f, 0.086061f, -0.133219f, 0.067586f, -0.133902f, 0.864208f, 0.250719f, 0.027403f, 0.041255f, -0.137566f, -0.928233f, 0.468450f, -0.061298f, 0.122563f, 0.019484f, -0.154322f,\n-0.175457f, -0.868506f, 0.139812f, 0.332484f, 0.025961f, -0.361420f, 0.126159f, 0.086267f, -0.035454f, -0.062364f, 0.071189f, -0.068371f, 0.152126f, 0.043416f, 0.317380f, 0.102375f, 0.782191f, 0.822720f, -0.169975f, 0.006777f, -1.303894f, 0.022686f,\n0.090177f, -0.124401f, 0.073202f, 0.440697f, 0.105934f, -0.028443f, -0.153621f, -0.064430f, -0.089738f, -0.144599f, -0.047841f, -0.597117f, 0.443839f, -0.213952f, -0.156760f, -0.812321f, 0.084526f, -0.211254f, -0.530493f, -0.170629f, -0.101642f,\n0.674203f, -0.112645f, -0.110931f, -0.028825f, 0.025380f, 0.187704f, -0.420700f, -0.133470f, 0.697671f, -0.032829f, -0.294025f, 0.032561f, -0.149995f, -0.167060f, 0.076534f, 0.004269f, 0.374502f, 0.071588f, 0.075622f, -0.064120f, -0.128656f,\n-0.099559f, 0.115804f, 0.042654f, 0.206107f, -0.073875f, 0.027202f, 0.582876f, 0.159915f, -0.052684f, -0.157860f, -0.001059f, -0.114417f, 0.077147f, 0.061402f, 0.055814f, 0.105043f, -0.034735f, 0.233048f, 0.142340f, 0.194260f, -0.021672f, -0.090510f,\n0.062758f, -0.066099f, -0.139676f, 0.074766f, -0.116824f, -0.002131f, -0.067473f, 0.068162f, 0.208984f, 0.093376f, -0.019206f, -0.115782f, 0.069261f, 0.001697f, -0.289111f, 0.160079f, 0.164505f, 0.060548f, 0.004100f, 0.489987f, 0.056368f, -0.015744f,\n-0.001237f, -0.013637f, 0.041690f, -0.060366f, -0.168982f, -0.240260f, -0.158105f, -0.040860f, 0.065354f, -0.071152f, -0.032071f, 0.012594f, -0.299832f, 0.008537f, -0.081079f, 0.046395f, -0.011898f, 0.128467f, -0.154587f, -0.113096f, 0.606602f,\n0.176871f, 0.140362f, 0.582529f, -0.048005f, 0.031662f, 0.061614f, -0.026387f, 0.104557f, -0.215663f, 0.024930f, 0.727302f, -0.045088f, -0.081619f, -0.562802f, 0.036108f, 0.028341f, -0.151788f, 0.014652f, 0.421875f, -0.048084f, -0.189476f, 0.809361f,\n-0.074210f, 0.064017f, 0.018295f, 0.080290f, 0.508075f, 0.060288f, 0.036489f, -0.123149f, 0.053201f, 0.139824f, 0.144511f, 0.072673f, -0.014499f, -0.062428f, 0.008042f, -0.000931f, -0.016204f, -0.137221f, 0.051274f, 0.045072f, -0.030949f, 0.100489f,\n-0.076677f, 0.423263f, -0.088207f, -0.547100f, -0.652281f, -0.059490f, -0.056808f, 0.345286f, -0.096267f, -0.075747f, 0.019336f, -0.145091f, 0.574104f, -0.074497f, -0.101843f, -0.167212f, -0.158475f, 0.110955f, -0.014467f, -0.100989f, -0.081761f,\n-0.026681f, -0.514241f, 0.055579f, -0.257074f, -0.153214f, -0.190699f, -0.038603f, 0.142805f, 0.045046f, -0.248917f, 0.617838f, 0.139572f, -0.103778f, -0.045523f, 0.745013f, 0.277820f, -0.145870f, 0.194536f, -0.122054f, -0.097610f, -0.138183f,\n-0.122122f, -0.153779f, 0.593562f, -0.079906f, 0.895690f, 0.163403f, -0.125286f, 0.341310f, 0.007722f, -0.052631f, -0.017974f, -0.081607f, -0.330691f, 0.055730f, 0.026773f, -0.465384f, -0.042992f, -0.005423f, -0.161203f, 0.193762f, 0.591305f,\n-0.148265f, 0.092197f, 0.039322f, -0.042604f, -0.122780f, 0.164694f, 0.047875f, 1.218976f, -0.066188f, -0.234169f, 0.024767f, 0.045865f, 0.001757f, 0.158833f, 0.033651f, 0.130341f, 0.001747f, -0.041399f, 0.233380f, -0.123664f, -0.215472f, -0.234341f,\n0.003427f, 0.062985f, 0.317811f, 0.099303f, -0.122459f, -0.006311f, 0.016439f, 0.005521f, -0.038854f, -0.112362f, 0.069589f, 0.024518f, -0.022326f, -0.141811f, 0.095439f, 0.283584f, -0.172793f, -0.316424f, -0.065356f, -0.033416f, 0.119303f,\n-0.396646f, -0.318282f, 0.028038f, 0.050950f, -0.431465f, -0.269033f, -0.110453f, -0.116412f, -0.052173f, -0.035985f, 1.147357f, -0.149834f, -0.128256f, -0.072632f, -0.516354f, -0.033091f, -0.059234f, -0.026180f, 0.395939f, 0.040677f, -0.534987f,\n-0.072698f, -0.149802f, 0.271418f, 0.099311f, 0.051339f, -0.039242f, -0.143387f, -0.662105f, 0.102707f, 0.012675f, -0.177845f, -0.107468f, 0.151077f, -0.120152f, -0.249787f, 0.460486f, -0.004720f, 0.078124f, -0.026013f, 0.107683f, -0.038040f,\n0.018779f, 0.088290f, 0.009688f, 0.161171f, -0.376588f, 0.049790f, -0.069513f, 0.006039f, -0.071800f, -0.159935f, -0.172682f, 0.102379f, -0.140449f, -0.875437f, 0.070051f, 0.295907f, 0.129824f, -0.077090f, 0.039624f, -0.103481f, -0.124347f,\n0.114907f, -0.074424f, -0.076915f, -0.469286f, 0.028950f, -0.110779f, -0.106099f, -0.080737f, -0.140407f, -0.119839f, 0.154557f, 0.881576f, -0.115825f, 0.904210f, -0.011457f, 0.071899f, -0.017413f, 0.159487f, 1.331001f, 0.015528f, -0.145026f,\n0.187323f, 0.414848f, 0.082920f, 0.136814f, -0.033955f, -0.845937f, 1.110619f, -0.102342f, -0.242880f, 0.062288f, 0.437969f, 0.009992f, -0.099642f, -0.061305f, -1.057592f, -0.044907f, -0.447989f, -0.140706f, -0.162313f, 0.346185f, 0.159940f,\n-0.015755f, 0.035373f, -0.099420f, -1.202381f, 0.014430f, -0.569073f, 0.213684f, -0.042637f, -0.151377f, 0.110922f, -0.030729f, -0.158712f, -0.172215f, -0.156838f, -0.162149f, 0.113818f, 0.043806f, 0.109666f, -0.033279f, -1.016600f, 0.124147f,\n-0.483378f, 0.156294f, -0.141435f, -0.059268f, -0.144280f, -0.144262f, 0.121810f, 0.102954f, 0.049709f, -0.911380f, -0.019410f, -0.048086f, -0.259702f, 0.130060f, 0.161345f, 1.047718f, -0.059750f, -0.037378f, -0.154208f, 0.031373f, -0.909927f,\n-0.122223f, 0.110877f, -0.002964f, -0.104057f, -0.207887f, -0.113199f, -0.143953f, 1.151245f, 1.161350f, 1.003350f, -0.003301f, 0.006862f, -0.102244f, 0.080668f, 0.759200f, -0.069038f, 0.107783f, -0.558809f, 1.116434f, 0.011030f, 0.113875f,\n-0.056844f, -0.730919f, 1.104172f, -0.015916f, 0.525546f, -0.003172f, 0.364502f, 0.152351f, -0.166901f, 0.116130f, 0.772443f, -0.175305f, -0.529954f, -0.061037f, 0.092500f, 0.300525f, -0.166098f, 0.150534f, 0.082359f, -0.082973f, -1.216716f,\n0.023034f, -0.248072f, -0.164516f, 0.035678f, 0.011547f, -0.041499f, 0.020228f, -0.049209f, -0.026354f, -0.091938f, -0.128639f, -0.124341f, 0.051253f, -0.568982f, 0.152958f, -1.289821f, -0.024211f, -0.583737f, 0.107522f, -0.022861f, 0.000006f,\n-0.008852f, 0.039242f, 0.122137f, -0.011361f, 0.127194f, -0.314232f, 0.088709f, 0.221585f, -0.224933f, -0.014166f, 0.151987f, 1.094866f, -0.056609f, 0.117929f, 0.100956f, -0.053635f, -0.326931f, -0.018535f, -0.140802f, 0.027198f, 0.052491f,\n-0.161229f, -0.047639f, -0.092698f, 0.092313f, 0.356008f, -0.192455f, -0.068653f, -0.329037f, 0.010737f, -0.058846f, -0.390059f, 0.141347f, -0.089523f, -0.527815f, 0.063689f, -0.091204f, -0.090829f, 0.031793f, -0.148673f, -0.043174f, 0.120187f,\n0.442715f, 0.081120f, -0.152364f, 0.034278f, 0.015750f, 0.080725f, -0.850410f, -0.152462f, 0.826650f, -0.100885f, 0.057040f, 0.160401f, -0.062677f, -0.039961f, -0.038209f, -0.020455f, -0.713187f, 0.082831f, 0.310514f, -0.081123f, 0.063370f,\n0.024140f, 0.028657f, -0.223312f, 0.042596f, -0.040187f, 0.107848f, 0.096080f, 0.058859f, 0.080924f, -0.792976f, 0.087731f, 0.140226f, -0.164286f, -0.226145f, -0.042898f, 0.166572f, -0.095738f, 0.010317f, -0.011124f, 0.057039f, 0.047309f, -0.116746f,\n-0.287391f, 0.019428f, 0.730522f, 0.119826f, -0.151707f, -0.062289f, -0.455986f, 0.100962f, 0.151682f, 0.030490f, -0.041354f, -0.050771f, -0.048936f, 0.124895f, 0.076557f, -0.031338f, 0.046273f, -0.095879f, -0.148506f, -0.350108f, 0.017449f,\n-1.373146f, -0.141630f, -0.191827f, 0.028952f, 0.021700f, 0.196403f, 0.020634f, 0.033830f, -0.158110f, -0.318327f, 0.082697f, 0.067593f, 0.095452f, -0.761629f, -0.648762f, -0.028091f, -0.109494f, 0.030495f, 0.023145f, 0.038641f, -0.004698f,\n-0.084495f, 0.168050f, -0.006353f, 0.317041f, -0.153637f, -0.010175f, -0.018063f, 0.008269f, 0.027119f, 0.091100f, -0.070785f, -0.287162f, 0.047297f, 0.863089f, -0.202841f, -0.078482f, -0.108513f, -0.025774f, -0.901349f, -0.046250f, -0.104458f,\n0.044097f, 0.114001f, 0.078784f, 0.020275f, -0.689606f, -0.086302f, 0.643242f, 0.002123f, 0.219547f, -0.086876f, 0.093114f, 0.136850f, -0.150409f, -0.131467f, -0.054743f, -0.039217f, 0.136960f, 0.347619f, 0.154931f, 0.837690f, 0.793423f, 0.093541f,\n-0.166799f, -1.320444f, -0.055052f, -0.133873f, 0.138049f, 0.095587f, 0.224280f, -0.153895f, -0.100749f, 0.137716f, -0.014238f, 0.071817f, -0.032567f, 0.001016f, -0.028103f, 0.147711f, -0.653698f, 0.010397f, -0.424600f, 0.126924f, -0.008714f,\n-0.397994f, -0.038724f, 0.091091f, 0.780883f, 0.130340f, 0.003688f, -0.165734f, 0.001680f, 0.070564f, -0.815860f, 0.059301f, 0.372615f, 0.053397f, -0.177839f, -0.027081f, -0.141460f, 0.043093f, 0.170009f, 0.055128f, 0.473807f, -0.168821f, -0.109528f,\n-0.144449f, -0.102449f, 0.089947f, 0.069231f, 0.131498f, -0.043838f, -0.099574f, 0.108531f, 0.622347f, 0.108019f, -0.020225f, 0.071499f, -0.045528f, -0.128473f, -0.088228f, -0.153037f, -0.032830f, -0.111905f, 0.119338f, 0.190174f, -0.155676f,\n0.003493f, -0.012363f, -0.080963f, -0.116928f, -0.069710f, 0.043020f, 0.138558f, -0.127618f, 0.075099f, -0.081071f, -0.029256f, 0.292281f, -0.048708f, 0.325559f, 0.276436f, -0.088096f, 0.082545f, -0.352494f, -0.158076f, -0.133629f, 0.086316f,\n-0.132764f, 0.809556f, 0.114713f, -0.155191f, -0.025910f, 0.010382f, 0.023441f, -0.096701f, 0.140638f, -0.013818f, 0.108289f, 0.267752f, 0.063565f, -0.179466f, -0.111734f, -0.101973f, -0.288643f, 0.071189f, 0.088982f, 0.045757f, 0.226915f,\n-0.115392f, -0.155383f, 0.111484f, 0.495043f, -0.150791f, -0.013116f, 0.477931f, -0.116001f, -0.110558f, -0.025357f, -0.007678f, 0.037164f, -0.290367f, 0.005868f, 0.548947f, 0.160419f, -0.052682f, -0.350934f, 0.141744f, 0.090741f, -0.093693f,\n-0.134553f, 0.471708f, -0.062424f, -0.142287f, 0.683193f, -0.034862f, -0.005829f, 0.030313f, 0.332105f, 0.657155f, 0.001619f, -0.170030f, -0.164772f, -0.054516f, -0.154170f, 0.176764f, -0.049601f, 0.148462f, -0.076659f, -0.058298f, -0.049723f,\n0.016737f, 0.150935f, -0.121779f, 0.043504f, 0.144205f, -0.001890f, 0.046756f, 0.345907f, -0.018657f, -0.316539f, -0.407106f, 0.104285f, -0.112137f, 0.637405f, -0.002994f, 0.003040f, 0.115207f, 0.135092f, 0.558295f, 0.124418f, -0.020623f, 0.019576f,\n0.020892f, -0.017903f, -0.028911f, 0.155096f, 0.141155f, -0.001072f, -0.194019f, 0.025956f, -0.194355f, 0.114349f, -0.276711f, -0.229150f, -0.173888f, -0.090028f, -0.151307f, 0.685750f, -0.108560f, -0.089545f, 0.012814f, 0.984618f, 0.581540f,\n0.066813f, 0.181931f, -0.051884f, 0.141867f, 0.087875f, 0.105595f, 0.132185f, 0.477028f, -0.119473f, 1.163033f, -0.012833f, 0.029117f, 0.437815f, -0.105239f, 0.067595f, -0.011720f, 0.031234f, -0.282839f, 0.006262f, 0.063163f, -0.664195f, -0.102124f,\n0.037333f, -0.076015f, 0.257313f, 0.440920f, -0.055967f, -0.137116f, 0.112364f, 0.086902f, -0.075919f, 0.284804f, 0.122323f, 1.535680f, -0.152448f, -0.121704f, -0.146899f, 0.058169f, 0.090805f, 0.150257f, -0.081639f, -0.098007f, -0.030648f,\n0.057710f, 0.656989f, -0.041801f, -0.407565f, 0.112361f, -0.071880f, -0.024615f, 0.251279f, 0.164453f, -0.086819f, -0.096627f, -0.143430f, 0.226281f, 0.018646f, -0.147920f, -0.092966f, 0.111654f, -0.011261f, 0.165819f, -0.013150f, 0.273204f,\n-0.633673f, -0.130815f, 0.129086f, -0.097217f, -0.088626f, -0.253296f, -0.291652f, -0.137188f, -0.045027f, -0.364067f, -0.711417f, 0.149637f, 0.053017f, 0.096542f, -0.344000f, 1.418816f, -0.088463f, -0.260476f, -0.160595f, -0.446347f, 0.059786f,\n0.007557f, -0.141230f, 0.168981f, -0.099511f, -0.933264f, -0.009879f, -0.085181f, 0.426377f, -0.162975f, -0.149802f, 0.044361f, -0.151230f, -0.452155f, -0.125460f, 0.009198f, -0.206413f, -0.111892f, -0.156588f, 0.142090f, 0.074786f, 0.416453f,\n0.088347f, 0.009962f, 0.039197f, -0.171139f, -0.043883f, 0.251231f, -0.043592f, 0.210232f, -0.104154f, -0.690937f, -0.140361f, -0.150438f, 0.143368f, 0.124906f, 0.018934f, -0.015998f, -0.114087f, 0.156292f, -1.105150f, 0.059927f, 0.240061f,\n-0.246385f, 0.116510f, 0.124770f, -0.024825f, -0.007311f, 0.073331f, -0.100335f, 0.115351f, -0.419145f, -0.106198f, -0.021237f, -0.123112f, -0.163951f, 0.101498f, 0.085695f, 0.076795f, 0.970884f, 0.118719f, 0.814779f, -0.099062f, -0.026672f,\n0.106999f, 0.313149f, 1.113628f, -0.042756f, -0.009509f, -0.058257f, 0.347336f, -0.043994f, -0.058842f, 0.162789f, -0.480805f, 1.199099f, 0.005261f, 0.154207f, -0.150531f, 0.360796f, 0.038408f, -0.167601f, -0.092042f, -1.549220f, 0.035626f,\n-0.555918f, -0.065059f, -0.151103f, 0.166451f, 0.151315f, -0.168600f, -0.166740f, 0.135930f, -0.810720f, -0.156640f, -0.266629f, -0.234761f, -0.108674f, 0.003203f, -0.139788f, -0.222636f, -0.527667f, -0.067493f, 0.093229f, 0.163862f, -0.089612f,\n-0.134048f, -0.006101f, -0.133714f, -1.510932f, 0.062823f, -0.016160f, -0.138756f, 0.034628f, -0.017841f, 0.087332f, 0.145237f, 0.159074f, 0.066353f, 0.100172f, -1.189749f, -0.000813f, 0.453245f, 0.038166f, 0.059666f, -0.095958f, 1.530645f,\n0.087764f, 0.042601f, -0.145080f, 0.040975f, -1.376315f, 0.092474f, 0.087376f, -0.090601f, 0.111560f, -0.194667f, -0.125224f, -0.162919f, 1.552629f, 1.458609f, 0.912904f, -0.018688f, 0.025149f, -0.018054f, 0.400341f, 1.111360f, 0.016829f, -0.108551f,\n-0.073556f, 1.172627f, -0.080878f, -0.121630f, 0.028481f, -0.951960f, 1.042903f, -0.157509f, 0.292360f, -0.081609f, 0.407023f, -0.024928f, -0.055933f, 0.048536f, 0.594001f, 0.038109f, -0.267126f, 0.024709f, -0.132989f, 0.297930f, -0.149708f,\n0.110342f, -0.058747f, -0.085244f, -1.338503f, -0.074265f, 0.374213f, -0.482711f, 0.110189f, 0.095990f, 0.004855f, -0.169360f, 0.413559f, 0.135939f, 0.150139f, -0.085688f, -0.074608f, -0.112207f, -0.653042f, -0.042256f, -1.348570f, 0.131418f,\n-0.911884f, 0.009060f, -0.096272f, -0.112500f, 0.158488f, -0.089633f, -0.152112f, 0.069479f, -0.063443f, -0.540847f, -0.003893f, 0.342040f, 0.012506f, -0.156554f, 0.113982f, 0.071889f, 0.014394f, -0.014194f, 0.003132f, 0.083814f, -0.121145f,\n-0.148791f, -0.015723f, -0.085686f, -0.016097f, 0.126850f, -0.030970f, -0.046528f, 0.402205f, 0.580551f, -0.437022f, 0.099344f, -0.473606f, 0.086071f, -0.299076f, 0.286027f, -0.072675f, -0.032193f, -0.309542f, 0.290556f, 0.004832f, 0.042364f,\n0.041927f, -0.616568f, -0.258496f, -0.062645f, 0.071534f, 0.092308f, -0.199108f, -0.048960f, -0.142307f, -0.107259f, -0.996967f, -0.113272f, 1.120450f, 0.127578f, -0.096832f, 0.411766f, 0.087300f, -0.024440f, 0.098269f, 0.034398f, -1.144005f,\n-0.025609f, 0.384916f, 0.018945f, 0.062610f, -0.071445f, -0.154884f, -0.146061f, 0.449647f, 0.095739f, -0.028417f, -0.082213f, -0.085622f, -0.075937f, -1.024626f, 0.112706f, 0.445312f, -0.014404f, -0.643231f, 0.146434f, -0.040985f, -0.147070f,\n-0.146889f, 0.106156f, -0.169601f, -0.031609f, 0.065869f, -0.497706f, 0.007122f, 0.910103f, 0.351238f, -0.078295f, -0.056218f, -1.367535f, 0.024373f, 0.037169f, -0.150689f, 0.071496f, -0.160883f, -0.142225f, 0.026309f, -0.119613f, 0.132793f,\n0.107641f, -0.048009f, -0.006132f, -0.091334f, -0.143066f, -1.134416f, 0.007482f, -0.351983f, 0.138302f, 0.021362f, 0.573720f, 0.144894f, 0.098271f, 0.011219f, 0.051432f, 0.063559f, 0.123827f, 0.128598f, -0.850998f, -0.599403f, 0.110176f, 0.275245f,\n-0.016039f, 0.020006f, -0.041713f, 0.156913f, -0.088447f, 0.485970f, -0.011499f, 0.352825f, 0.087457f, -0.067693f, -0.232081f, -0.068480f, -0.131330f, -0.144756f, 0.022524f, -0.718809f, -0.098496f, 0.688568f, -0.385786f, -0.117863f, 0.041115f,\n-0.127659f, -0.661038f, -0.520343f, -0.020711f, 0.039974f, -0.071094f, -0.024813f, -0.156027f, -0.463539f, -0.042406f, 0.561985f, 0.012222f, 0.373405f, -0.107660f, -0.089613f, -0.042872f, -0.120091f, -0.014758f, -0.055366f, -0.039123f, 0.071638f,\n0.393585f, -0.111888f, 0.958773f, 0.928140f, -0.013151f, -0.081899f, -1.463152f, -0.077125f, -0.103351f, 0.006528f, -0.166659f, 0.391320f, -0.014177f, 0.122586f, -0.148950f, -0.040936f, -0.135560f, 0.057943f, -0.083359f, 0.161424f, 0.520336f,\n-0.917724f, 0.072994f, 0.159215f, 0.060076f, 0.406415f, -0.219447f, -0.017187f, 0.038902f, 1.308535f, 0.027867f, 0.158949f, 0.060084f, 0.069655f, 0.325919f, -1.491795f, 0.003767f, 0.260245f, -0.140241f, 0.367899f, 0.088412f, 0.070470f, -0.113778f,\n0.140361f, 0.131092f, 0.213900f, -0.050871f, 0.106958f, 0.182931f, -0.103323f, -0.108197f, -0.096947f, 0.108747f, -0.123101f, 0.072750f, 0.251607f, 0.581822f, -0.012246f, -0.159487f, -0.075572f, -0.169157f, -0.372764f, -0.008480f, 0.091722f,\n0.093021f, 0.034643f, -0.128271f, 0.245491f, -0.131830f, 0.024203f, -0.154276f, -0.154095f, 0.084673f, 0.075269f, -0.159214f, -0.108538f, -0.004344f, -0.148574f, -0.001645f, 0.021384f, 0.363728f, -0.084916f, 0.279303f, 0.409023f, -0.065566f,\n0.105332f, -0.328643f, -0.166258f, 0.018285f, -0.162939f, 0.037932f, 0.613784f, 0.105045f, -0.166336f, -0.065762f, -0.146102f, -0.032652f, 0.057134f, 0.118013f, 0.113037f, -0.064109f, 0.365100f, 0.017531f, -0.254138f, -0.110304f, 0.017306f,\n-0.099965f, -0.075471f, 0.079112f, 0.395738f, -0.139462f, -0.012855f, -0.125058f, -0.168240f, 0.756902f, -0.320824f, -0.089358f, 0.495321f, 0.039833f, -0.133039f, 0.144701f, 0.095554f, -0.004487f, -0.358583f, -0.150355f, 0.774967f, 0.079322f,\n-0.023243f, -0.664474f, -0.029457f, -0.042318f, -0.091225f, 0.089697f, 0.469015f, -0.053704f, -0.165038f, 0.797516f, 0.125775f, -0.020876f, -0.160483f, 0.257861f, 0.884639f, 0.043328f, -0.036978f, 0.015343f, -0.163755f, 0.046332f, 0.371320f,\n0.084734f, 0.193928f, -0.061379f, -0.074917f, -0.034485f, -0.139330f, 0.064472f, 0.139757f, -0.039634f, 0.001355f, 0.136090f, -0.039832f, 0.462763f, -0.154234f, -0.371476f, -0.626527f, -0.169733f, 0.023097f, 0.485812f, -0.072254f, -0.036382f,\n0.109911f, 0.100563f, 0.467966f, -0.152154f, -0.022637f, 0.109288f, 0.119267f, -0.193099f, 0.146869f, 0.020667f, 0.188854f, -0.012289f, -0.375086f, -0.004769f, -0.211515f, -0.152616f, -0.241596f, -0.017247f, 0.040605f, 0.011897f, -0.196111f,\n0.775204f, -0.074914f, -0.118886f, 0.158186f, 0.976482f, 0.380906f, -0.096987f, 0.499786f, -0.040720f, 0.078653f, 0.092975f, 0.098422f, -0.130823f, 0.635360f, -0.166532f, 1.116516f, -0.028822f, 0.050173f, 0.398767f, 0.127198f, -0.046795f, 0.022261f,\n0.008297f, -0.238880f, -0.074627f, 0.107459f, -0.545749f, 0.094853f, 0.127707f, 0.032078f, 0.068024f, 0.290030f, 0.055570f, 0.058240f, 0.051564f, 0.069606f, 0.061928f, 0.130764f, -0.000629f, 0.736211f, -0.003334f, 0.183006f, 0.085238f, 0.002607f,\n-0.112201f, -0.016257f, -0.045828f, 0.030184f, -0.050986f, -0.055554f, 0.532351f, 0.014617f, -0.283734f, 0.254290f, -0.139285f, -0.156281f, 0.311558f, 0.035083f, 0.067811f, -0.170636f, 0.041281f, 0.602267f, -0.169679f, -0.001326f, -0.042376f,\n-0.137741f, -0.000586f, -0.156936f, -0.025898f, 0.292753f, -0.319520f, -0.163011f, -0.122971f, 0.020626f, 0.113368f, -0.131386f, 0.037278f, -0.150384f, -0.140156f, -0.409003f, -0.572028f, -0.160774f, 0.134278f, 0.004513f, -0.493766f, 1.432964f,\n0.081759f, -0.452648f, 0.042393f, -0.231042f, -0.169054f, 0.130888f, 0.091939f, -0.042482f, -0.136700f, -0.935756f, 0.001925f, 0.079089f, 0.362349f, 0.049304f, -0.002753f, -0.171668f, -0.038261f, -0.419616f, 0.016313f, 0.283046f, -0.897201f,\n-0.015190f, 0.064813f, 0.083195f, -0.172760f, 0.049114f, 0.021199f, -0.128266f, -0.009904f, 0.062851f, -0.098109f, -0.295322f, -0.137123f, 0.079633f, 0.008314f, 0.160274f, 0.063458f, 0.166774f, 0.081854f, 0.076832f, 0.143618f, -0.041840f, -0.149103f,\n-0.131113f, -0.227870f, -0.095351f, 0.428790f, 0.369748f, 0.081885f, 0.101566f, 0.098213f, -0.020972f, -0.100727f, -0.099722f, 0.089353f, 0.391351f, -0.039410f, -0.055243f, -0.064008f, 0.042366f, 0.116799f, -0.000115f, -0.065534f, 1.118985f,\n-0.150892f, 0.907126f, 0.009356f, 0.323200f, 0.086591f, 0.253106f, 0.871135f, 0.027446f, -0.079374f, 0.177059f, -0.264928f, -0.082064f, -0.127703f, 0.038560f, -0.861251f, 0.940055f, 0.150914f, -0.838947f, -0.120557f, 0.173993f, -0.169648f,\n-0.135468f, 0.100539f, -1.132208f, -0.133738f, -0.636131f, 0.143129f, 0.022544f, 0.049004f, -0.033556f, 0.067163f, -0.000819f, 0.134655f, -1.069764f, 0.004107f, 0.390858f, -1.054374f, -0.087072f, -0.078475f, 0.095340f, -0.077360f, -0.079694f,\n0.112528f, -0.163864f, -0.025895f, 0.033012f, -0.002988f, -0.123488f, -0.047236f, -0.992184f, 0.111958f, 0.584395f, 0.018118f, -0.082049f, 0.063481f, -0.119803f, -0.115392f, -0.143123f, -0.034709f, -0.015945f, 0.644912f, 0.073879f, 0.468628f,\n0.796504f, 0.080594f, -0.157639f, 0.933375f, 0.145748f, -0.063391f, 0.052704f, -0.120427f, -0.757625f, 0.118544f, -0.159231f, 0.108978f, -0.163063f, 0.033982f, 0.034779f, -0.117347f, 0.717122f, 0.623359f, 0.787643f, -0.008792f, 0.246648f, 0.092108f,\n0.070486f, 0.615897f, 0.035350f, -0.073940f, 0.179483f, -0.374321f, 0.111223f, -0.150173f, -0.105482f, -0.978131f, 0.384401f, -0.033644f, -0.599869f, 0.126825f, -0.236329f, 0.056506f, 0.053303f, -0.039851f, 0.128243f, -0.149685f, -0.572956f,\n0.154683f, -0.171484f, 0.554188f, 0.080982f, -0.144617f, -0.141028f, -0.139659f, -1.213598f, 0.062814f, 0.174976f, -0.806127f, 0.142975f, 0.158296f, 0.079059f, 0.009951f, 0.398235f, 0.036683f, 0.063667f, 0.150168f, 0.056335f, -0.158934f, -0.821101f,\n-0.045493f, -0.876078f, -0.166711f, 0.097073f, -0.054426f, -0.134596f, 0.150833f, -0.037105f, 0.029409f, -0.124959f, -0.095531f, -0.052279f, 0.001950f, -0.014660f, 0.194727f, 0.233988f, -0.124787f, -0.060759f, -0.420349f, 0.010285f, -0.011913f,\n0.136001f, -0.136964f, -0.209446f, 0.011528f, -0.206675f, -0.153306f, -0.034734f, -0.039457f, 0.022818f, -0.027185f, 0.705290f, -0.604170f, -0.701904f, -0.132291f, 0.217535f, -0.112266f, 0.144675f, 0.161740f, -0.139493f, 0.100885f, -0.025189f,\n-1.161086f, 0.154641f, 0.071497f, 0.015172f, -0.969338f, -1.096644f, 0.008484f, -0.936152f, -0.132412f, -0.406348f, 0.137416f, -0.012340f, 0.086205f, -0.951165f, 0.107174f, -0.150764f, -0.087747f, 0.049234f, 0.287816f, 0.079114f, 0.130767f,\n-0.175504f, 0.075964f, -0.884441f, 0.105517f, 0.638084f, -0.259750f, 0.118112f, 0.118205f, 0.087361f, -0.251223f, 0.193752f, 0.053528f, -0.030357f, -0.068837f, -0.048089f, 0.059956f, -0.826942f, 0.036780f, 1.048854f, -0.148060f, 0.626551f,\n-0.157515f, -0.079982f, -0.031179f, 0.025485f, -0.087180f, 0.053602f, -0.112892f, -0.176457f, 0.174648f, -0.050525f, 1.001203f, 1.049956f, -0.136094f, -0.040626f, -1.613340f, 0.101496f, 0.007465f, -0.000436f, -0.126028f, -0.473021f, -0.034300f,\n0.152795f, -0.030562f, 0.122904f, -0.010543f, -0.024783f, -0.156446f, 0.483942f, -0.649103f, -0.912796f, 0.118093f, 0.058651f, -0.115210f, 0.458269f, 0.644158f, 0.128347f, 0.058943f, 0.565443f, -0.539750f, -0.049488f, 0.057036f, 0.019956f,\n-0.848102f, -1.673169f, -0.067656f, -0.543302f, -0.016087f, 0.002096f, -0.031547f, -0.009358f, 0.083034f, 0.645969f, -0.131253f, 0.370987f, 0.062454f, 0.120401f, -0.181253f, -0.153533f, 0.093092f, 0.121499f, -0.062021f, -0.777851f, 0.074406f,\n0.816739f, -0.065301f, 0.051544f, -0.044886f, -0.043050f, -0.752890f, -0.330975f, -0.162620f, -0.044088f, 0.082171f, -0.053339f, 0.015560f, -0.219160f, 0.095423f, 0.421291f, -0.082498f, 0.454026f, 0.034082f, 0.068999f, -0.161704f, -0.144783f,\n0.105402f, 0.019951f, -0.152874f, 0.133323f, 0.345747f, -0.062972f, 1.032106f, 1.081318f, 0.033715f, 0.097383f, -1.419532f, 0.153560f, 0.040085f, 0.060155f, 0.138139f, 0.472676f, -0.010583f, -0.082761f, -0.140231f, 0.118974f, 0.112450f, -0.011588f,\n-0.037010f, 0.475323f, 0.335745f, -0.351206f, -0.004709f, 0.001174f, 0.144682f, 0.338070f, -0.365507f, -0.028610f, 0.122161f, 1.050343f, 0.216280f, 0.043021f, 0.108013f, -0.007855f, 0.305387f, -1.384305f, -0.156121f, 0.833922f, -0.022477f,\n-0.033630f, -0.039212f, 0.094000f, 0.060896f, 0.318630f, -0.115449f, -0.173482f, -0.022484f, -0.180366f, 0.305609f, 0.132614f, 0.098328f, 0.084907f, 0.024071f, -0.155560f, 0.142120f, 0.479620f, 0.676648f, -0.169870f, 0.051266f, 0.116445f, -0.259951f,\n-0.380399f, -0.121704f, 0.069830f, -0.074126f, 0.002144f, -0.074075f, 0.323656f, -0.003989f, -0.094984f, -0.107182f, -0.154841f, -0.002667f, 0.161604f, -0.137317f, -0.034886f, 0.147448f, 0.060918f, -0.087565f, -0.078740f, 0.150724f, 0.102087f,\n0.336367f, 0.469975f, -0.012850f, -0.050844f, -0.582533f, 0.034995f, -0.139364f, 0.005695f, 0.017730f, 0.832122f, 0.061653f, 0.021723f, -0.163930f, 0.008146f, -0.098311f, -0.156676f, -0.140179f, 0.507875f, 0.272731f, 0.437376f, 0.093243f, -0.224907f,\n0.129224f, -0.123540f, -0.150268f, -0.130977f, -0.056282f, 0.408000f, -0.288290f, 0.027900f, 0.136323f, 0.009400f, 0.664199f, -0.601215f, -0.045094f, 0.508683f, -0.095720f, -0.285707f, -0.040187f, -0.121795f, -0.063571f, -0.654845f, -0.063853f,\n0.949577f, -0.065990f, -0.097805f, -0.676039f, 0.135821f, 0.106918f, -0.017009f, -0.030222f, 0.444924f, -0.122927f, -0.011496f, 0.637484f, 0.018371f, 0.060646f, -0.000786f, 0.035649f, 0.701083f, -0.011718f, 0.022927f, 0.103057f, -0.077741f,\n0.032064f, 0.193223f, -0.145424f, -0.041342f, 0.056980f, -0.231904f, 0.034221f, 0.099111f, -0.120361f, 0.009060f, -0.095277f, 0.126760f, 0.116584f, 0.091423f, 0.378517f, 0.119606f, -0.204103f, -0.480920f, -0.121380f, -0.008652f, 0.619604f, 0.128552f,\n-0.036299f, 0.100213f, -0.170747f, 0.611112f, 0.136231f, 0.006049f, 0.125290f, 0.158587f, 0.046061f, -0.080380f, 0.035659f, 0.070167f, -0.157408f, -0.115128f, -0.100179f, -0.242509f, -0.011575f, -0.512296f, 0.060034f, -0.062121f, 0.027403f,\n-0.175594f, 0.644829f, 0.078181f, 0.131089f, -0.070691f, 0.816309f, 0.649090f, -0.058158f, 0.479394f, -0.130907f, 0.119308f, 0.096011f, 0.155396f, -0.163355f, 0.534004f, -0.141007f, 1.207676f, -0.133510f, 0.152503f, 0.008651f, 0.014700f, -0.016841f,\n0.124367f, 0.072159f, -0.233837f, 0.018158f, -0.041024f, -0.470239f, -0.161419f, -0.146112f, 0.106806f, 0.203496f, -0.141640f, -0.056791f, 0.018447f, 0.154088f, 0.091871f, 0.093057f, -0.109229f, 0.146854f, -0.145408f, 0.161284f, 0.398129f,\n-0.136272f, 0.005788f, -0.036287f, 0.085244f, 0.104895f, 0.087649f, 0.118352f, 0.042260f, -0.209147f, 0.095116f, 0.046087f, 0.364237f, 0.129717f, -0.004153f, 0.316766f, 0.017142f, 0.027852f, 0.057608f, 0.071518f, 0.546824f, 0.115759f, -0.153648f,\n-0.128445f, 0.143857f, -0.054143f, 0.132693f, -0.112081f, 0.138909f, 0.029097f, -0.018568f, -0.110832f, 0.243647f, -0.134997f, 0.427819f, 0.499245f, -0.089347f, 0.019543f, 0.359801f, -0.268943f, 0.116427f, 0.134011f, -0.078892f, -0.808602f,\n1.261852f, 0.152472f, -0.326992f, 0.091400f, 0.144230f, 0.031891f, -0.123163f, -0.106844f, 0.352000f, 0.111970f, -0.704806f, 0.169124f, -0.011835f, -0.454846f, 0.084797f, 0.049378f, 0.025731f, -0.024507f, -0.102885f, -0.104327f, -0.381995f,\n-1.029548f, 0.153017f, -0.065676f, 0.000066f, -0.100380f, -0.541719f, 0.024006f, -0.070503f, -0.109737f, -0.087636f, 0.147765f, -0.462096f, 0.078314f, -1.231288f, 0.033036f, 0.584704f, -0.068160f, -0.111572f, -0.091590f, 0.053051f, 0.067232f,\n-0.156727f, -0.157672f, -0.150798f, -0.325468f, 0.147575f, 0.735800f, 0.379152f, -0.075730f, 0.126438f, 0.188331f, 0.014552f, 0.029809f, 0.092790f, 0.153566f, 0.473318f, -0.035936f, 0.104692f, -0.036492f, -0.169345f, -0.027197f, -0.154329f,\n-0.170053f, 1.131391f, 0.118253f, 0.925747f, 0.095057f, 0.657558f, 0.086488f, 1.056596f, 0.907055f, 0.069264f, 0.094246f, 0.800644f, 0.495664f, -0.133719f, -0.059796f, -0.096575f, -1.197458f, 0.600013f, -0.129930f, -0.172631f, 0.057742f, 0.976803f,\n-0.015637f, 0.028163f, 0.138240f, -0.403912f, 0.099934f, -0.243808f, -0.067820f, 0.027929f, -0.290025f, 0.049091f, 0.151552f, 0.076013f, -0.060338f, -0.599681f, -0.059522f, 0.249947f, -1.436192f, 0.061650f, 0.141808f, 0.005266f, -0.249060f,\n-0.180008f, 0.099953f, 0.121079f, 0.061177f, 0.091671f, -0.069615f, -0.648891f, 0.003018f, -1.372934f, 0.104476f, 0.830376f, -0.020940f, -0.119086f, 0.094252f, 0.091907f, 0.127261f, -0.084227f, 0.121700f, 0.105941f, 0.689311f, -0.127645f, 0.297644f,\n0.674022f, -0.010804f, 0.018844f, 0.622724f, 0.031853f, -0.150000f, -0.061023f, -0.039109f, 0.158891f, -0.157658f, 0.072265f, -0.114194f, 0.013300f, 0.154036f, 0.066056f, 0.040903f, 0.009069f, 0.362445f, 0.189216f, -0.169853f, 0.546393f, -0.097343f,\n0.554059f, 0.766670f, -0.163291f, -0.104949f, 0.377191f, -0.296908f, -0.115152f, -0.129860f, 0.123529f, -1.312135f, -0.276530f, 0.010188f, -0.842083f, -0.013376f, 0.026514f, -0.145280f, -0.072877f, -0.151993f, 0.132542f, 0.032538f, -0.423031f,\n0.032959f, 0.051750f, 0.338737f, 0.165872f, 0.084056f, -0.006527f, -0.094010f, -0.925022f, 0.114853f, 0.089660f, -0.803425f, -0.031357f, -0.095357f, 0.124569f, 0.172098f, 0.193991f, -0.042852f, -0.175967f, -0.012947f, -0.090376f, -0.082479f,\n-0.717904f, -0.167400f, -0.445774f, -0.036551f, -0.146599f, 0.116206f, -0.121552f, -0.140909f, -0.142096f, 0.126292f, 0.021772f, -0.161548f, -0.150161f, 0.051227f, 0.090706f, 0.156793f, 0.247803f, 0.098506f, -0.135964f, -1.138149f, -0.058096f,\n-0.033956f, -0.164176f, 0.113706f, -0.050515f, -0.103910f, 0.060390f, -0.055084f, -0.049944f, -0.045347f, -0.069542f, -0.038994f, 0.163713f, -1.146572f, -1.345314f, 0.011289f, 0.190193f, 0.032347f, 0.042790f, 0.455528f, -0.164567f, 0.053245f,\n-0.091698f, -0.513336f, -0.154311f, -0.021528f, 0.161033f, -0.665585f, -0.943387f, -0.110902f, -0.304630f, -0.151534f, -0.052023f, 0.142912f, 0.135456f, -0.126437f, -0.154416f, -0.117609f, -0.136423f, -0.070338f, -0.112989f, 0.253881f, -0.098487f,\n0.079100f, 0.148948f, -0.070700f, -0.787475f, -0.009155f, 0.576893f, 0.319355f, 0.114455f, 0.020267f, 0.148397f, -0.501623f, -0.303502f, 0.146236f, -0.094697f, 0.140387f, 0.055665f, -0.101822f, -0.744600f, -0.160725f, 0.977430f, -0.094086f,\n0.638944f, -0.033126f, -0.130925f, -0.001121f, 0.044496f, 0.154368f, -0.116663f, -0.125494f, -0.165370f, -0.272789f, 0.067460f, 1.196550f, 0.901977f, 0.107766f, -0.040119f, -1.212971f, 0.005140f, 0.042255f, 0.046252f, 0.076116f, -1.306030f,\n-0.087567f, -0.038116f, -0.130017f, -0.099480f, 0.029232f, 0.112940f, 0.061075f, 0.423221f, -1.046967f, -0.702104f, -0.126774f, 0.394862f, -0.050649f, 0.830139f, 0.525805f, 0.140768f, 0.123363f, 0.593220f, -0.082597f, -0.107241f, 0.048900f,\n0.076565f, -0.652974f, -1.875218f, -0.076923f, -0.775025f, 0.162391f, 0.085145f, 0.012666f, 0.033062f, -0.047484f, 0.379830f, 0.038251f, 0.287553f, -0.141017f, 0.075021f, 0.176578f, -0.063737f, -0.089401f, 0.007238f, -0.068668f, -0.865201f,\n-0.142765f, 0.602883f, 0.171719f, -0.049089f, 0.124551f, 0.144650f, -0.751264f, -0.248810f, 0.085490f, 0.108806f, -0.042655f, 0.005077f, -0.168151f, 0.570720f, 0.089359f, 0.613623f, -0.016334f, 0.499676f, -0.094577f, -0.149998f, -0.048443f,\n0.088795f, -0.000708f, -0.119181f, 0.139801f, -0.041889f, 0.043468f, -0.070078f, 1.003061f, 0.873155f, 0.099738f, 0.105601f, -1.132320f, 0.049926f, -0.074530f, 0.096306f, -0.052459f, -0.039956f, 0.063639f, 0.000184f, 0.062780f, 0.111186f, -0.120848f,\n-0.077234f, 0.100502f, 0.541622f, 0.467152f, -0.250160f, -0.090284f, 0.114069f, -0.157014f, 0.526573f, -0.182008f, 0.070539f, 0.105714f, 0.795936f, 0.218616f, -0.156281f, 0.106350f, -0.106831f, 0.394715f, -1.330911f, -0.119674f, 0.225941f, 0.076758f,\n-0.061896f, -0.082209f, 0.106025f, -0.136929f, -0.125558f, 0.039252f, 0.069194f, -0.107150f, 0.133285f, 0.260369f, -0.019341f, -0.129586f, -0.053664f, 0.069220f, -0.048124f, -0.118719f, 0.380681f, 0.801596f, -0.085064f, 0.029295f, 0.045396f,\n-0.071687f, -0.187272f, -0.037800f, -0.075015f, -0.075406f, -0.004403f, 0.102059f, 0.204374f, -0.129704f, 0.002252f, 0.119321f, -0.089763f, -0.072642f, 0.106564f, 0.073064f, -0.152183f, -0.058039f, -0.051670f, 0.002712f, 0.118476f, -0.000513f,\n-0.042379f, 0.297192f, 0.348372f, 0.055753f, -0.026289f, -0.715333f, -0.080056f, -0.088239f, -0.152999f, 0.102876f, 0.437304f, -0.002311f, -0.038907f, -0.035037f, -0.026212f, -0.106516f, -0.079805f, 0.106403f, 0.356527f, 0.266184f, 0.535079f,\n-0.160814f, -0.304608f, 0.029663f, -0.050449f, -0.049111f, -0.101536f, 0.073437f, 0.453820f, -0.386725f, 0.052741f, -0.005307f, 0.065307f, 0.799821f, -0.747873f, -0.095829f, 0.423566f, 0.030315f, -0.252735f, -0.144393f, 0.006571f, -0.074951f,\n-0.469632f, -0.022004f, 0.930453f, 0.034161f, -0.136654f, -0.497042f, -0.088905f, -0.120453f, -0.007464f, -0.074786f, 0.398213f, -0.022579f, -0.142004f, 0.762197f, -0.084382f, 0.094501f, -0.002796f, -0.003517f, 0.552675f, -0.121365f, 0.143934f,\n-0.154106f, 0.087506f, 0.014462f, 0.173336f, -0.144766f, -0.065547f, 0.044379f, -0.205085f, 0.013751f, 0.095907f, -0.059589f, -0.030944f, -0.033827f, -0.048313f, 0.003456f, -0.117967f, 0.326293f, -0.087033f, -0.507016f, -0.684222f, 0.092441f,\n0.060252f, 0.681711f, 0.133213f, 0.059489f, 0.052368f, 0.108725f, 0.624699f, -0.078888f, 0.112877f, 0.110267f, 0.003640f, 0.095201f, -0.019785f, -0.178198f, 0.183849f, -0.064660f, -0.080784f, 0.016977f, -0.178209f, -0.058398f, -0.257492f, 0.093237f,\n-0.175302f, -0.078319f, -0.188981f, 0.710471f, -0.059848f, -0.019725f, -0.104101f, 0.873805f, 0.565527f, -0.044339f, 0.385258f, 0.148508f, 0.014780f, 0.125069f, -0.141021f, 0.066326f, 0.433650f, 0.079149f, 1.020791f, -0.008993f, -0.044372f,\n-0.196637f, 0.113566f, -0.094936f, 0.000068f, 0.147711f, 0.115380f, -0.133800f, -0.155600f, -0.110914f, -0.153374f, 0.050979f, -0.119439f, -0.136429f, -0.273684f, 0.117139f, -0.058844f, -0.112003f, 0.142885f, 0.031513f, -0.151198f, 0.157971f,\n-1.015328f, -0.079795f, 0.428261f, -0.061623f, -0.063517f, -0.110709f, -0.138490f, 0.131632f, 0.107261f, 0.144171f, -0.066145f, -0.342131f, -0.127605f, 0.208410f, 0.242732f, 0.070865f, -0.122255f, 0.326426f, 0.140768f, -0.117421f, -0.010544f,\n-0.063075f, 0.290757f, 0.027553f, -0.155593f, 0.074159f, -0.083776f, 0.066917f, 0.060528f, -0.009322f, -0.077643f, 0.113196f, 0.166159f, 0.014946f, 0.432606f, -0.122770f, 0.729919f, 0.210397f, -0.105430f, 0.134925f, 0.582693f, 0.058112f, -0.014994f,\n0.081027f, 0.128574f, -0.675617f, 1.042610f, 0.111655f, 0.048341f, -0.123912f, 0.583560f, -0.146761f, -0.104798f, 0.084977f, 0.526634f, 0.112701f, -0.339785f, -0.100034f, 0.047384f, -0.498704f, 0.164893f, -0.013796f, 0.070422f, -0.033574f, 0.092338f,\n0.068489f, -0.347135f, -1.074822f, 0.030497f, -0.015467f, -0.055068f, -0.168983f, -1.239390f, 0.136956f, 0.055706f, 0.045822f, 0.105200f, 0.081010f, -0.575963f, 0.038062f, -1.583641f, 0.162823f, 0.858886f, 0.093858f, -0.036360f, -0.038735f,\n0.162687f, 0.085307f, 0.028925f, -0.017822f, 0.038520f, -0.507167f, -0.171931f, 0.665389f, 0.442927f, -0.167725f, 0.111968f, 0.232935f, 0.002940f, -0.033500f, -0.107055f, -0.055132f, 0.394489f, 0.082776f, 0.031860f, 0.067014f, 0.132411f, 0.083926f,\n0.127428f, 0.054883f, 0.703752f, -0.025893f, 1.129801f, 0.082209f, 0.903472f, 0.037342f, 1.398725f, 0.259324f, 0.104530f, 0.104483f, 1.324027f, 0.206345f, -0.084919f, 0.026520f, -0.151204f, -1.150003f, 0.626784f, 0.065628f, 0.223216f, 0.019766f,\n1.124189f, 0.114960f, -0.058899f, -0.131041f, 0.221180f, -0.103464f, -0.300908f, 0.081552f, 0.043346f, -0.501931f, 0.062042f, -0.124651f, -0.107992f, -0.128660f, -0.010868f, 0.130826f, 0.064255f, -1.244852f, -0.136502f, -0.158757f, 0.141481f,\n0.128590f, -0.373488f, -0.054764f, -0.049091f, -0.052210f, -0.011459f, 0.078398f, -0.431814f, -0.083063f, -1.404103f, -0.020378f, 1.263504f, -0.037733f, -0.100149f, 0.043720f, 0.125940f, 0.090776f, 0.113031f, 0.047088f, -0.013189f, 0.445977f,\n0.082596f, 0.338067f, 0.514384f, 0.054925f, -0.104983f, 0.062725f, -0.066634f, 0.149602f, 0.056248f, 0.079014f, 0.703022f, -0.061372f, -0.003142f, 0.168803f, 0.081437f, -0.067171f, -0.052023f, 0.082080f, -0.667436f, 0.013611f, -0.114300f, 0.036357f,\n0.648874f, -0.152779f, 0.808233f, -0.330929f, 0.135232f, -0.174028f, 0.950101f, -0.591130f, -0.027806f, 0.094892f, 0.124563f, -0.701296f, -0.341931f, -0.122424f, -0.774041f, 0.047290f, 0.521651f, -0.010499f, -0.132569f, -0.090770f, 0.635599f,\n0.047941f, -0.540636f, -0.107287f, 0.059570f, 0.089198f, -0.152456f, 0.034123f, 0.037831f, -0.118772f, -0.357655f, -0.187020f, 0.015909f, -1.053277f, 0.094221f, -0.057127f, 0.024379f, 0.314574f, -0.282637f, -0.079391f, 0.024314f, -0.016502f,\n0.097302f, 0.069451f, -0.542035f, -0.099363f, -0.306470f, 0.136945f, 0.316848f, -0.127522f, -0.157773f, 0.164513f, -0.044886f, 0.012131f, -0.030888f, 0.000299f, 0.027561f, 0.355234f, -0.027381f, 0.014521f, 0.313114f, -0.164978f, -0.149110f,\n-1.085781f, 0.030476f, -0.131637f, 0.007116f, -0.122763f, 0.209856f, 0.023283f, 0.085138f, -0.045496f, -0.143899f, 0.123488f, -0.137580f, 0.154251f, -0.215621f, -1.226251f, -1.665151f, 0.006801f, 0.548838f, 0.077012f, 0.421325f, -0.256271f,\n-0.181577f, -0.135278f, 0.476988f, -0.792620f, 0.053134f, -0.041860f, -0.108247f, -0.295507f, -0.962555f, -0.077723f, -0.254883f, 0.136141f, 0.241391f, 0.123993f, 0.015912f, -0.095495f, 0.639009f, -0.007518f, -0.608571f, 0.164025f, 0.094168f,\n-0.082771f, 0.091529f, -0.093474f, -0.019347f, 0.071495f, -0.582165f, -0.129946f, -0.297226f, 0.082153f, 0.068050f, -0.057522f, -0.065316f, 0.172561f, -0.955187f, -0.146684f, 0.154835f, 0.019882f, -0.059681f, 0.058030f, -0.047140f, -0.003975f,\n0.964772f, -0.026559f, 0.979889f, 0.007878f, -0.120748f, 0.128818f, 0.113369f, 0.010349f, 0.043619f, 0.128439f, 0.107445f, -0.109902f, 0.113289f, 0.553926f, 0.375162f, 0.062801f, 0.141474f, -0.984734f, -0.149911f, 0.114726f, -0.164138f, -0.033300f,\n-0.782799f, -0.067244f, 0.011811f, 0.126291f, -0.058033f, 0.005761f, 0.033242f, 0.142800f, 0.125785f, -0.466165f, -0.496217f, -0.004684f, 0.873674f, 0.134177f, 1.073702f, 0.210086f, 0.073612f, 0.040043f, 0.698108f, -0.063391f, 0.166843f, 0.060208f,\n-0.035568f, -0.400388f, -1.516489f, 0.150969f, -0.256116f, -0.133494f, 0.648871f, 0.155885f, -0.075944f, 0.148534f, 0.500113f, 0.042360f, -0.293053f, 0.020017f, 0.142282f, 0.280126f, -0.021114f, 0.145019f, 0.000981f, -0.066240f, -0.730851f,\n-0.113077f, 0.200382f, 0.167347f, -0.128493f, 0.133957f, -0.083296f, -0.237406f, -0.424140f, 0.083820f, -0.045775f, -0.094494f, -0.153449f, 0.054500f, 0.914426f, 0.099166f, 0.471526f, 0.099515f, 0.488254f, -0.018319f, 0.103984f, -0.012896f,\n0.077461f, -0.086427f, 0.089122f, 0.012726f, 0.150197f, -0.293732f, 0.004904f, 0.453368f, 0.503680f, -0.162480f, -0.047592f, -0.650226f, 0.030065f, -0.011435f, 0.019790f, -0.101333f, -0.083388f, -0.015702f, -0.183058f, -0.080946f, 0.018151f,\n-0.166724f, -0.092517f, -0.012456f, 0.606439f, -0.037879f, -0.365460f, -0.023365f, 0.547995f, 0.003182f, 0.736309f, -0.275974f, -0.135300f, -0.025510f, 0.738717f, 0.364665f, -0.053382f, -0.062788f, 0.005792f, 0.750454f, -1.191229f, 0.066546f,\n-0.338152f, 0.001397f, 0.273330f, -0.064439f, -0.004605f, -0.033483f, -0.277760f, 0.017293f, 0.145266f, 0.120511f, 0.043378f, 0.302812f, -0.141092f, 0.089913f, -0.103668f, -0.124408f, 0.092828f, -0.111419f, 0.284935f, 0.777528f, 0.082572f,\n-0.173597f, -0.095917f, -0.082095f, -0.013862f, -0.111860f, -0.055551f, 0.158407f, -0.145236f, -0.157436f, 0.386793f, 0.103070f, -0.231139f, -0.168268f, -0.269176f, -0.059768f, -0.102641f, -0.035912f, -0.031374f, -0.091054f, -0.143258f, 0.031470f,\n-0.005794f, 0.058610f, -0.056516f, 0.319286f, 0.245314f, 0.051506f, -0.027735f, -0.297291f, -0.030904f, 0.015580f, 0.020144f, -0.141713f, 0.404921f, -0.056295f, -0.062083f, -0.132248f, 0.059021f, -0.166871f, -0.028624f, -0.159027f, 0.458301f,\n-0.037682f, 0.774613f, -0.035106f, -0.048988f, -0.001941f, 0.084208f, -0.066922f, 0.149032f, -0.186633f, 0.457010f, -0.424850f, -0.140581f, -0.082394f, 0.084402f, 0.691102f, -0.438439f, -0.013167f, 0.034156f, -0.087530f, 0.018461f, -0.106167f,\n0.081968f, -0.094066f, -0.497568f, 0.155146f, 0.963354f, -0.166428f, 0.145733f, -0.586077f, 0.122485f, -0.117927f, 0.111242f, 0.081273f, 0.570599f, -0.152938f, -0.139608f, 0.635831f, -0.074078f, -0.056857f, -0.049907f, 0.024969f, 0.523686f,\n-0.148860f, -0.055249f, -0.025289f, -0.046941f, -0.120887f, 0.218822f, -0.177601f, -0.289373f, -0.038906f, -0.104464f, 0.108251f, 0.057856f, -0.155530f, -0.093336f, -0.046294f, 0.134439f, 0.138868f, -0.083370f, 0.410103f, -0.029167f, -0.260765f,\n-0.422833f, 0.058422f, -0.054492f, 0.724910f, 0.123367f, -0.044707f, 0.088564f, 0.040655f, 0.603333f, 0.081420f, 0.114331f, -0.018147f, 0.153911f, -0.189941f, -0.005836f, 0.156859f, 0.098322f, 0.105425f, -0.367766f, 0.024429f, -0.282926f, -0.095546f,\n-0.429239f, 0.163595f, -0.088735f, 0.025098f, -0.113789f, 0.828989f, -0.075044f, -0.009705f, -0.070054f, 0.748750f, 0.563548f, -0.104371f, 0.491981f, -0.044545f, 0.103788f, -0.006407f, -0.169041f, -0.108720f, 0.422388f, 0.042709f, 1.238993f,\n0.009856f, 0.157384f, -0.115167f, 0.005196f, -0.167239f, 0.022369f, -0.058275f, 0.359986f, -0.082533f, -0.210996f, -0.244417f, -0.003844f, 0.035512f, 0.035028f, 0.307271f, -0.304772f, -0.047928f, -0.067690f, -0.056821f, -0.007554f, 0.033374f,\n-0.082516f, -0.076232f, -0.902630f, -0.055891f, 0.699942f, 0.075891f, 0.055826f, 0.020599f, 0.058814f, 0.124855f, -0.146338f, 0.111688f, 0.077663f, -0.340896f, -0.159590f, 0.212051f, 0.051590f, -0.145353f, 0.084925f, 0.288576f, 0.084446f, 0.033824f,\n0.115827f, 0.111872f, 0.108727f, -0.098809f, 0.013801f, 0.019572f, 0.003931f, -0.144373f, 0.120097f, -0.105394f, -0.203359f, 0.510475f, 0.203746f, 0.141646f, 0.617790f, 0.010859f, 0.624316f, 0.357430f, 0.003250f, 0.058959f, 0.563359f, -0.130503f,\n0.140491f, 0.027724f, 0.099072f, -0.553174f, 1.265031f, -0.005951f, -0.141115f, -0.151414f, 0.414316f, 0.159984f, -0.095295f, -0.097729f, 0.659221f, 0.044359f, -0.235732f, -0.041785f, 0.119560f, -0.368123f, -0.124479f, -0.011853f, 0.014357f,\n0.101451f, 0.639167f, -0.082741f, -0.099354f, -1.080168f, -0.016592f, -0.097228f, 0.079944f, 0.203687f, -0.693503f, -0.135491f, 0.108403f, -0.073537f, -0.105905f, -0.000297f, -0.568150f, 0.083106f, -1.286940f, 0.012215f, 0.553718f, -0.125298f,\n-0.023003f, 0.000889f, -0.056619f, 0.025880f, -0.059107f, 0.126058f, 0.094769f, -0.746561f, 0.160513f, 0.432048f, 0.348104f, -0.164541f, -0.009065f, 0.418727f, -0.119663f, 0.043272f, 0.044281f, 0.104365f, 0.004651f, 0.058961f, 0.131843f, -0.025995f,\n-0.129749f, 0.134091f, -0.075487f, 0.028778f, -0.582490f, 0.433643f, 0.770308f, -0.053347f, 0.846912f, -0.108983f, 1.138830f, 0.403516f, 0.084846f, -0.036056f, 1.216748f, -0.345484f, -0.091067f, 0.006578f, 0.070340f, -0.680648f, 0.265343f, 0.078475f,\n-0.120072f, -0.006074f, 0.955769f, 0.067925f, 0.149966f, 0.092404f, -0.046942f, -0.090264f, -0.559988f, -0.077719f, 0.127541f, 0.227744f, -0.146792f, 0.115837f, -0.134698f, -0.021801f, 0.227532f, 0.110100f, -0.003879f, -0.790971f, 0.009796f,\n0.152565f, -0.125441f, 0.517045f, 0.101952f, -0.167265f, -0.089819f, -0.152276f, -0.011514f, -0.125980f, -0.174951f, -0.163096f, -1.347616f, 0.103676f, 1.153744f, -0.066117f, -0.167165f, 0.075386f, -0.121286f, -0.050524f, -0.098394f, 0.100089f,\n0.010543f, -0.242661f, 0.061353f, 0.387369f, 0.209379f, -0.148949f, 0.041858f, -0.350776f, 0.045307f, -0.051456f, -0.046776f, -0.048395f, 0.557576f, -0.026445f, 0.112428f, -0.137971f, 0.101467f, -0.111216f, 0.066374f, 0.036936f, -1.129874f,\n0.045428f, -0.460758f, 0.087035f, 0.424396f, -0.028694f, 0.624692f, 0.386915f, -0.180846f, 0.121413f, 0.699049f, -0.824376f, -0.061444f, -0.023953f, -0.100530f, -0.683693f, -0.269016f, -0.013722f, -0.795760f, -0.075572f, 0.351393f, -0.137673f,\n0.071118f, 0.152009f, 0.678274f, -0.140785f, -1.269188f, 0.062711f, 0.033270f, 0.231581f, 0.001913f, -0.069874f, -0.127445f, -0.135411f, 0.077910f, -0.097078f, 0.075001f, -0.506387f, -0.033305f, 0.108984f, -0.007457f, 0.383114f, -0.075277f,\n-0.040324f, -0.121700f, -0.069396f, 0.152048f, -0.094838f, -0.408979f, -0.091818f, 0.834354f, -0.121602f, 0.424214f, -0.032938f, -0.130035f, -0.130661f, -0.132371f, 0.167949f, -0.161699f, -0.011612f, -0.119321f, 0.347186f, -0.100025f, -0.346729f,\n0.211596f, 0.141258f, 0.017085f, -1.100040f, 0.016666f, -0.113267f, -0.102386f, -0.038793f, -0.076640f, -0.002756f, 0.099427f, -0.108034f, -0.095493f, -0.015898f, 0.112693f, 0.077652f, -1.027529f, -0.198828f, -1.442992f, 0.075981f, 0.498286f,\n0.120109f, 0.375672f, 0.051303f, -0.181175f, 0.048703f, 0.131543f, -1.124085f, -0.068498f, -0.040411f, -0.055564f, 0.203809f, -0.877017f, 0.138734f, -0.954828f, -0.036616f, 0.046601f, -0.101217f, -0.084753f, 0.072928f, 0.195558f, 0.032271f,\n-0.758255f, -0.079518f, -0.169309f, -0.086683f, -0.154177f, -0.114097f, -0.067783f, -0.069457f, 0.152477f, -0.151776f, -0.710416f, -0.412840f, 0.048965f, 0.047575f, -0.165416f, 0.388504f, -0.873422f, 0.039603f, -0.168585f, -0.082334f, 0.113030f,\n-0.117885f, 0.641768f, 0.150238f, 1.269805f, 0.072800f, 0.987362f, 0.048619f, 0.029853f, -0.077486f, 0.032889f, -0.106001f, -0.153344f, -0.175212f, -0.015660f, -0.121438f, 0.029014f, 0.085985f, 0.023979f, -0.084902f, -0.089750f, -0.640527f,\n-0.024815f, -0.069226f, 0.155165f, 0.147256f, -0.486724f, -0.118128f, -0.144462f, 0.075196f, 0.010491f, 0.107423f, -0.016980f, 0.120873f, 0.050560f, 0.662611f, -0.532724f, 0.076949f, 0.924144f, -0.042902f, 1.031289f, 0.259911f, 0.034246f, -0.000363f,\n0.498841f, -0.150296f, 0.067906f, -0.153867f, 0.070825f, -0.030815f, -0.959112f, -0.025053f, -0.168382f, 0.115573f, 0.712286f, 0.162181f, 0.010255f, -0.110786f, 0.177012f, 0.141494f, -0.781290f, 0.137365f, 0.004063f, 0.407400f, -0.085567f, 0.025274f,\n-0.060532f, 0.030547f, -0.286027f, 0.152661f, -0.368638f, -0.414321f, 0.111456f, 0.021764f, -0.129845f, 0.286048f, -0.426075f, 0.079421f, 0.133402f, 0.053844f, 0.060228f, -0.176347f, 1.359113f, 0.027344f, 0.246136f, -0.063611f, 0.287365f, -0.084184f,\n-0.086979f, 0.010896f, 0.091290f, -0.072136f, -0.088912f, 0.092590f, -0.083075f, -0.265170f, -0.104491f, -0.112381f, 0.072823f, -0.140891f, -0.023023f, -0.432164f, -0.027057f, 0.154133f, -0.182034f, -0.104143f, -0.315702f, 0.124539f, -0.128349f,\n0.129386f, -0.075336f, -0.084343f, -0.159903f, -0.039291f, 0.547526f, -0.011563f, 0.123601f, 0.011113f, 0.829404f, -0.101736f, 0.711372f, -0.005135f, 0.147661f, 0.066567f, 0.358273f, -0.228828f, -0.053896f, 0.096717f, 0.128006f, 0.506377f,\n-0.994292f, -0.083390f, -0.590135f, -0.015689f, 0.355918f, 0.124008f, -0.169374f, -0.045013f, -0.311617f, 0.121115f, -0.534696f, 0.120060f, -0.079182f, 0.070511f, 0.167248f, -0.157683f, -0.051808f, 0.136500f, 0.237153f, -0.175056f, 0.022245f,\n0.532685f, 0.054859f, -0.050164f, 0.020976f, 0.242175f, 0.043895f, -0.056359f, -0.152465f, 0.156632f, -0.032992f, 0.047313f, 0.248051f, 0.053041f, -0.402046f, 0.091239f, -0.076131f, 0.114325f, -0.066177f, 0.041666f, 0.043238f, -0.042991f, 0.048374f,\n0.005138f, 0.024151f, 0.015298f, 0.046189f, -0.141974f, 0.067048f, 0.082749f, 0.160773f, 0.003230f, 0.124224f, -0.130570f, 0.019205f, -0.141954f, 0.500738f, 0.122315f, -0.138312f, -0.005157f, -0.091258f, -0.155164f, 0.159859f, -0.083857f, 0.334343f,\n-0.180851f, 0.784576f, 0.015302f, 0.011243f, -0.152642f, -0.071439f, -0.065428f, 0.110945f, -0.041262f, 0.479708f, -0.554511f, -0.130789f, -0.088417f, -0.100300f, 0.574499f, -0.436967f, -0.050859f, 0.045529f, -0.030610f, 0.101100f, 0.116749f,\n-0.102464f, -0.112252f, -0.471629f, 0.121662f, 1.005214f, 0.138331f, 0.142515f, -0.667244f, -0.120830f, -0.153039f, 0.078541f, 0.131549f, 0.439320f, 0.132064f, -0.130036f, 0.698212f, 0.145121f, -0.128070f, 0.112567f, 0.201590f, 0.735015f, 0.120725f,\n0.004205f, -0.140962f, -0.074516f, -0.034401f, 0.266350f, 0.112165f, -0.052678f, -0.067440f, -0.041446f, 0.056853f, 0.053820f, 0.038589f, -0.103435f, 0.029872f, 0.150200f, 0.078832f, -0.014186f, 0.166124f, -0.113571f, -0.371527f, -0.426069f,\n0.066045f, -0.063029f, 0.651654f, 0.118152f, -0.161227f, -0.003072f, -0.154142f, 0.584433f, 0.025781f, -0.072078f, -0.164359f, -0.015068f, -0.155561f, 0.155758f, -0.017343f, 0.120593f, -0.201487f, -0.362442f, 0.039392f, -0.073560f, -0.143717f,\n-0.238110f, -0.014036f, 0.125993f, -0.077326f, -0.169848f, 0.861979f, 0.009554f, 0.046916f, 0.056183f, 0.512855f, 0.379789f, -0.141007f, 0.487001f, 0.169301f, 0.162037f, 0.113886f, 0.006953f, 0.105983f, 0.286114f, -0.062100f, 0.946683f, 0.145979f,\n-0.090476f, -0.137129f, -0.156282f, 0.030679f, 0.153459f, -0.098298f, 0.514575f, 0.125078f, -0.293786f, -0.734722f, 0.146488f, -0.111678f, 0.108544f, 0.305201f, -0.281234f, 0.035962f, 0.047833f, 0.149092f, -0.141335f, 0.151894f, -0.075254f,\n0.016176f, -0.569670f, 0.012340f, 0.804471f, -0.083721f, -0.082968f, 0.009890f, -0.156938f, 0.024664f, -0.152430f, -0.017635f, 0.065567f, -0.558625f, 0.096507f, 0.097858f, 0.158097f, -0.102788f, 0.008066f, 0.555843f, 0.057471f, -0.122876f, 0.147218f,\n-0.056804f, -0.065848f, -0.058659f, 0.042819f, 0.118758f, -0.068947f, -0.029488f, 0.124798f, -0.146038f, -0.234652f, 0.602878f, 0.189922f, 0.064050f, 0.740496f, 0.136672f, 0.880399f, 0.143826f, 0.127866f, -0.152547f, 0.659017f, -0.227031f, 0.138171f,\n-0.125507f, -0.083945f, -0.436458f, 0.961312f, 0.018250f, -0.504826f, 0.138527f, 0.693786f, 0.096050f, -0.145180f, 0.086653f, 0.425848f, 0.014649f, -0.466960f, 0.065116f, -0.022228f, -0.516228f, 0.148750f, -0.019741f, 0.092419f, -0.136020f,\n0.670367f, 0.055159f, -0.173350f, -1.418678f, 0.056001f, -0.164842f, -0.054848f, 0.255409f, -0.758140f, 0.053175f, -0.100975f, -0.092815f, 0.127477f, 0.144487f, -0.609888f, -0.062894f, -1.183960f, -0.010364f, 0.732384f, 0.110499f, -0.018078f,\n0.075058f, 0.102893f, -0.109549f, 0.149310f, -0.008722f, 0.057308f, -0.670828f, -0.049883f, 0.294664f, 0.264846f, -0.103113f, -0.017066f, 0.277549f, -0.114169f, -0.070720f, -0.174436f, -0.175790f, 0.341564f, -0.001323f, 0.053644f, -0.083388f,\n-0.146370f, -0.080028f, -0.147028f, 0.093304f, -1.336226f, 0.493848f, 0.346542f, -0.049190f, 0.727714f, 0.092376f, 1.010022f, 0.241272f, 0.109974f, 0.013172f, 1.424602f, -0.608021f, -0.025242f, -0.105112f, 0.155682f, -0.740239f, -0.031366f,\n-0.047246f, -0.599972f, -0.065844f, 0.905878f, 0.112926f, 0.030349f, -0.032724f, -0.152811f, 0.040934f, -0.531832f, 0.146704f, -0.160308f, 0.231187f, 0.094435f, -0.169406f, 0.099930f, -0.144742f, 0.796687f, -0.185121f, -0.300098f, -0.662291f,\n0.052942f, -0.009473f, -0.011408f, 1.020446f, -0.019520f, -0.175020f, -0.134148f, 0.168386f, -0.046994f, -0.052599f, 0.419357f, 0.104707f, -1.195408f, 0.045294f, 1.135858f, -0.136344f, -0.117422f, -0.106885f, -0.136458f, -0.099149f, 0.158408f,\n0.015409f, -0.114867f, -0.757035f, -0.140123f, -0.095231f, -0.155179f, -0.143510f, 0.133250f, -0.213847f, -0.160165f, -0.123131f, 0.148416f, 0.105035f, 0.383594f, -0.028849f, 0.155700f, 0.012821f, 0.137411f, -0.050257f, -0.030650f, -0.070623f,\n-1.474296f, -0.345306f, -0.721061f, 0.103753f, 0.970228f, 0.028449f, 0.711557f, 0.692283f, 0.003483f, 0.055914f, 0.392868f, -1.224574f, 0.011268f, 0.044006f, 0.071382f, -0.369364f, -0.246272f, -0.149909f, -1.303559f, 0.153859f, 0.726808f, -0.061510f,\n-0.095539f, 0.120890f, 0.732920f, 0.027392f, -1.261840f, -0.053689f, -0.022333f, -0.016926f, 0.080629f, 0.028580f, 0.026488f, -0.100005f, 0.796450f, 0.134507f, -0.517161f, -0.241933f, 0.025445f, -0.039245f, -0.004659f, 0.873108f, -0.169297f,\n0.115628f, -0.054453f, -0.103732f, 0.063329f, -0.173383f, 0.183028f, -0.029046f, 1.576831f, -0.126832f, 0.959285f, -0.082081f, 0.032954f, 0.125347f, 0.066717f, -0.043916f, -0.095270f, 0.133607f, 0.057997f, 0.085420f, 0.098087f, -0.607875f,\n-0.394778f, 0.062452f, 0.073706f, -0.403678f, 0.059248f, -0.095642f, -0.015955f, 0.051011f, 0.531634f, -0.158452f, 0.012657f, 0.163674f, 0.074926f, -0.069755f, -0.094441f, -0.004863f, -1.443962f, 0.550686f, -0.803934f, -0.118749f, 0.671203f,\n0.060056f, 0.485300f, -0.484983f, 0.053006f, -0.158352f, 0.210419f, -0.985580f, -0.034002f, -0.171499f, -0.166943f, 0.928612f, -1.030636f, -0.122955f, -1.420182f, 0.159977f, 0.169600f, -0.043981f, 0.153919f, -0.020024f, -0.021737f, 0.097055f,\n-1.173867f, 0.044364f, -0.127292f, -0.053815f, -0.029931f, -0.023146f, 0.050977f, -0.160226f, 0.624682f, -0.103196f, -0.708827f, -0.594143f, -0.048996f, -0.093624f, 0.117635f, 0.957880f, -0.616114f, 0.051301f, 0.064486f, 0.044127f, 0.021158f,\n-0.070637f, 1.314867f, -0.080281f, 1.656339f, 0.009830f, 0.990332f, 0.071853f, -0.129791f, -0.073153f, -0.099821f, -0.059054f, -0.016416f, 0.129655f, -0.064268f, -0.441510f, -0.001860f, -0.424011f, -0.633249f, 0.110070f, -0.027917f, 0.033964f,\n-0.047049f, -0.052974f, -0.038024f, 0.034411f, -0.144087f, 0.133193f, -0.123613f, -0.071774f, -0.121366f, 0.096873f, 0.156895f, 0.133813f, 0.404273f, 0.883057f, 0.016315f, -0.154726f, 1.195987f, -0.044780f, 0.914835f, 0.118598f, -0.017004f,\n0.076278f, 0.252441f, -0.792920f, -0.163525f, 0.144118f, 0.090490f, 0.525089f, -0.418902f, -0.019694f, -0.853554f, -0.028332f, 0.379706f, -0.131042f, 0.087815f, 0.134733f, 0.138432f, 0.086011f, -1.277378f, -0.110056f, -0.100609f, 0.694523f,\n0.097807f, -0.069166f, 0.011760f, 0.073998f, 0.445459f, 0.014876f, -0.749050f, -0.510506f, 0.113031f, 0.011652f, -0.008054f, 1.061236f, -0.462657f, -0.033752f, -0.006726f, -0.132767f, -0.106330f, 0.139872f, 1.370125f, -0.094420f, 0.406615f,\n0.135535f, 0.241444f, -0.158058f, -0.021611f, 0.028679f, -0.065297f, -0.152451f, -0.112899f, -0.025390f, 0.114575f, -0.566036f, 0.099669f, -0.432246f, -0.596419f, -0.089080f, 0.108638f, 0.398292f, 0.108102f, -0.014745f, 0.073458f, -0.075320f,\n-0.579374f, 0.158428f, 0.037604f, 0.005383f, -0.104451f, -0.051963f, -0.072395f, -0.036367f, 0.362729f, -0.456875f, 0.875168f, -0.167593f, 0.972587f, 0.036013f, 0.539497f, -0.275154f, 0.148295f, -0.104954f, 0.036071f, -0.912451f, -0.077012f,\n-0.138645f, -0.072705f, 0.535086f, -0.463427f, -0.130045f, -1.352722f, -0.043955f, 0.067781f, -0.088995f, -0.042856f, -0.088875f, -0.601971f, -0.067329f, -0.712267f, -0.124864f, -0.058411f, -0.006867f, -0.158759f, 0.135931f, -0.099823f, 0.130507f,\n0.465234f, 0.018090f, -0.104923f, 0.663228f, 0.139246f, 0.007005f, -0.151036f, 0.468760f, -0.337369f, -0.137977f, -0.123703f, -0.040950f, -0.161550f, -0.122306f, 0.176824f, -0.058303f, -0.333626f, -0.143326f, -0.372730f, 0.157760f, -0.135489f,\n0.004722f, -0.141387f, 0.136956f, 0.126002f, -0.077899f, 0.040439f, -0.070951f, 0.133527f, -0.212510f, -0.053524f, 0.025234f, -0.060227f, 0.416139f, -0.000240f, -0.079732f, -0.086079f, 0.088722f, 0.355497f, 0.129133f, 0.013312f, 0.044822f,\n-0.076605f, 0.098984f, -0.049630f, 0.131083f, 0.345137f, -0.412226f, 0.838898f, -0.140613f, 0.212752f, 0.012023f, 0.072712f, -0.048527f, -0.049692f, 0.072711f, 0.346943f, -0.550921f, 0.151375f, -0.135548f, -0.040154f, 0.593099f, -0.066183f,\n0.155938f, -0.241847f, -0.067373f, 0.160346f, -0.021963f, 0.134694f, 0.088518f, -0.740722f, 0.092571f, 1.037287f, -0.148274f, 0.055742f, -0.546854f, -0.162574f, -0.114471f, 0.117332f, -0.161942f, 0.580617f, -0.143313f, -0.445692f, 0.559908f,\n-0.147754f, 0.045337f, -0.136866f, 0.282684f, 0.459756f, 0.009159f, 0.154203f, 0.133348f, 0.103867f, 0.062818f, 0.295597f, -0.050674f, -0.050574f, 0.139538f, -0.239324f, 0.120743f, -0.131671f, -0.128562f, -0.115945f, 0.064955f, -0.111201f,\n-0.049072f, -0.146016f, 0.289283f, -0.162793f, -0.199786f, -0.447922f, -0.163307f, -0.032180f, 0.883450f, -0.065049f, -0.000028f, -0.140743f, -0.097083f, 0.482801f, -0.052437f, 0.144269f, -0.101855f, -0.156679f, -0.063468f, 0.084481f, -0.041544f,\n-0.070415f, -0.189835f, -0.080021f, -0.049767f, 0.006037f, -0.090977f, -0.347735f, 0.117724f, 0.053579f, -0.150194f, -0.126657f, 0.739326f, -0.167214f, -0.080381f, -0.058220f, 0.786341f, 0.360055f, 0.017214f, 0.442090f, -0.146303f, 0.000100f,\n0.038823f, 0.051703f, 0.061513f, 0.156479f, -0.084229f, 1.051573f, -0.154266f, -0.131878f, -0.318818f, 0.062302f, -0.084746f, 0.044819f, 0.146600f, 0.409223f, 0.047598f, -0.427208f, -0.674674f, -0.103574f, -0.164501f, -0.148475f, 0.408316f,\n-0.336606f, 0.044188f, -0.160648f, -0.013032f, -0.175507f, -0.056132f, -0.100432f, 0.116733f, -0.647581f, -0.091321f, 0.339525f, -0.029633f, 0.120386f, 0.148254f, 0.163247f, -0.059549f, 0.074800f, 0.070922f, 0.023775f, -0.305175f, 0.154025f,\n0.184904f, 0.024225f, 0.085001f, 0.146612f, 0.604174f, 0.012881f, 0.025496f, 0.068730f, 0.107467f, -0.051186f, -0.033828f, -0.052823f, 0.006301f, 0.100796f, -0.036365f, 0.084247f, 0.154338f, -0.128705f, 0.387154f, -0.090050f, 0.034416f, 0.538527f,\n-0.138561f, 0.664924f, 0.401345f, 0.061527f, 0.044743f, 0.344966f, 0.023197f, -0.136131f, -0.056306f, -0.150102f, -0.704347f, 1.042653f, -0.052873f, -0.067116f, 0.126157f, 0.422810f, 0.122146f, -0.162749f, -0.117454f, 0.508942f, 0.014287f,\n-0.384542f, 0.005015f, -0.153654f, -0.138445f, 0.060229f, -0.068412f, -0.161227f, 0.054513f, 0.857915f, 0.151153f, -0.197786f, -1.362385f, -0.061760f, -0.138434f, -0.023255f, 0.058440f, -0.609029f, -0.100422f, 0.030523f, 0.014310f, 0.100100f,\n-0.183080f, -0.600612f, 0.118340f, -0.558225f, -0.098032f, 0.443222f, -0.127784f, -0.112202f, 0.059392f, 0.048023f, -0.094506f, 0.127913f, 0.024860f, -0.007159f, 0.145114f, -0.121205f, 0.240038f, 0.201140f, -0.046816f, -0.058931f, 0.549162f,\n0.046032f, -0.006580f, 0.112158f, -0.069953f, 0.129371f, -0.015575f, 0.015783f, -0.002148f, -0.088814f, -0.110639f, 0.131178f, -0.025500f, -1.473739f, 0.273445f, 0.121083f, -0.121484f, 0.684441f, -0.133291f, 0.494237f, -0.044080f, -0.073369f,\n-0.012170f, 0.945812f, -0.381762f, -0.002684f, -0.063694f, -0.098200f, -0.631417f, 0.073520f, -0.106564f, -0.285303f, 0.054935f, 0.556626f, -0.055767f, 0.035238f, -0.167873f, -0.626566f, -0.104438f, -0.781355f, 0.082869f, 0.046688f, 0.524150f,\n0.037464f, 0.035748f, -0.119468f, -0.037762f, 0.840876f, 0.004626f, 0.180431f, -0.637223f, 0.066718f, -0.109114f, -0.082141f, 0.220935f, -0.326800f, -0.017726f, -0.093911f, 0.099629f, 0.138628f, 0.066882f, -0.105097f, -0.010419f, 0.079022f,\n-0.069921f, 0.566422f, 0.093646f, 0.007314f, -0.098822f, -0.119350f, -0.043432f, 0.103276f, 0.073716f, -0.062946f, 0.422856f, 0.017409f, 0.146776f, 0.534303f, 0.094415f, 0.138134f, -0.177887f, -0.109372f, -0.033919f, -0.139609f, -0.137192f,\n-0.577288f, -0.070035f, 0.074226f, -0.166527f, -0.092571f, -0.112792f, 0.131408f, 0.107291f, -1.756273f, -1.084080f, -0.887356f, -0.162529f, 0.184705f, -0.038235f, 0.279333f, 0.252339f, 0.101124f, -0.021200f, 0.001761f, -0.734587f, -0.026150f,\n0.082985f, 0.011983f, -0.971228f, -0.066563f, 0.081217f, -0.556114f, -0.113167f, -0.243062f, -0.171839f, -0.009232f, -0.029770f, 0.927262f, 0.116865f, -1.677233f, 0.026550f, -0.094827f, -0.019117f, -0.027790f, -0.002991f, 0.004710f, -0.024981f,\n0.821773f, 0.040185f, -0.293755f, -0.194739f, -0.067481f, -0.033546f, -0.037575f, 0.517719f, -0.727971f, 0.088711f, -0.122756f, 0.107389f, 0.098858f, 0.033141f, 0.218319f, 0.125154f, 1.910247f, -0.034695f, 0.406604f, 0.027034f, -0.085205f, 0.145423f,\n0.039108f, 0.055521f, -0.164826f, 0.017425f, -0.115223f, 0.715679f, 0.054227f, -0.148316f, -0.019056f, -0.176923f, 0.022478f, -0.876261f, -0.067784f, 0.104497f, 0.150929f, -0.110397f, -0.419837f, 0.058913f, 0.022401f, -0.090029f, 0.029126f,\n0.119179f, -0.065926f, -0.111554f, -1.023096f, 0.174201f, -0.524341f, 0.042585f, 0.524923f, 0.067087f, 0.535623f, -0.320248f, -0.081812f, -0.140601f, -0.218112f, -0.212108f, 0.008375f, 0.124541f, -0.138456f, 0.652024f, -1.198235f, 0.162413f,\n-0.360590f, 0.009538f, -0.311009f, 0.059327f, 0.021777f, -0.025099f, -0.381778f, 0.008995f, -1.530216f, -0.019513f, 0.142295f, 0.044936f, -0.158311f, 0.056264f, 0.103808f, -0.017812f, 0.610799f, 0.116164f, -0.559087f, -0.880224f, -0.080280f,\n-0.135288f, -0.108344f, 0.632382f, -0.618637f, 0.024754f, 0.054478f, -0.067540f, -0.008561f, -0.083398f, 1.288293f, -0.105790f, 1.568907f, 0.105125f, 0.375849f, 0.069130f, -0.166792f, 0.034399f, -0.070116f, -0.049254f, 0.040071f, -0.108582f,\n-0.175965f, 0.079357f, 0.051280f, -0.180737f, 0.107824f, 0.032787f, -0.166706f, -0.103031f, -0.092745f, -0.053718f, 0.070316f, 0.137449f, -0.720730f, -0.132977f, 0.089722f, 0.006607f, -0.147581f, 0.033194f, -0.070481f, -0.122707f, 0.110540f,\n0.200813f, 0.446609f, -0.092176f, 0.730410f, -0.002034f, 0.638583f, 0.240758f, 0.128516f, 0.074698f, -0.302865f, -0.262256f, 0.113647f, -0.112458f, 0.092797f, 0.777770f, -0.829498f, 0.014928f, -0.389937f, 0.167751f, -0.021925f, 0.136785f, 0.046278f,\n0.088331f, -0.155599f, -0.077795f, -1.158800f, 0.096687f, 0.062702f, 0.628096f, -0.008007f, -0.118762f, 0.007110f, 0.125200f, 0.771803f, 0.081563f, -0.625261f, -0.367478f, -0.086944f, -0.132949f, 0.167003f, 1.007479f, -0.164517f, 0.057803f,\n0.023042f, -0.072247f, -0.118072f, 0.121020f, 1.295962f, -0.056884f, 0.401449f, 0.164319f, 0.399461f, 0.057643f, 0.120089f, 0.032209f, 0.107051f, -0.067250f, 0.032365f, -0.184403f, -0.115573f, -0.365325f, -0.062276f, -0.518360f, -0.627392f,\n0.082782f, -0.058133f, 0.299423f, -0.100087f, 0.043263f, 0.084153f, 0.096660f, -0.898041f, 0.069974f, 0.089400f, 0.065557f, 0.166745f, 0.151800f, 0.126644f, 0.072742f, 0.146101f, -0.784538f, 0.988438f, 0.145337f, 0.973179f, -0.122409f, 0.643735f,\n-0.163094f, -0.048228f, 0.131398f, -0.133816f, -0.838532f, -0.046463f, -0.049365f, 0.069376f, 0.502042f, -0.270563f, 0.058998f, -1.133923f, 0.163137f, 0.178915f, 0.118993f, -0.138344f, -0.098013f, -1.009095f, 0.094204f, -0.518501f, -0.082395f,\n0.091486f, -0.033567f, -0.145921f, 0.103852f, 0.129656f, -0.147492f, 0.210918f, 0.029657f, -0.015038f, 0.656145f, -0.034117f, -0.035050f, 0.000178f, 0.205404f, -0.487126f, 0.053025f, -0.172562f, -0.147576f, 0.066030f, 0.129591f, 0.280925f,\n-0.087902f, -0.207349f, 0.058259f, -0.504607f, 0.020102f, -0.048522f, -0.041194f, -0.025255f, 0.002982f, 0.023723f, -0.077141f, -0.049540f, 0.084283f, 0.044151f, -0.088276f, -0.230866f, 0.094046f, -0.167848f, 0.387898f, -0.174285f, 0.121759f,\n-0.102286f, 0.154514f, 0.141829f, 0.118105f, -0.170803f, -0.104044f, 0.069984f, -0.024658f, -0.120270f, -0.002385f, 0.165341f, -0.353915f, 0.529440f, 0.134279f, -0.016752f, 0.041926f, -0.098519f, -0.205096f, 0.145826f, -0.029462f, 0.104369f,\n-0.677356f, 0.108206f, -0.127382f, -0.028967f, 0.422666f, -0.150678f, -0.168848f, 0.051256f, -0.062360f, -0.085205f, 0.100249f, -0.075459f, 0.102633f, -0.542980f, 0.116225f, 0.945900f, -0.106327f, 0.091798f, -0.543146f, -0.117738f, -0.091442f,\n-0.147216f, -0.027681f, 0.587858f, 0.076622f, -0.301021f, 0.636562f, -0.121367f, 0.039986f, -0.140292f, 0.272871f, 0.276512f, -0.007506f, 0.011250f, 0.142799f, -0.015187f, -0.165069f, 0.208613f, -0.016742f, -0.256633f, 0.017588f, -0.144155f,\n0.085305f, -0.113834f, 0.090118f, -0.082351f, -0.059671f, -0.122870f, -0.074093f, -0.086338f, 0.262869f, 0.095288f, -0.220215f, -0.390778f, 0.041820f, -0.162284f, 0.815094f, 0.068990f, -0.039302f, -0.016564f, -0.030005f, 0.227128f, 0.099914f,\n0.068527f, 0.003235f, 0.106525f, -0.080129f, -0.113775f, -0.095360f, -0.162041f, -0.244089f, -0.205815f, 0.156841f, -0.210325f, 0.110146f, -0.074880f, 0.252458f, 0.040214f, 0.056376f, -0.313229f, 0.876736f, -0.135767f, -0.162913f, 0.083667f,\n0.784144f, 0.260920f, 0.039772f, 0.210003f, -0.056189f, 0.181106f, 0.050992f, -0.133819f, -0.042396f, 0.222595f, 0.128163f, 1.168875f, -0.059107f, 0.119101f, -0.411788f, 0.149539f, -0.090372f, -0.152483f, 0.073039f, 0.632970f, 0.093058f, -0.352561f,\n-0.599167f, -0.013916f, -0.086703f, 0.022048f, 0.306536f, -0.215566f, 0.128572f, -0.096425f, -0.076944f, 0.110308f, 0.132065f, -0.028736f, -0.037816f, -0.657185f, -0.135024f, 0.307998f, 0.090284f, -0.118361f, 0.006865f, -0.100704f, -0.024731f,\n0.061040f, -0.079569f, -0.156534f, -0.207873f, -0.009108f, 0.028908f, -0.078651f, 0.143568f, -0.143897f, 0.671501f, 0.070615f, -0.093476f, 0.112376f, 0.135797f, 0.161038f, 0.039125f, 0.080836f, 0.006868f, -0.055338f, 0.058859f, 0.025529f, 0.077016f,\n-0.071546f, 0.047173f, 0.241441f, -0.019418f, 0.498623f, 0.044458f, 0.652046f, 0.329206f, -0.143241f, -0.022528f, 0.265776f, 0.089973f, 0.068469f, 0.134096f, -0.149566f, -0.510787f, 0.943447f, 0.155366f, 0.141182f, -0.049894f, 0.319256f, -0.174494f,\n0.098995f, 0.002222f, 0.381306f, 0.121483f, -0.244614f, -0.018319f, -0.117522f, -0.259256f, -0.076519f, 0.145986f, -0.117616f, -0.137202f, 0.194518f, 0.055122f, -0.493631f, -0.747434f, -0.158018f, 0.017217f, 0.128114f, 0.136496f, -0.694163f,\n0.060665f, 0.108301f, -0.122943f, 0.013205f, -0.016704f, -0.313630f, 0.045386f, -0.723669f, 0.091045f, 0.349443f, 0.033479f, 0.024983f, -0.015961f, -0.053648f, -0.114659f, 0.128938f, -0.057008f, -0.010643f, -0.013355f, 0.148377f, 0.397262f,\n-0.111835f, 0.084422f, 0.108804f, 0.195932f, 0.144572f, -0.106376f, 0.024228f, -0.083916f, -0.164017f, -0.157721f, -0.081864f, -0.013738f, -0.100584f, 0.156466f, -0.118989f, -0.175253f, -1.356019f, 0.001227f, -0.096277f, 0.094431f, 0.429905f,\n-0.168158f, 0.425567f, -0.313666f, -0.022391f, 0.013304f, 0.367667f, 0.325563f, -0.143479f, 0.069660f, -0.009777f, -0.590026f, 0.259225f, -0.101517f, 0.341262f, 0.097356f, 0.517392f, -0.101947f, -0.038735f, -0.151709f, -0.457127f, -0.110063f,\n-0.277505f, 0.113876f, 0.012952f, 0.078993f, -0.075703f, 0.148211f, 0.072278f, -0.110368f, 0.115761f, 0.029824f, -0.169189f, -0.333222f, -0.143238f, -0.019030f, 0.129284f, 0.095915f, -0.422321f, -0.023349f, -0.089838f, 0.006029f, 0.088826f,\n0.112693f, 0.101898f, -0.108061f, 0.648859f, 0.027527f, 0.426062f, 0.092162f, -0.120388f, -0.050799f, -0.141230f, -0.158346f, 0.103343f, -0.134713f, -0.007218f, 1.249804f, -0.051014f, -0.024292f, 0.382489f, 0.018467f, -0.104079f, -0.329360f,\n0.081655f, 0.139050f, -0.092404f, -0.008210f, -0.801497f, -0.161975f, 0.013696f, -0.158430f, 0.061989f, -0.049306f, -0.062119f, -0.052135f, -1.257620f, -1.135566f, -0.910187f, -0.016237f, 0.090198f, -0.006696f, -0.164430f, -0.101565f, -0.172812f,\n-0.071238f, -0.270856f, -0.585765f, -0.031600f, -0.037474f, 0.001772f, -0.355728f, 0.308499f, -0.025490f, -0.222207f, -0.167886f, -0.325299f, -0.073440f, 0.053177f, 0.032085f, 1.180182f, -0.167865f, -1.046266f, -0.022651f, 0.006806f, 0.020453f,\n0.054907f, 0.099340f, -0.078093f, -0.025395f, 0.087852f, 0.132306f, -0.158119f, -0.396158f, -0.063309f, 0.077568f, 0.027655f, 0.057764f, -0.087677f, 0.141839f, 0.004632f, 0.085920f, 0.043157f, 0.114431f, 0.808519f, 0.125523f, 1.642262f, 0.033790f,\n0.197231f, -0.069226f, -0.051213f, -0.099407f, -0.046560f, -0.010524f, 0.029400f, 0.026528f, 0.054682f, 0.651580f, -0.041927f, 0.106642f, 0.192463f, -0.083347f, -0.062392f, -0.683276f, 0.100130f, -0.139606f, 0.113464f, -0.102041f, 0.309522f,\n0.161886f, -0.044394f, 0.133661f, -0.090641f, 0.091288f, -0.154953f, 0.017159f, -0.490833f, -0.050733f, -0.368117f, -0.027722f, 0.235753f, -0.167710f, 0.148186f, 0.009606f, 0.117076f, -0.129284f, -0.392442f, 0.225235f, 0.018531f, 0.070132f,\n0.083636f, 1.135701f, -0.659698f, 0.063815f, 0.547246f, -0.020961f, -0.076346f, 0.152733f, -0.153828f, 0.053306f, -0.273867f, 0.157568f, -1.142068f, 0.002609f, 0.139385f, -0.154033f, 0.019015f, 0.031657f, 0.052756f, -0.082288f, -0.058847f,\n-0.133734f, -0.011599f, -0.358696f, -0.024895f, -0.073652f, -0.080488f, 0.165580f, -0.431778f, -0.090716f, 0.099146f, -0.134695f, -0.088883f, -0.032562f, 1.457502f, -0.168943f, 1.332020f, -0.116246f, 0.304916f, 0.124767f, 0.116385f, -0.018629f,\n0.041995f, 0.003007f, -0.114867f, -0.085792f, 0.056745f, 0.755021f, -0.062473f, -0.116866f, 0.330612f, 0.076724f, -0.163924f, -0.367513f, 0.004861f, 0.069664f, -0.106473f, -0.104976f, -0.369862f, -0.062256f, 0.070435f, 0.116917f, -0.129596f,\n-0.100885f, -0.035419f, 0.152398f, -0.300973f, -0.741343f, 0.815329f, 0.132169f, 0.184160f, -0.147818f, 0.152952f, 0.080652f, -0.000017f, -0.185053f, -0.221138f, -0.288951f, -0.106106f, -0.008888f, -0.071005f, 0.999621f, -0.817097f, 0.029754f,\n-0.274609f, -0.088435f, -0.098419f, -0.003648f, 0.097257f, -0.057415f, 0.013220f, -0.033636f, -1.522089f, 0.014641f, -0.125216f, 0.641243f, 0.020768f, 0.025218f, -0.028398f, -0.140795f, 0.424299f, 0.088869f, -0.644038f, -0.163829f, -0.142007f,\n0.069709f, 0.150174f, 0.845359f, -0.173980f, -0.124872f, 0.030746f, 0.083245f, -0.113333f, -0.148996f, 0.813539f, -0.139728f, 0.296761f, 0.002373f, 0.514162f, 0.073375f, -0.135615f, -0.073097f, 0.077908f, -0.160741f, -0.077843f, 0.116402f,\n-0.000910f, -0.355594f, -0.112093f, -0.442140f, -0.231895f, 0.061712f, -0.064865f, 0.330073f, 0.022536f, -0.058433f, 0.037821f, -0.168507f, -0.875188f, 0.047409f, 0.099130f, 0.029829f, 0.001255f, 0.126462f, 0.016592f, -0.096561f, -0.155204f,\n-0.992606f, 0.830021f, 0.019289f, 0.646519f, 0.094185f, 0.490364f, -0.421839f, -0.166227f, 0.117453f, -0.164011f, -0.696062f, 0.129087f, -0.080016f, -0.084262f, 0.674044f, -0.279309f, 0.095782f, -0.946156f, 0.055399f, -0.191573f, 0.160291f,\n0.117444f, -0.003247f, -1.031640f, -0.002578f, -0.268245f, -0.120235f, -0.008258f, 0.056091f, -0.151646f, 0.087157f, 0.095619f, -0.129003f, 0.272315f, -0.042666f, -0.024501f, 0.934982f, 0.110007f, -0.035433f, -0.045838f, 0.411794f, -0.444641f,\n0.116251f, -0.101867f, 0.117506f, -0.139995f, 0.113995f, 0.068331f, 0.128237f, -0.194895f, -0.087286f, -0.228593f, -0.023840f, -0.072140f, 0.140072f, 0.049059f, -0.163969f, -0.011718f, -0.103525f, -0.039585f, -0.112158f, -0.138144f, -0.206560f,\n-0.130585f, 0.017005f, -0.144313f, 0.331003f, -0.000195f, 0.031740f, 0.089983f, -0.110514f, 0.108356f, 0.070043f, 0.089666f, 0.012131f, 0.083364f, 0.055511f, -0.004278f, -0.103539f, -0.048224f, -0.255001f, 0.566194f, 0.123176f, 0.204176f, 0.139733f,\n0.119284f, -0.553342f, -0.124346f, -0.165074f, 0.090397f, -0.450688f, 0.054640f, -0.100144f, -0.130749f, 0.721830f, 0.055589f, -0.165066f, -0.038328f, -0.136055f, -0.005558f, -0.170249f, -0.022308f, 0.044504f, -0.509620f, 0.083829f, 0.743559f,\n0.135997f, -0.010400f, -0.726946f, -0.108306f, 0.080617f, -0.081215f, -0.105170f, 0.790361f, 0.068946f, -0.285464f, 0.868063f, -0.050610f, 0.133075f, -0.145744f, 0.310147f, 0.088325f, 0.005082f, 0.140103f, -0.006938f, -0.081184f, 0.148674f,\n0.217350f, -0.072149f, -0.079678f, -0.018658f, -0.328983f, 0.158776f, -0.074578f, 0.002172f, 0.164543f, 0.145431f, -0.041209f, 0.013267f, 0.108678f, 0.272891f, 0.108577f, -0.426701f, -0.420256f, -0.140690f, -0.013240f, 0.960655f, -0.074915f,\n0.141064f, -0.024096f, -0.177500f, 0.170776f, 0.095907f, -0.018235f, 0.154278f, 0.118310f, -0.087168f, 0.025454f, -0.054385f, -0.344882f, -0.395000f, -0.227972f, 0.053518f, 0.003669f, 0.158074f, -0.115387f, 0.326967f, -0.071612f, -0.093971f,\n-0.384118f, 1.045070f, -0.101743f, -0.074410f, -0.142999f, 0.937091f, 0.325418f, 0.015128f, 0.368960f, 0.049679f, 0.407504f, -0.074402f, -0.118869f, 0.027621f, 0.085653f, 0.134573f, 1.282368f, -0.037132f, 0.047403f, -0.197919f, -0.145572f, 0.157247f,\n-0.133565f, 0.115931f, 0.492185f, -0.146424f, -0.667479f, -0.035759f, 0.131583f, -0.166059f, 0.167565f, 0.093528f, -0.187137f, 0.157332f, -0.036319f, -0.158127f, -0.129432f, -0.164513f, 0.164943f, -0.152595f, -0.278959f, -0.050871f, -0.100867f,\n-0.156080f, 0.021030f, -0.035562f, 0.110457f, 0.084247f, -0.165870f, -0.036094f, -0.118151f, -0.230944f, 0.035662f, 0.036634f, -0.147455f, -0.039596f, 0.048598f, 0.535677f, -0.023167f, 0.128391f, 0.135499f, -0.102699f, -0.016919f, -0.016132f,\n-0.068700f, -0.085476f, 0.132883f, 0.064868f, -0.028653f, 0.079282f, 0.241672f, -0.039314f, 0.007193f, -0.167412f, 0.090557f, 0.092384f, 0.321653f, 0.176785f, -0.149314f, -0.129823f, -0.231270f, 0.460691f, -0.134503f, -0.170979f, -0.167633f,\n-0.156076f, 1.081251f, -0.166506f, 0.359821f, -0.029321f, 0.458843f, -0.096566f, 0.137153f, 0.023639f, 0.281812f, -0.026621f, 0.157390f, 0.154381f, 0.027992f, -0.683582f, -0.079835f, 0.076566f, 0.105526f, 0.150057f, 0.428266f, 0.014212f, -0.522881f,\n-0.492610f, 0.096719f, -0.125529f, 0.119010f, 0.159373f, -0.413615f, -0.114531f, -0.011836f, -0.112574f, -0.066115f, 0.069177f, -0.395490f, -0.149783f, -0.809484f, -0.099915f, -0.284792f, -0.118116f, -0.111139f, 0.047110f, -0.089353f, -0.086302f,\n-0.146075f, 0.124955f, 0.049647f, -0.429523f, -0.037791f, 0.287967f, -0.218817f, -0.038669f, 0.144768f, 0.521222f, 0.024551f, -0.140753f, -0.079351f, 0.018125f, 0.041087f, 0.131073f, -0.003689f, -0.069824f, 0.056039f, -0.008335f, 0.025579f,\n-0.037041f, -0.808748f, -0.347694f, -0.450201f, -0.048564f, 0.141263f, 0.112393f, 0.168084f, -0.057401f, 0.101114f, 0.033031f, 0.295431f, 0.806216f, 0.032054f, -0.121677f, -0.129075f, -0.588770f, 0.854283f, 0.158223f, 0.993633f, -0.085679f,\n0.796406f, 0.160469f, -0.097951f, -0.095442f, -0.378982f, -0.033277f, 0.441689f, 0.083084f, -0.106826f, -0.067705f, -0.120797f, -0.018935f, -0.096629f, -0.024945f, 0.127113f, -0.181377f, -0.372416f, -0.231392f, 0.115834f, -0.004238f, 0.047528f,\n0.428134f, -0.456155f, -0.106732f, -0.084889f, -0.069860f, -0.134736f, 0.150065f, -0.056227f, 0.095704f, 0.771172f, 0.164039f, 0.132559f, 0.022732f, 0.040464f, -0.131016f, -0.053240f, 0.031377f, -0.072569f, -0.046836f, -0.155649f, 0.140632f,\n0.034050f, 0.134235f, 0.100600f, -0.014490f, -0.145692f, -0.229757f, -0.141340f, 0.094539f, 0.129817f, 0.137234f, -0.592412f, -0.147219f, 0.116718f, 0.092737f, -0.024140f, 0.117441f, -0.061411f, -0.137968f, -0.648906f, -0.416399f, -1.292302f,\n0.083112f, 0.378781f, -0.171814f, 0.303166f, 0.217316f, -0.041348f, -0.030121f, -0.194166f, -0.171904f, 0.002796f, -0.017341f, -0.011014f, -0.489507f, 0.743102f, 0.059668f, -0.124926f, 0.162999f, 0.067581f, 0.089959f, 0.096162f, -0.000326f,\n0.547679f, 0.135781f, -0.880126f, 0.125333f, -0.103129f, -0.076201f, -0.042898f, -0.015942f, 0.047875f, 0.011999f, 0.419640f, 0.126033f, -0.889963f, -0.123397f, 0.127141f, -0.173494f, -0.124182f, 0.309434f, -0.613631f, 0.060746f, 0.099321f,\n0.046358f, -0.040766f, -0.028074f, 0.986913f, 0.075602f, 2.093490f, -0.159583f, 0.030707f, -0.074001f, 0.125668f, 0.095920f, -0.160258f, -0.169352f, -0.080280f, -0.054102f, -0.105214f, 0.047948f, -0.090452f, 0.067707f, -0.402339f, -0.076048f,\n-0.005807f, -0.152756f, -0.137365f, -0.000085f, -0.078132f, 0.014263f, 0.533129f, 0.079187f, -0.118166f, 0.091168f, 0.101301f, -0.037340f, -0.157705f, -0.108579f, -0.372262f, 0.170901f, -1.023456f, -0.174192f, 0.265428f, -0.081734f, 0.436579f,\n-0.491802f, -0.079153f, -0.055680f, -0.406117f, 0.494986f, 0.097630f, -0.067732f, -0.034672f, 0.567622f, -0.567370f, 0.022594f, 0.628723f, 0.107981f, 0.141075f, -0.079503f, -0.113783f, 0.158034f, 0.047402f, 0.105955f, -1.398012f, -0.110474f,\n-0.032826f, 0.294530f, 0.145164f, -0.130803f, 0.006880f, 0.020954f, 0.165579f, 0.094097f, -0.516949f, 0.203083f, -0.158802f, 0.042772f, 0.030064f, 0.453803f, -0.409050f, -0.029211f, 0.139739f, 0.070239f, 0.123594f, -0.060593f, 1.426649f, -0.123673f,\n1.013489f, -0.165099f, 0.099103f, -0.112116f, -0.034835f, 0.101862f, -0.075417f, -0.086882f, -0.135914f, -0.181430f, 0.032427f, 0.329383f, -0.128239f, -0.098502f, -0.071395f, -0.050289f, 0.127174f, -0.184993f, -0.122083f, -0.153731f, 0.131965f,\n-0.126925f, -0.940501f, 0.039162f, 0.022574f, 0.080661f, 0.107280f, -0.082246f, -0.123461f, 0.079009f, -0.077165f, -0.138130f, 0.481499f, -0.132278f, 0.223981f, -0.132619f, 0.184172f, 0.144527f, 0.090474f, -0.007084f, -0.210414f, 0.147890f,\n-0.145532f, 0.043874f, -0.025374f, 0.999593f, -0.645811f, -0.027269f, 0.203669f, 0.132196f, -0.038238f, -0.073996f, -0.141085f, -0.090088f, 0.590879f, -0.025637f, -1.337079f, -0.046981f, 0.033875f, 0.278306f, -0.046327f, -0.048059f, 0.080418f,\n0.024569f, 0.553889f, 0.049517f, -0.732667f, 0.451434f, 0.042228f, -0.057261f, 0.143012f, 1.003077f, -0.330981f, -0.137226f, 0.156602f, -0.152653f, -0.106906f, 0.053105f, 0.980048f, 0.089239f, 0.133277f, 0.139269f, 0.388793f, -0.059080f, 0.091920f,\n0.030014f, -0.082365f, 0.051498f, -0.093413f, 0.018553f, 0.011872f, -0.616929f, -0.128431f, -0.401519f, -0.577533f, 0.063697f, 0.002939f, 0.870045f, -0.063258f, 0.121713f, 0.031473f, -0.175098f, -0.938323f, 0.154549f, -0.170855f, -0.092190f,\n-0.159505f, -0.026045f, 0.019221f, 0.107927f, -0.281560f, -0.450838f, 0.279570f, -0.107696f, 0.877878f, -0.057591f, 0.655646f, -0.554293f, -0.033318f, -0.033393f, -0.588394f, -0.166458f, -0.159412f, 0.005308f, 0.002385f, 0.715884f, 0.319332f,\n-0.143332f, -0.655073f, -0.142252f, 0.109133f, -0.107522f, -0.165553f, -0.039590f, -0.669080f, -0.007280f, -0.271532f, 0.043719f, -0.112052f, 0.224198f, -0.046624f, -0.073843f, 0.148096f, -0.119241f, 0.049509f, 0.125768f, -0.036763f, 1.056331f,\n0.066134f, 0.093719f, 0.139848f, 0.297642f, -0.523209f, -0.157422f, 0.094374f, -0.019459f, -0.120583f, 0.043375f, 0.383807f, 0.118709f, 0.008063f, 0.155285f, -0.294782f, 0.023622f, -0.157439f, -0.041701f, 0.152932f, 0.045854f, -0.030311f, 0.113699f,\n-0.086094f, 0.018318f, 0.030611f, -0.147758f, -0.210327f, -0.071866f, 0.110182f, 0.273787f, -0.065989f, 0.008585f, 0.090363f, -0.008569f, 0.060313f, 0.132459f, 0.020100f, 0.129518f, 0.004783f, -0.050281f, 0.074979f, -0.051637f, -0.160858f,\n-0.294675f, 0.251290f, 0.043143f, -0.047120f, 0.034574f, 0.039406f, -0.971722f, -0.167385f, -0.091973f, 0.138159f, -0.368112f, -0.099060f, 0.056216f, 0.104372f, 0.701812f, -0.095581f, 0.131605f, 0.380199f, 0.006101f, -0.019925f, -0.175584f,\n0.086156f, -0.085094f, -0.498569f, -0.098427f, 1.051223f, -0.025823f, -0.161409f};\n            Layer0B = new float[]{-0.258583f, 0.000000f, -0.005248f, 0.002120f, 0.000000f, 0.554779f, -0.008047f, 0.335827f, -0.337699f, -0.007573f, -0.009143f, 0.000000f, -0.638380f, 0.012579f, -0.005553f, -0.006835f, 0.000000f, -0.008788f, -0.015453f,\n-0.157895f, -0.011690f, -0.363602f, 0.000000f, -0.508440f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, -0.005633f, -0.010451f, -0.009868f, 0.896214f, -0.005512f, 0.393380f, 0.608579f, -0.013059f, 0.000000f, 0.067292f, -0.005353f,\n0.000000f, -0.015928f, -0.008583f, 0.059077f, -0.015042f, -0.004119f, 0.000000f, 0.000000f, -0.027269f, 0.000000f, -0.010855f, -0.106572f, -0.495989f, 0.362551f, -0.005173f, -0.410091f, -0.007469f, -0.290679f, 0.644469f, -0.015980f, -0.017832f,\n0.052698f, 0.685596f, 0.000000f, -0.005973f, 0.000000f, -0.262743f, -0.646989f, 0.000000f, 0.533473f, 0.000000f, -0.212637f, -0.005506f, 0.000000f, 0.000000f, -0.089068f, -0.007746f, 0.420801f, 0.000000f, -0.010833f};\n            Layer1W = new float[]{2.872352f, 0.556339f, 0.895218f, -2.633433f, 2.053285f, -0.507537f, 0.932087f, 1.866706f, -1.415985f, -1.588534f, 0.014323f, 0.055322f, 0.092370f, 0.182154f, 0.232235f, -0.229758f, 0.103136f, -0.123429f, -0.055899f, -0.205034f,\n0.235538f, -0.125417f, 0.044909f, 0.069140f, -0.055655f, -0.084499f, 0.033251f, -0.012585f, 0.120955f, -0.149363f, -0.312361f, -0.058666f, -0.023780f, -0.029115f, -0.133812f, -0.217813f, 0.075189f, -0.092771f, -0.119107f, 0.117169f, 0.233317f,\n0.233984f, 0.045792f, 0.189911f, -0.014824f, 0.009572f, 0.217259f, 0.214096f, 0.250269f, 0.253756f, -6.872386f, -1.351016f, 6.035550f, 8.571924f, -10.131823f, -9.367016f, -13.352841f, 2.998245f, 2.248629f, 2.047788f, -0.177559f, -0.231822f,\n-0.132193f, 0.047925f, 0.227843f, 0.218174f, 0.074161f, -0.102255f, 0.205715f, -0.094381f, 1.828311f, 0.442961f, -1.191791f, 0.160166f, 1.092862f, 2.238777f, 2.197591f, -1.863975f, -0.945041f, -2.109085f, -9.020297f, -11.619086f, -3.330025f,\n-6.822249f, 3.089345f, -0.855188f, 2.045776f, -7.314623f, 0.767956f, 2.372971f, -0.078842f, 0.023192f, -0.045379f, -0.190051f, -0.204497f, 0.163856f, 0.033390f, -0.209885f, 0.005038f, -0.243228f, -0.085859f, 0.020061f, 0.233847f, 0.210755f,\n-0.067229f, -0.110267f, 0.073638f, -0.180283f, -0.215597f, 0.127264f, -0.223316f, -0.102419f, 0.236136f, 0.044038f, 0.155319f, -0.188849f, -0.095012f, 0.012847f, -0.143265f, 0.213285f, -3.683848f, -0.481653f, 3.723568f, -0.347374f, 0.402750f,\n-4.868135f, -4.350837f, 1.519851f, 1.972614f, 0.617691f, -6.247998f, -7.832911f, -0.023818f, 0.022304f, 3.415335f, -1.006235f, 1.743585f, -2.325608f, 0.295237f, -0.415970f, 0.204051f, 0.079190f, -0.088982f, -0.064066f, 0.213939f, 0.065844f,\n-0.247622f, 0.125372f, 0.229277f, 0.216610f, 0.249110f, 0.193935f, 0.172923f, 0.058107f, 0.215047f, 0.070795f, 0.063728f, -0.176464f, -0.046154f, 0.035154f, 0.230278f, -0.125439f, -0.121620f, 0.001265f, 0.213185f, 0.195191f, -0.049603f, 0.114569f,\n-0.041239f, -0.142195f, -0.256586f, 0.215897f, 0.225524f, -0.235515f, 0.238659f, 0.215709f, -0.153728f, 0.170772f, -0.075901f, 0.059694f, 0.201915f, 0.075322f, -0.214879f, -0.076065f, -0.120580f, 0.236577f, -0.003614f, -0.207308f, -0.043615f,\n-0.222190f, -7.619622f, -5.560598f, 3.462031f, -4.043738f, 2.786101f, -13.118224f, -5.124330f, 1.915245f, 3.954341f, 2.902455f, -0.030049f, -0.136162f, 0.201107f, 0.048837f, 0.012309f, 0.155613f, 0.242665f, -0.165177f, -0.173594f, 0.239568f,\n-8.719612f, -2.122308f, -0.149140f, -3.365802f, 8.238699f, -8.327408f, -1.698435f, 4.317533f, 3.830385f, -3.163497f, -0.047915f, 0.153857f, -0.249278f, 0.190574f, 0.203597f, 0.147773f, 0.136646f, -0.050815f, -0.112024f, -0.044663f, 0.184154f,\n-1.079801f, 1.905044f, 1.303494f, -5.841938f, 2.609357f, -1.388126f, 0.733502f, -0.059715f, -0.304990f, -0.090891f, 0.019332f, -0.134694f, 0.167361f, 0.005244f, -0.216550f, -0.084640f, -0.019012f, -0.224394f, 0.101602f, -0.247187f, 0.211302f,\n0.091856f, 0.130682f, 0.238871f, -0.073102f, -0.028503f, 0.058321f, -0.136055f, 0.197304f, -0.065966f, -0.000978f, 0.194992f, 0.163487f, -0.143312f, 0.047361f, -0.238663f, 0.237708f, -0.119919f, 0.176796f, 0.150135f, -0.049808f, -0.099537f,\n-0.238367f, -0.013241f, -0.144539f, -0.000735f, -0.049284f, -0.208694f, -0.247227f, 0.043258f, -0.186124f, 0.175718f, -0.121449f, 0.055659f, 0.197750f, -0.028383f, 0.152103f, -0.164853f, -0.088172f, 0.057280f, -0.188759f, -0.130714f, -0.176337f,\n0.212515f, -0.023564f, -0.039879f, 0.102758f, -0.179630f, 0.113386f, 0.187256f, -0.216268f, -0.224707f, 0.273447f, -0.072166f, 0.000934f, 0.229939f, 0.068703f, -0.174794f, 0.196696f, 0.030173f, 0.175626f, 0.168953f, -0.191626f, 0.120064f, -0.028740f,\n-0.224937f, 0.058375f, -0.235908f, 0.071239f, -2.158468f, -0.909163f, -3.299388f, 1.114295f, -0.344128f, 1.398108f, 0.294991f, 0.785368f, 2.349527f, -2.107732f, 0.132709f, -0.238318f, 0.070406f, 0.186095f, -0.168048f, -0.225059f, 0.049086f,\n0.107934f, 0.242768f, -0.135865f, 1.437463f, 1.137362f, -0.817285f, 0.066641f, -0.510345f, 1.318769f, 0.710571f, 0.092374f, -1.435750f, -0.280488f, 2.038239f, 1.193575f, -1.190363f, 0.298009f, -1.305820f, 2.520758f, 0.886658f, 0.580163f, -1.717903f,\n-1.742613f, 0.025035f, 0.195731f, -0.057998f, -0.029491f, -0.224788f, -0.052863f, -0.178014f, -0.139383f, 0.225932f, 0.076853f, -0.015381f, 0.168973f, -0.162203f, 0.200214f, 0.208409f, -0.083068f, 0.074760f, -0.141107f, -0.256331f, 0.039262f,\n-7.763834f, -4.061152f, 0.595419f, 0.536281f, 1.220449f, -7.300698f, -3.785963f, 2.195778f, 4.303672f, 2.332188f, -0.039697f, 0.138619f, -0.148312f, -0.202208f, -0.200927f, -0.013164f, -0.011289f, -0.206978f, 0.031915f, 0.108101f, 0.204135f,\n-0.101193f, 0.124039f, -0.177474f, -0.143415f, 0.151093f, -0.131997f, 0.132086f, -0.028083f, -0.250702f, 0.144884f, 0.070518f, 0.264896f, 0.037894f, -0.139258f, -0.007479f, 0.045130f, 0.150952f, -0.168439f, 0.101958f, 0.030164f, -0.105919f,\n-0.020879f, -0.237999f, -0.216263f, 0.214960f, -0.183960f, 0.004486f, -0.144213f, 0.258278f, -6.541598f, 0.112269f, -0.324116f, 2.578374f, -0.794154f, 1.781607f, 0.128907f, -9.689257f, 1.508573f, -0.115196f, 0.188947f, 0.107935f, 0.213172f,\n0.181873f, -0.018082f, -0.099962f, 0.144777f, 0.046141f, 0.258213f, 0.030689f, 0.102238f, -0.104016f, -0.207787f, -0.115086f, -0.086628f, 0.074477f, -0.077114f, 0.234147f, 0.002156f, 0.250684f, -0.200818f, 0.226525f, 0.256699f, -0.165381f, 0.257887f,\n-0.120057f, 0.130978f, 0.067758f, -0.135301f, -0.110477f, -0.042005f, -0.090917f, 0.106255f, -0.194814f, 0.127727f, 0.208857f, -0.177936f, -0.142509f, 0.143752f, 0.210962f, 0.116241f, 0.217696f, 0.184540f, -0.145857f, 0.147356f, 0.215128f, 0.028871f,\n0.156532f, 0.055379f, 0.037882f, -0.038575f, 0.044745f, 0.199801f, -0.211577f, -0.156830f, -0.206845f, -0.056307f, 0.210683f, 0.207344f, 0.049912f, 0.158971f, 0.198026f, -0.255370f, -0.046776f, 0.145370f, -0.219364f, 0.070860f, -0.002316f,\n-0.052841f, -0.181893f, -7.854765f, -0.045120f, 4.688258f, 7.260476f, 5.879862f, -11.848519f, -1.717938f, 1.729347f, 4.449252f, -17.536463f, -1.457542f, -3.730885f, 4.242805f, -2.089553f, 7.067261f, 4.266905f, 2.616580f, -5.165204f, -9.313859f,\n2.029003f, -8.570450f, -0.312409f, -0.323086f, -6.410367f, 6.591171f, -3.121047f, 1.699370f, 2.738599f, 3.991308f, -4.790890f, 0.108385f, 0.060745f, 0.149312f, 0.086479f, 0.033799f, -0.050499f, -0.201168f, -0.263162f, -0.060231f, 0.140076f,\n1.083183f, 1.219212f, 2.353325f, 1.075074f, -4.514788f, -0.800917f, -2.302846f, 1.744806f, -0.422714f, 0.192229f, 0.063781f, -0.057966f, -0.078008f, 0.015390f, -0.024741f, -0.028426f, -0.109606f, -0.032861f, -0.245649f, 0.248431f, 1.810097f,\n1.340297f, 0.931568f, 0.512651f, -2.617301f, 0.180286f, -0.880321f, 1.010140f, -1.045019f, -0.020080f, 0.384399f, -1.645020f, -0.422868f, 3.202615f, 1.900042f, -0.588829f, -1.161514f, 0.854993f, -3.150161f, 1.229013f, -0.231487f, 0.210034f,\n-0.162108f, 0.083164f, -0.141065f, 0.071319f, -0.013426f, -0.157803f, 0.016289f, -0.006683f, 0.071628f, 0.222173f, -0.226261f, 0.223329f, -0.039872f, -0.177466f, -0.212196f, 0.103225f, -0.027094f, 0.097620f, 0.837501f, 1.526435f, 0.389245f,\n0.762602f, -1.773677f, 0.901766f, 0.006951f, -0.752922f, -0.664210f, -0.290406f, -0.791320f, -2.992069f, -6.094047f, -0.614840f, -0.529963f, -0.146730f, 0.225384f, 0.968909f, -0.361733f, 3.108127f, -0.231604f, -0.013451f, -0.200010f, 0.210446f,\n0.122481f, -0.251846f, -0.161671f, -0.141318f, -0.087464f, 0.012722f, 0.210376f, 0.051582f, -0.084380f, 0.048616f, 0.251909f, 0.059813f, 0.009555f, 0.072539f, 0.063879f, 0.047277f, 0.079884f, -0.048160f, -0.109641f, -0.244513f, -0.161881f, 0.194767f,\n-0.254853f, 0.152454f, -0.183503f, 0.215654f, -9.182094f, -8.326635f, 1.823247f, -2.469782f, 1.586532f, -5.111103f, -0.560375f, -4.411130f, 3.917098f, 1.390118f, -8.706013f, -5.285527f, -0.981863f, -2.457423f, 3.531057f, -3.503635f, 0.049301f,\n-3.943882f, 2.904227f, 2.007026f, -0.161105f, 0.073860f, -0.243452f, -0.018434f, 0.127820f, 0.088417f, -0.188545f, -0.009093f, 0.065997f, 0.078477f, 0.520892f, 1.775638f, -4.889804f, -2.039557f, 1.292920f, 1.689069f, 0.807174f, -3.684866f, 0.241035f,\n1.338463f, -0.052828f, -0.228867f, -0.209500f, -0.103845f, -0.220540f, 0.099207f, 0.233574f, -0.236191f, -0.243106f, -0.033014f, 0.812292f, 1.174574f, 0.706137f, 1.223500f, -1.888971f, -0.330829f, -1.049980f, -0.555601f, -0.904660f, 1.207527f,\n0.084722f, 0.013920f, -0.058199f, 0.159147f, 0.049049f, -0.194031f, 0.020604f, 0.007205f, 0.119985f, -0.113327f, 0.256631f, -0.177733f, 0.131227f, -0.236483f, -0.060902f, -0.166460f, -0.071201f, -0.204042f, 0.213920f, -0.071954f, 0.078648f,\n0.250683f, -0.177121f, -0.084381f, 0.156645f, 0.017098f, -0.249259f, -0.102388f, -0.040517f, -0.048875f, -8.303774f, -3.622553f, -1.463740f, 1.285590f, -7.258330f, 1.969501f, 2.111588f, -16.723055f, 1.869975f, 1.688206f, 0.200887f, -0.113476f,\n0.010434f, 0.087776f, 0.027554f, 0.170507f, 0.162494f, -0.013715f, 0.256295f, -0.214305f, -9.539000f, -5.413673f, -4.321090f, 0.897134f, -0.328475f, 0.489940f, 0.972745f, -9.124123f, 0.321578f, 2.286171f, 0.126754f, 0.169733f, -0.002473f, -0.083655f,\n-0.170499f, 0.067872f, -0.225047f, -0.173138f, 0.080968f, -0.135631f, 0.115883f, -0.202853f, 0.247769f, 0.155964f, -0.194910f, 0.062492f, 0.161738f, 0.237758f, -0.141454f, 0.157030f};\n            Layer1B = new float[] { 0.825892f, 0.613250f, -2.187903f, 1.074679f, 0.004663f, -0.595076f, 0.245288f, 0.675730f, 0.209541f, -0.257801f };\n        }\n    }\n}\n"
  },
  {
    "path": "sources/vision/MLClassifierTriadDigit.cs",
    "content": "﻿using MgAl2O4.Utils;\n\nnamespace FFTriadBuddy\n{\n    public class MLClassifierTriadDigit\n    {\n        private float[] Layer0W = null;\n        private float[] Layer0B = null;\n        private float[] Layer1W = null;\n        private float[] Layer1B = null;\n\n        public int Calculate(float[] input, out float BestPct)\n        {\n            float[] Layer0Out = new float[Layer0B.Length];\n            MLUtils.CalcNetworkLayer(input, Layer0Out, Layer0W, Layer0B);\n            MLUtils.ApplyRelu(Layer0Out);\n\n            float[] Layer1Out = new float[Layer1B.Length];\n            MLUtils.CalcNetworkLayer(Layer0Out, Layer1Out, Layer1W, Layer1B);\n            MLUtils.ApplySoftmax(Layer1Out);\n\n            return MLUtils.PickHighestProbability(Layer1Out, out BestPct);\n        }\n\n        public void InitializeModel()\n        {\n            // AUTO GENERATED, see: ml/PatternMatch/ for details\n            Layer0W = new float[]{0.271546f, -0.205081f, -0.651176f, -0.261082f, -0.383329f, -0.078573f, -0.318611f, -0.580253f, -0.010273f, -0.797560f, -1.847914f, 0.324567f, 1.012823f, 0.800718f, -0.327897f, -0.492692f, -0.002565f, -0.118536f, -0.468724f,\n0.280153f, 1.182788f, 1.317530f, 0.332012f, -0.696705f, -0.233519f, 0.111471f, 0.081786f, 0.469512f, 0.214254f, 0.389200f, 0.074101f, -0.210050f, 0.690561f, -0.103074f, -0.798476f, 0.093215f, 0.492949f, 0.094889f, -0.051687f, -0.406276f, 0.749405f,\n0.065477f, -0.153692f, 0.801971f, -0.120357f, -0.231973f, -0.801188f, 0.182893f, 0.519780f, 0.425267f, 0.476315f, -0.009347f, 0.130495f, -2.551004f, -0.409618f, -0.241282f, 0.073374f, -1.105936f, 0.576733f, 0.406369f, -0.837336f, 0.143996f,\n0.716095f, 0.510630f, -0.146934f, 0.617297f, -1.187699f, -0.760222f, -0.818938f, -0.744702f, -1.051053f, -1.296377f, 1.038678f, -1.683531f, -0.185057f, 0.050346f, 0.149905f, -0.807990f, 0.133908f, -1.093074f, -0.173351f, -1.021442f, -0.573601f,\n0.732847f, -0.307244f, 1.191144f, 0.033877f, 1.025724f, -1.653224f, -1.251046f, 1.105925f, 1.130046f, -0.863230f, -0.933529f, 0.844586f, 0.000138f, 0.386531f, 0.724121f, -2.045587f, -2.468557f, -0.735529f, -0.175709f, -0.095613f, 1.467504f,\n1.369352f, 0.155247f, -0.575578f, -0.119469f, 0.631953f, 0.174656f, -0.710072f, -0.355091f, 0.520958f, 0.282135f, 1.517227f, 0.474407f, 0.621948f, -0.857578f, -1.169736f, -1.249021f, -1.475948f, -1.542416f, 1.406823f, 0.370852f, -1.617293f,\n-1.010209f, 0.655005f, -0.018751f, -0.577111f, 0.380469f, -1.117330f, -0.605397f, 0.097021f, -0.538094f, -0.189161f, -0.951056f, 0.241956f, -2.322987f, -0.668585f, -0.343824f, 0.319533f, 0.175736f, -0.013798f, -1.177638f, 0.765871f, -0.077799f,\n-0.228830f, 0.025923f, -0.889169f, 0.398856f, 0.053321f, 0.384943f, -0.243018f, -0.958108f, -0.133106f, 0.162244f, -0.187324f, -0.891595f, 1.062646f, -0.695027f, -0.730241f, 0.346924f, -0.935136f, -1.192364f, -0.820551f, -0.616945f, -0.006884f,\n1.768268f, 0.259298f, -0.169132f, 0.598399f, -1.134489f, 0.057953f, -0.474717f, -0.307865f, 0.328431f, 0.176963f, -0.368920f, 0.076617f, -0.182313f, 1.134772f, 0.160967f, -0.588647f, 0.116401f, -0.252017f, -1.318279f, 2.314683f, 0.534291f,\n-1.887248f, -0.995131f, -0.679788f, 0.155157f, -0.275734f, -1.098808f, 0.525792f, 0.672002f, 1.128391f, 0.000172f, 0.813298f, -0.406030f, -0.663606f, -2.127263f, -1.563887f, -0.669477f, -0.224666f, 0.597803f, 0.070979f, -0.283940f, 1.122621f,\n0.570371f, 0.234627f, -0.481241f, -0.862284f, -0.361209f, 0.094664f, -0.294406f, -0.132990f, 0.269469f, -0.628075f, -0.542272f, 0.216095f, -0.311202f, 0.788993f, -1.016705f, -1.203795f, 0.201438f, 0.508126f, 0.625639f, -1.200311f, -0.132036f,\n0.003114f, 0.869865f, -0.355348f, 0.080775f, 0.754567f, -1.284700f, -0.272696f, -0.546975f, -0.409584f, 0.923353f, -0.803034f, -0.527560f, -0.079193f, -0.588996f, 0.756177f, 0.111457f, -0.232664f, 1.436145f, 0.393745f, -0.579094f, 1.560041f,\n0.977584f, -1.376689f, -0.327748f, -0.840224f, 0.343236f, -0.766295f, -1.847912f, 0.723464f, 1.281596f, 0.580530f, -0.046700f, 0.923720f, 0.213200f, -0.655170f, -1.533134f, -1.533579f, -0.510443f, -0.712194f, 0.319991f, -0.351740f, 0.391897f,\n0.961203f, -0.064601f, 0.173083f, -0.290702f, -1.306525f, 0.244206f, -0.006299f, 0.445867f, -0.668679f, 0.971515f, -1.001165f, -0.764805f, 0.002271f, -0.217254f, 0.027898f, -0.061139f, -1.120501f, 0.484531f, 0.559994f, 0.713600f, -1.847836f,\n0.075235f, -0.662206f, 0.833714f, -0.307238f, 0.094330f, 0.135360f, -0.573788f, -0.303131f, -0.066727f, -0.162289f, 0.874334f, -0.796114f, -0.198015f, -0.300568f, -0.727762f, 0.147200f, 0.058902f, -0.044126f, 1.492238f, 0.547659f, -0.241622f,\n0.417806f, 0.786114f, -0.182985f, 0.544228f, -1.087354f, 0.792427f, -1.409215f, -0.985820f, -0.196858f, 0.684575f, 0.307558f, 0.160244f, 1.029148f, 0.032594f, -0.329411f, -1.422652f, 0.218412f, -0.050991f, -1.119925f, -0.289936f, -0.679726f,\n0.356057f, 0.285453f, -0.123766f, -0.268865f, 0.347235f, -1.862547f, -0.050812f, 0.142213f, 0.297951f, -0.316191f, 0.685789f, -0.865725f, 0.044046f, -0.121074f, -0.332531f, -0.495015f, 0.275749f, -1.094628f, 0.042051f, 0.407306f, -0.596977f,\n-1.162996f, 0.059157f, -0.098443f, 0.953877f, -0.081841f, 0.136084f, 0.367731f, -0.845649f, -0.689167f, 0.239695f, 0.600596f, 0.541226f, -0.804042f, 0.170727f, -0.401800f, -0.620091f, 0.068791f, 0.240472f, 0.126322f, 1.064179f, 0.133144f, 0.033113f,\n0.321734f, 0.497469f, 0.129059f, 0.547632f, -1.106544f, 0.744280f, -1.794200f, -0.449246f, -0.527474f, 0.639556f, 0.315139f, -0.056515f, 0.947369f, -0.008823f, -0.101460f, -1.187326f, 0.840000f, -0.075185f, -1.561897f, -0.747350f, -0.439400f,\n0.244230f, -0.004641f, -0.102597f, -0.241408f, 0.677444f, -2.748073f, -0.295784f, -0.062253f, 0.038878f, -0.476057f, 0.466450f, -0.818240f, 0.315080f, 0.112677f, -0.533895f, -1.125359f, 0.191043f, -1.202716f, 0.159599f, 0.617583f, -1.101067f,\n-0.706195f, -0.226668f, -0.710698f, 0.828757f, -0.167285f, 0.491972f, 0.379149f, -1.018179f, -0.350129f, 0.258998f, 0.416729f, 0.074862f, -0.772785f, 0.529541f, -0.413959f, -0.022830f, -0.005225f, 0.827138f, -0.183618f, 0.581875f, 0.191781f,\n0.402265f, 0.202991f, 0.736840f, 0.184689f, 0.234629f, -1.150904f, 1.114066f, -2.147441f, -0.348584f, -0.037486f, 0.425342f, -0.031681f, 0.486749f, 0.252626f, 0.002107f, -0.187112f, -1.505399f, 1.459733f, -0.578970f, -1.819932f, -0.798893f,\n-0.051551f, 0.170068f, -0.142569f, -0.253743f, 0.110381f, 0.414434f, -2.742434f, -0.211558f, 0.024168f, 0.236330f, -0.385590f, 0.365228f, -1.158853f, 0.341325f, 0.131930f, -0.766245f, -1.874704f, -0.166365f, -0.541754f, -0.300999f, 0.570849f,\n-1.480958f, -0.291351f, -0.151439f, -0.224010f, 0.896703f, 0.322719f, 0.588390f, -0.091656f, -0.925246f, -0.850632f, 0.352763f, 0.635212f, -0.219908f, -1.034883f, 0.388733f, 0.185243f, -0.071930f, 0.251783f, 0.148923f, -0.037038f, 0.015486f,\n-0.096499f, 0.447285f, 0.242924f, 1.035903f, -0.322769f, -0.044634f, -0.559554f, 0.894952f, -1.699435f, 1.517281f, 0.040663f, 0.233264f, 0.096636f, -0.060400f, 0.620527f, -0.388773f, -0.622065f, -2.827325f, 1.659923f, 0.038310f, -0.974294f,\n-0.164242f, 0.589253f, -0.027740f, 0.025984f, -0.205388f, -0.297829f, 0.740500f, -1.845897f, -0.497136f, -0.050523f, 0.145278f, -0.516205f, -0.584182f, -1.233057f, 0.751597f, -0.275943f, 0.585021f, -3.015336f, -0.573613f, -0.260912f, -0.256384f,\n-0.288116f, -1.612339f, 0.007534f, -0.346917f, 0.506848f, 0.906506f, 1.030530f, 0.112957f, 0.224662f, -0.414425f, -1.202762f, 0.164552f, 0.201896f, 0.087846f, -0.973051f, 0.676471f, 0.545174f, 1.058772f, 0.142474f, 0.336968f, -0.316629f, 0.537222f,\n-0.244673f, -0.052919f, -0.372941f, 0.475764f, -0.075443f, 0.175678f, 0.666622f, 0.701639f, -0.928581f, 1.034767f, 0.546371f, 0.769761f, -1.570502f, -0.472987f, -0.526021f, -0.651031f, -0.534904f, -1.681854f, 0.701625f, -0.712093f, -0.146554f,\n0.036507f, 0.947415f, 0.720288f, 0.345912f, -0.256840f, 0.363802f, -0.073122f, -1.144349f, 0.376229f, -0.518441f, 0.631322f, -0.602142f, -0.320435f, -1.101757f, 0.342167f, -0.396502f, 0.435009f, -2.105217f, -0.279007f, -0.045872f, 0.150212f,\n-1.274971f, -0.593592f, -0.234185f, -0.133945f, -0.937917f, 0.654914f, 0.814818f, -0.473170f, -0.034200f, 0.120494f, -1.124161f, -0.210357f, 0.735658f, -0.097489f, -0.711382f, 0.030594f, 0.910432f, 0.134439f, -0.170515f, 0.134520f, -0.489893f,\n-0.562107f, -0.993875f, -0.499517f, -0.326826f, 0.152174f, -0.226580f, 0.052896f, 0.709010f, 0.327524f, -0.397307f, -0.683964f, -0.784094f, -0.509508f, 0.016892f, 0.081538f, 0.014890f, -0.246280f, 0.590540f, 0.046360f, -0.177094f, 0.186291f,\n-0.791710f, -0.105357f, -1.081548f, -0.329905f, -0.460448f, 0.270120f, 0.166411f, 0.598402f, -0.420427f, 0.624285f, 0.563410f, -0.084066f, 0.292480f, 0.546979f, -0.226578f, 0.242330f, 0.244487f, -0.004723f, -0.416252f, 0.204850f, 0.343430f,\n-0.196403f, 0.205474f, 0.927321f, -0.276923f, 0.158020f, -0.403249f, 0.857595f, -0.038645f, 0.544162f, 0.098822f, 0.759738f, -0.159182f, 0.204759f, -0.813977f, -1.219917f, -0.533678f, 0.032103f, -0.198772f, -0.475109f, 0.659517f, -1.364480f,\n0.012131f, -0.585195f, 0.326440f, 0.000411f, 0.744465f, 0.834389f, 0.184901f, -0.290312f, -0.403694f, 0.000545f, 1.137512f, -0.051188f, -1.096329f, -0.018010f, 1.509732f, -0.136942f, 0.636378f, 0.094369f, 0.609250f, -0.976065f, -0.214377f, 1.280357f,\n-0.157457f, 0.413722f, -1.044858f, -1.094216f, 1.653567f, 0.738696f, -0.595899f, 0.450620f, -1.710434f, -0.896884f, -0.362627f, 1.192231f, 0.245344f, 0.589161f, 0.263135f, 0.338581f, 0.405928f, 0.139485f, 0.316047f, -1.569250f, -0.411457f, 0.219950f,\n-0.537089f, 1.562857f, -0.976142f, -0.252016f, 0.548806f, 1.326845f, 0.310098f, 0.049564f, 0.448897f, -0.184886f, 0.265448f, 0.123436f, -1.856541f, 0.620315f, -0.217809f, 0.046365f, -0.591535f, 0.168155f, 0.336612f, -0.043483f, -0.439969f, 1.256147f,\n0.549331f, -1.208474f, 1.387088f, 0.233168f, -1.008185f, 0.686659f, -0.569932f, 0.953622f, 0.000868f, -0.215384f, -0.011173f, -0.124582f, 1.822997f, 0.666014f, 1.297197f, 0.750285f, -1.062939f, -1.864323f, -0.670052f, 0.466926f, -0.103658f,\n1.842523f, -0.902326f, -0.789354f, 1.230255f, 1.794520f, 0.020401f, 0.084131f, -1.562437f, -0.377575f, 0.848067f, -0.768733f, 1.162321f, 1.189686f, -1.019511f, -0.717100f, 1.095528f, 0.923588f, -0.058389f, -1.712370f, 0.000472f, -1.025993f,\n0.662752f, 2.403830f, 0.469162f, 0.477202f, 0.724072f, 0.493661f, 0.091073f, 1.253016f, 1.509460f, -0.308869f, -0.545687f, -0.500568f, -1.088738f, 0.516751f, -1.332285f, -0.694817f, -0.215663f, -0.307261f, 1.555278f, -0.380643f, 0.372286f, 1.589256f,\n1.476271f, -0.488084f, 2.259937f, -0.130166f, -1.605889f, 0.666976f, -0.104815f, -0.540231f, -0.208651f, -0.486596f, 1.213979f, 0.700048f, 1.237770f, 0.743596f, 0.527791f, 1.068498f, -1.254946f, -2.147520f, -1.447119f, -0.210019f, 0.237998f,\n1.898551f, -0.449325f, 0.327777f, 0.641703f, 1.174889f, 0.432344f, -0.997057f, -1.059322f, 0.360066f, 0.385893f, 0.158391f, 0.714197f, 1.158036f, -1.345855f, -1.015610f, 1.010454f, 0.823474f, -0.610061f, -1.151725f, -0.033620f, -0.551850f, 0.572347f,\n2.339703f, 0.156216f, 0.751296f, -0.037707f, 0.038263f, 0.238852f, 0.610463f, 0.587804f, 0.323833f, -1.009658f, -0.241112f, -0.463909f, 0.281523f, -0.853296f, -0.772058f, 0.813488f, -1.063994f, 0.906514f, -1.403501f, 0.506150f, 1.104217f, 1.157378f,\n-0.595170f, 1.396184f, 0.358103f, -1.379263f, 0.509877f, 0.580108f, -0.264008f, 0.583135f, -0.442082f, -0.961304f, 0.632493f, -0.553891f, -0.330510f, 0.191023f, 0.549474f, -1.019051f, -2.010425f, -1.514304f, 0.570731f, 1.726448f, 0.930546f,\n-0.159049f, 0.479772f, 0.946268f, -0.012739f, -0.484212f, -0.648363f, -1.499862f, 0.303651f, -0.732383f, 0.985595f, -0.571217f, 0.384268f, -0.635522f, 0.380269f, -0.242018f, -0.199322f, 0.078032f, -0.075980f, 0.500874f, -0.051482f, -0.358809f,\n1.225762f, -0.790872f, -0.092526f, -0.024254f, 1.159581f, 0.800608f, -0.599806f, -0.155794f, 1.096889f, -0.487759f, 0.254775f, -0.644212f, 1.327325f, 0.903603f, 0.273520f, 0.538684f, -0.923151f, -0.208738f, -0.517755f, -0.585145f, 1.006929f,\n-0.008504f, -1.121011f, 0.268768f, -0.522298f, 0.235880f, 1.172698f, 0.500305f, 0.890062f, 0.155154f, 0.279818f, -1.378979f, 0.372908f, -0.504060f, -0.704794f, 0.620145f, 0.319017f, -0.552815f, -2.621045f, -0.434735f, 0.639051f, 1.353550f, 0.994901f,\n0.235984f, 0.463365f, 0.718823f, -0.199296f, -0.420587f, -0.159833f, -1.810447f, -0.390144f, -0.762312f, 0.653485f, -0.414015f, -0.053301f, -0.387199f, 0.877867f, -0.460296f, 0.318483f, -0.050850f, 0.091415f, 0.325990f, -0.329265f, -0.479100f,\n0.390314f, -0.461764f, -0.378636f, 0.089277f, 1.202160f, 0.409784f, -0.422649f, 0.397622f, 0.533058f, -0.699039f, 0.023734f, -0.390929f, 0.797597f, 1.076748f, 0.678585f, 0.677856f, 0.384035f, -0.142939f, 0.577150f, -0.435540f, 0.892677f, -0.438798f,\n-1.222140f, 0.265574f, -1.000681f, 0.205222f, 0.913199f, 0.857479f, 0.531263f, -0.428997f, 0.424318f, -1.439214f, 0.538927f, -0.091856f, -0.390551f, 0.414231f, 0.035927f, 0.614627f, -2.489101f, -0.056738f, 0.562764f, 0.345249f, 0.327323f, -0.145211f,\n-0.006105f, 0.947551f, -0.445929f, -0.716199f, 0.530474f, -1.474340f, -0.340798f, -0.562258f, 0.198608f, -0.081096f, -0.126486f, 0.217686f, 0.981943f, 0.235399f, 0.292842f, -0.657649f, -0.475989f, 0.076079f, -0.172550f, -0.399540f, 0.962815f,\n0.118590f, -0.303479f, 0.516954f, 1.264405f, 0.838972f, 0.183468f, 0.606256f, 0.560349f, -0.132066f, 0.269519f, -0.832959f, 0.562528f, 0.294961f, 0.501272f, 0.396026f, 0.443470f, 0.449355f, 0.035889f, -0.240914f, 0.698302f, -0.084471f, -1.399863f,\n0.591033f, 0.235098f, -0.460156f, 0.681082f, 0.147581f, 0.755589f, 0.051054f, -0.140432f, -0.911976f, 0.426613f, 0.009000f, 0.050071f, 0.240558f, 0.310599f, 0.280150f, -2.520090f, 0.292173f, 0.302160f, 0.317536f, 0.418046f, -0.195298f, 0.013372f,\n0.954635f, -0.011029f, -0.803800f, 0.327379f, -1.447638f, 0.137594f, -0.085513f, 0.361195f, 0.061362f, 0.520621f, -0.133346f, 0.309367f, 0.619944f, 0.213399f, -0.237856f, -0.263293f, 0.292451f, -0.211715f, -0.283243f, 1.193253f, -0.117560f,\n0.046471f, 0.087609f, 1.192473f, 0.588462f, 0.206822f, 0.572854f, 0.677350f, -0.439894f, -0.020060f, -1.065979f, 0.144890f, 0.463748f, 0.322143f, 0.347175f, 0.047113f, 0.566816f, -0.291762f, -0.114610f, 0.611431f, 0.122017f, -1.063513f, 0.994286f,\n0.682294f, -0.586794f, 0.337326f, -0.163184f, 0.596636f, -0.044800f, -0.056475f, -0.069928f, 0.059304f, 0.402646f, 0.388517f, -0.129523f, 0.323974f, 0.124245f, -2.941917f, -0.379790f, 0.274305f, 0.292435f, 0.086309f, -0.086751f, 0.182888f, 0.507367f,\n0.042502f, -0.355498f, 0.271068f, -1.689586f, 0.725666f, 0.318761f, 0.702896f, 0.243194f, 0.642541f, -0.197239f, 0.001944f, 0.324659f, -0.368324f, -0.231238f, 0.311186f, 0.513053f, 0.336464f, -0.276865f, 0.762090f, 0.470812f, 0.031776f, 0.070829f,\n1.309171f, 0.982902f, 0.351432f, -0.110788f, 0.746506f, -0.533787f, 0.491981f, -0.317773f, 0.196116f, 0.293308f, 0.382926f, 0.331057f, -0.188482f, 0.708737f, -0.905295f, -0.098597f, 0.145082f, 0.528701f, 0.178465f, 1.202898f, 0.385136f, -0.418132f,\n0.362169f, -0.217885f, 0.358555f, -0.174417f, -0.732179f, 0.691044f, 0.308986f, -0.324570f, 0.206976f, 0.298853f, 0.121950f, -0.552244f, -3.175802f, -1.482228f, 0.130799f, 0.378161f, 0.320276f, -0.552584f, 0.351573f, 0.034695f, 0.034828f, 0.125876f,\n0.402935f, -0.901307f, 0.423723f, 0.114233f, 0.627519f, -0.007586f, 0.558478f, -0.753574f, -0.563465f, 0.046695f, -0.487728f, 0.277122f, 0.496172f, 0.837692f, -0.056315f, 0.552212f, 1.366562f, 0.140749f, 0.264816f, -0.703164f, 1.165995f, -0.354386f,\n0.306618f, 0.282449f, 0.897714f, -1.132159f, 0.209014f, 1.091977f, 0.252596f, 0.228543f, -0.085201f, -0.416939f, -0.475867f, 0.515481f, 0.222353f, 0.143559f, 0.054381f, 0.265858f, 0.068483f, 0.930836f, -0.145754f, -0.421934f, 0.537115f, -0.850170f,\n0.044494f, -0.444060f, -0.518756f, -0.105020f, -0.032185f, 0.185592f, 0.010921f, 0.138447f, 0.667162f, 0.017785f, 0.112906f, -0.163901f, 0.857834f, 0.226934f, -0.182183f, -0.965383f, -0.160586f, -0.254949f, 0.084299f, -0.205829f, 0.747439f,\n-1.215715f, 0.503538f, 0.109204f, -0.352118f, 0.220583f, 0.973624f, -0.575094f, 0.329226f, 0.299400f, 0.065952f, 0.435918f, 0.180011f, 0.145723f, -0.385518f, -0.283930f, 1.054251f, 0.089540f, 0.100618f, 0.163851f, -0.006075f, -0.219600f, 0.491149f,\n0.087048f, -0.037310f, -0.068693f, 0.288037f, -0.474095f, -0.652663f, -1.204483f, 0.351342f, -1.589776f, -0.570745f, 0.138769f, -0.879687f, 0.106654f, -0.088149f, 0.387197f, -0.073765f, -0.524428f, 0.013859f, 0.182398f, 0.691031f, -0.223602f,\n-0.216779f, 0.274356f, -1.140053f, 1.573989f, 0.639203f, 1.102058f, 0.058501f, 0.060505f, 1.231911f, -0.040164f, 0.889294f, -2.378090f, 1.030160f, 0.650604f, 0.609302f, -0.380905f, 0.251482f, 1.687474f, 0.920183f, 0.086727f, -0.281668f, -1.019290f,\n-0.357308f, -0.309539f, 0.302754f, 0.385897f, 1.393777f, -0.851252f, 0.278840f, 0.593525f, 0.312838f, -0.701097f, -0.864843f, -1.286733f, -1.999694f, 0.335218f, 2.566610f, -1.061331f, -0.335142f, -0.033232f, 0.550938f, -0.128937f, -0.145590f,\n0.244471f, -0.856354f, 0.318351f, 0.460732f, 0.639008f, 1.214880f, -0.489789f, -0.348573f, -0.743061f, -1.562240f, -0.018048f, -1.072031f, 0.107773f, 1.031943f, 1.117074f, -0.571925f, -0.100646f, 0.097859f, 0.001616f, 1.756195f, -0.385935f,\n1.274958f, -0.057652f, -0.513739f, 2.054711f, -0.243075f, 1.599605f, 0.966183f, 0.481217f, 0.912834f, -1.578921f, 0.904642f, -1.768747f, -0.046397f, -0.132635f, 1.789948f, 0.110071f, -0.065060f, 1.031520f, 1.752890f, 0.830716f, -0.915994f,\n-0.324652f, 0.668080f, 0.706571f, -1.012645f, 1.171827f, 1.127625f, -1.364111f, -0.777653f, 0.886902f, 0.821729f, -2.067158f, -1.375489f, -0.977519f, -1.806147f, 1.424574f, 2.124995f, 1.043514f, 0.840309f, 0.075667f, -0.802017f, 0.218572f, 0.482723f,\n0.841032f, -0.834674f, -1.342217f, -0.290402f, 0.468695f, 0.944204f, -0.955598f, -1.014006f, 0.448802f, -1.596130f, 1.116160f, -1.095569f, 0.842307f, 0.934892f, 1.336579f, 0.407652f, 0.401492f, -0.080534f, -1.177780f, 0.703049f, 0.721069f,\n-0.059691f, -0.176662f, -0.746988f, 2.235966f, -0.178955f, 0.129493f, 0.630090f, 0.331257f, 1.379975f, -1.870544f, 0.080971f, -1.810805f, -0.113859f, -0.243398f, 1.555601f, 0.407849f, -0.010792f, 0.308373f, 0.445665f, 0.686629f, -0.657526f,\n-0.077856f, 0.465153f, 0.497724f, -0.029647f, 0.669696f, 1.374704f, -1.826554f, -0.498121f, 0.900941f, 1.064931f, -1.145070f, -0.639176f, -1.268221f, -1.218532f, 1.079730f, 2.156567f, 0.559116f, 0.672538f, -0.247033f, -1.461897f, -0.170670f,\n0.359279f, 0.006221f, -0.393584f, -1.388520f, 0.008994f, 0.974644f, 0.829895f, -0.296321f, -0.698868f, 0.361974f, -1.988813f, 0.194032f, -1.608216f, 0.450227f, 0.718681f, 0.971945f, 0.170124f, -0.482304f, 0.031583f, -0.489998f, 0.974142f, 1.399532f,\n0.047982f, 1.290832f, 0.724920f, -0.649101f, -0.854547f, -1.567729f, -0.824220f, 1.068590f, -0.072272f, -0.763480f, -0.264656f, -0.896586f, 0.843725f, 1.503333f, 1.013205f, -0.140515f, -0.193984f, 1.243008f, -0.542564f, -0.930131f, -0.077897f,\n-1.035147f, -0.050102f, -0.934255f, 0.672017f, -1.143854f, -0.006422f, -0.517107f, 0.896917f, -0.854749f, 0.686919f, 0.799933f, -0.316448f, -0.444607f, -0.233620f, 0.145397f, 0.447314f, -1.025200f, -0.330569f, 0.248917f, -0.873577f, 0.447272f,\n-0.743560f, 0.427043f, -0.789756f, -0.675068f, -0.097774f, -0.308162f, 2.201529f, 1.505340f, 0.486947f, 0.639959f, 1.026942f, -0.471785f, 1.205883f, -0.731519f, 1.718997f, -0.286489f, -1.026481f, -1.383783f, -1.963131f, 0.358147f, 1.720623f,\n1.701618f, 0.723932f, 0.595173f, 1.434918f, -0.843922f, -1.025250f, -1.318680f, -0.806095f, 0.717633f, -0.162178f, -0.161172f, -0.563191f, 0.624246f, -0.040595f, 1.053340f, 1.333671f, 0.811980f, -0.145380f, 1.022437f, -0.208365f, -0.619549f,\n-0.419989f, -1.072381f, -0.129190f, -0.810320f, 1.307069f, -0.815745f, -0.940948f, 0.141662f, 0.979821f, -1.015062f, 0.592536f, 0.666201f, -0.644503f, -1.159542f, -0.086162f, -0.828323f, 0.089336f, -0.350590f, 0.007106f, 0.920619f, -0.806929f,\n1.066087f, -0.585822f, 0.335109f, -1.541357f, -0.111976f, -0.179066f, -0.127982f, 1.745349f, 1.111958f, 0.030798f, 1.202086f, 1.141323f, -0.480609f, 1.033577f, -0.555006f, 1.280502f, -1.059136f, -1.181859f, -1.350770f, -1.579383f, 0.024430f,\n1.240391f, 2.238526f, 0.345209f, 0.448424f, 1.327339f, -0.300849f, -0.997881f, -0.374041f, -0.908684f, 0.716155f, -0.312830f, 1.273228f, -0.943261f, 1.558265f, 0.412152f, 0.271041f, 1.373434f, -0.082347f, -0.577430f, 1.791670f, -0.247071f,\n-1.327344f, -0.004921f, -1.221262f, -0.095983f, -0.644805f, 1.038255f, 0.004152f, -0.671747f, 0.797998f, 0.898476f, -0.074631f, 0.990613f, 0.325171f, -0.983323f, -1.071473f, 0.250382f, -0.613263f, 1.871123f, 0.107468f, 0.040171f, 0.897981f,\n-0.146549f, 1.438284f, 0.012998f, 0.566819f, -1.296841f, 0.258975f, -0.392766f, -0.919577f, 1.610630f, 0.619254f, -0.077577f, 1.023741f, 1.006328f, -0.066311f, 0.313277f, -0.853576f, 1.286994f, -0.916878f, -0.909231f, -0.314500f, -0.496665f,\n-0.309404f, 0.392404f, 1.076766f, 0.489867f, 0.522738f, 0.225542f, 0.428607f, -0.756333f, 0.521881f, 0.118482f, 0.822006f, -0.278025f, 0.709769f, -0.752710f, 1.366197f, -0.549667f, -0.893745f, 1.247522f, -0.550169f, -0.286168f, 0.738134f, 0.366566f,\n-0.517557f, 0.138171f, -1.029223f, 0.328503f, 0.502361f, 1.356095f, 0.421427f, 0.446322f, -0.026852f, -0.539472f, 0.543264f, 0.215817f, 0.085010f, -0.877290f, -0.599466f, -0.142533f, -0.511319f, 2.281082f, 0.078020f, 0.271206f, -0.170727f, 0.493260f,\n0.202596f, 0.487744f, 0.750330f, -0.835663f, 0.077266f, -0.392052f, -0.807193f, 0.513209f, -0.020052f, -0.945375f, 0.139504f, 0.337856f, 0.920073f, 0.238462f, -0.072210f, 0.622622f, 0.046040f, 0.105198f, 1.139147f, 0.083859f, -0.771195f, -0.371312f,\n-0.345261f, -0.011304f, 0.129477f, -0.411842f, 0.813824f, -1.203380f, 1.274788f, 0.687376f, 1.155630f, 0.072149f, 0.057921f, 0.203743f, 0.640418f, -0.231889f, -1.720653f, 0.823961f, -0.961158f, -1.159179f, -0.220101f, 0.814278f, 0.387357f, 0.945227f,\n-0.604224f, 0.024903f, 0.962457f, 0.823381f, 0.880186f, 0.826199f, -1.250524f, -0.809577f, 0.896145f, -0.267258f, 0.667073f, -0.439483f, -0.663153f, 0.220929f, 0.441682f, 1.264349f, 1.097726f, 0.626813f, -0.009558f, 0.697702f, -0.747394f, 1.413977f,\n0.936015f, -1.463968f, -0.275294f, -0.500186f, -0.161584f, -0.483962f, -0.885760f, -1.005593f, -1.180488f, -0.303824f, 1.574514f, 0.388721f, 0.837296f, 0.786709f, 0.894538f, 1.491776f, 2.761055f, 0.491345f, -0.797470f, -0.506998f, -1.533824f,\n-0.745832f, -0.848677f, -0.827822f, 1.000729f, -1.310350f, 0.835606f, 0.706313f, 2.363956f, -0.741505f, -0.442785f, 0.025073f, -0.280407f, -0.840044f, -1.106879f, 1.159366f, -1.403566f, -1.508731f, -0.550613f, 0.383788f, 0.003266f, 1.231542f,\n-0.151238f, 0.344210f, 1.117956f, 1.221389f, 0.173888f, 0.636617f, -1.441671f, -1.640358f, -0.037248f, -0.213392f, 0.270474f, -0.647906f, 0.245348f, 0.206120f, 1.484892f, 1.814146f, 0.324125f, 0.634108f, -0.634752f, 0.994766f, -1.859792f, 1.136132f,\n1.353520f, -1.333471f, -0.731962f, -1.117626f, 0.692115f, -0.293620f, -1.331375f, -1.583966f, -0.798469f, 0.711536f, 1.589342f, 2.139678f, 1.064787f, 1.200796f, 1.119291f, 1.939007f, 3.438847f, -0.399915f, -1.039192f, -0.693384f, -2.229959f,\n-1.175671f, 0.348004f, -0.200417f, -0.008719f, 0.183106f, 0.014217f, -0.253236f, -0.063439f, 0.433617f, 0.497625f, 0.184962f, -0.259558f, 0.854717f, 0.509709f, -0.376525f, -0.821326f, 0.251834f, 0.862322f, 0.967608f, -0.491565f, -0.458184f,\n-0.090327f, 0.526728f, -0.568005f, -0.332828f, 0.916249f, 0.743033f, 0.152809f, 0.603107f, 0.848055f, 0.711125f, 0.164160f, -0.377989f, -0.259662f, -0.724769f, 0.880075f, 2.742141f, -0.337986f, -0.274545f, 0.746122f, 0.178844f, -0.326505f, 0.348320f,\n-0.044197f, 0.056082f, 0.541302f, 0.367228f, -1.613336f, 1.137177f, -1.426608f, -0.036617f, -1.493104f, -1.025090f, -1.740113f, -1.674055f, -0.632858f, 0.692826f, 0.344202f, -0.720328f, -3.542557f, 1.155197f, -0.073103f, 0.718532f, -0.129426f,\n1.305057f, 0.948170f, -0.326212f, 0.699014f, 0.711583f, 0.099444f, 0.024291f, -0.077276f, 0.565732f, -0.094968f, 1.018572f, -0.352030f, 1.277305f, -0.454435f, 0.420971f, 0.115202f, 0.312413f, 1.503133f, 1.315275f, 0.010509f, -0.735792f, 1.495587f,\n0.058786f, -0.738018f, -0.683165f, 1.333669f, 1.005705f, -0.643909f, 0.501936f, 0.879282f, 1.113949f, -1.471948f, -1.201704f, -1.325554f, -2.519678f, 1.315514f, 1.696924f, -0.010689f, -0.291412f, 0.152973f, -0.230966f, 0.742211f, -0.165852f,\n-0.230469f, -0.739608f, 0.006404f, 0.650410f, -0.587318f, 1.094612f, -0.859061f, -0.545519f, 0.102245f, -1.805834f, -0.856547f, -2.225173f, -0.096775f, 0.904607f, 1.172465f, -0.168561f, -3.429005f, 1.101484f, 0.296448f, 1.098788f, 0.165645f,\n1.809438f, 1.136383f, -0.192666f, 1.627203f, 0.967829f, 0.258953f, 0.058989f, -1.331380f, 0.657689f, -0.184877f, 2.064977f, -1.119079f, 0.401460f, -0.816985f, -0.115944f, 1.575432f, 0.266588f, 1.330013f, 0.753806f, 0.999395f, -1.927078f, 2.588152f,\n0.717075f, -0.255575f, -0.752051f, 1.460695f, 0.555108f, -0.275050f, 0.147362f, 1.169885f, 0.461055f, -2.130569f, -1.046857f, -1.059341f, -1.602651f, 0.954527f, 0.574566f, 1.869376f, 0.246796f, -0.290948f, 0.102291f, 1.343703f, -0.618839f,\n-1.361580f, -0.078486f, -1.192458f, 0.617255f, 0.007418f, 0.549200f, 0.051613f, -0.292795f, 1.466128f, -1.158222f, -1.891160f, -2.479371f, -0.049735f, -0.578037f, 0.481974f, 0.696634f, -4.468387f, 1.504452f, 0.641938f, 0.167667f, 1.416460f,\n1.795936f, 0.793445f, -0.934650f, 2.399026f, 1.411211f, -0.670044f, 0.421070f, -2.283827f, 1.649251f, -0.603268f, 0.761579f, -2.580004f, 0.398130f, -0.661755f, -0.125388f, 2.043356f, 0.948841f, 0.286150f, 0.794935f, 1.047890f, -1.756001f, 2.860859f,\n-0.287356f, -0.097949f, -0.417356f, 1.629452f, 1.178954f, -0.863924f, 0.193067f, 1.431152f, 0.033980f, -0.594804f, -0.609901f, -1.441074f, -1.001083f, 0.821374f, 1.654561f, 1.619894f, -0.112691f, -0.886691f, -0.770844f, 0.715898f, -0.903805f,\n-1.348066f, -0.078557f, -0.697744f, 0.620278f, 0.673081f, -0.790130f, 0.539835f, -0.230041f, 0.500876f, -2.817986f, -1.132215f, -3.032368f, 0.206135f, -1.135617f, 0.894232f, -0.056452f, -2.861495f, 2.466116f, 0.917763f, 0.749092f, 1.405741f,\n1.221077f, 0.960363f, 1.389066f, -0.823486f, 0.408835f, -1.418376f, -0.356819f, -0.156908f, -0.080380f, -1.244339f, 0.071866f, -0.236365f, -0.296223f, 1.261007f, 1.403679f, 0.535660f, -0.290284f, 0.394099f, 0.632666f, 0.198315f, -1.295891f,\n1.702760f, 0.394597f, -0.374548f, -0.752725f, 0.540213f, 0.314180f, -0.623937f, 0.267935f, 0.342463f, 1.471155f, 1.148692f, -0.946229f, -0.012829f, -0.490147f, -0.274340f, -0.668402f, 0.580413f, 0.016829f, 1.046243f, -2.926209f, 0.808637f,\n-0.502297f, 0.928831f, -0.163060f, -1.097341f, -0.930575f, -0.563598f, -0.211830f, 0.880699f, -0.285876f, 1.238832f, 0.197033f, 0.243628f, -0.580148f, -0.547670f, 0.648123f, -0.033015f, -1.151865f, 0.199927f, -0.527654f, -1.016449f, 0.457613f,\n1.841911f, -0.617600f, 1.712816f, 1.903145f, -1.232179f, 0.621525f, -2.038928f, -1.271706f, -0.089696f, -0.726942f, -0.580617f, -0.462020f, 1.422264f, -0.204600f, 1.610754f, 1.727248f, 0.552833f, -0.343047f, 2.100165f, 0.784448f, -0.671762f,\n-1.563748f, 1.504352f, 0.935269f, -1.079662f, 0.545203f, -0.093561f, -0.543154f, -0.068978f, 0.719134f, -0.305986f, 1.323418f, 0.594134f, -1.024948f, 0.021845f, -0.204671f, -1.485774f, -0.442965f, -0.530739f, 0.067684f, 1.159979f, -3.000168f,\n1.474698f, -0.905905f, 0.723852f, 0.650261f, -1.000030f, -1.132443f, -1.980425f, 0.940243f, 1.679363f, 0.131809f, 1.948199f, 0.991958f, -0.130668f, 0.273019f, -1.375425f, 1.303166f, -1.369131f, -2.345227f, -0.441439f, -1.087910f, -0.796562f,\n0.858966f, 2.477473f, 0.318854f, 1.435427f, 1.880939f, -1.697181f, -0.523382f, -0.995839f, -0.934111f, 0.342769f, -0.774560f, -0.036702f, -0.578524f, 2.430594f, 0.524383f, 0.834602f, 1.848293f, -0.884492f, -1.104446f, 2.216371f, 0.057349f,\n-2.097357f, -0.431203f, 1.288481f, 0.174456f, -0.540578f, 0.069584f, -0.024955f, -0.019129f, 0.386335f, 0.052591f, 0.077810f, 1.489988f, -0.059426f, -1.489538f, 0.440519f, 0.006256f, -1.743425f, 0.630978f, 0.110171f, 0.039936f, 1.567723f, -3.279586f,\n1.720556f, -0.034250f, 0.512634f, 0.557796f, -0.762849f, -0.986045f, -2.661812f, 0.283165f, 0.992716f, 0.502032f, 0.912724f, 1.688351f, 0.205834f, 0.494293f, -1.247295f, 1.322001f, -0.603216f, -1.355920f, 0.180429f, -0.847656f, -0.914487f, 0.664621f,\n1.798601f, -0.705383f, 0.310927f, 0.837943f, -0.606780f, -0.590630f, -0.077777f, 0.336372f, 0.809580f, -0.377804f, -0.403340f, 0.570952f, 2.085498f, -0.310089f, -0.324199f, 1.880087f, -0.513968f, -1.018188f, 0.560714f, 0.203326f, -1.279339f,\n0.939768f, 1.036914f, 0.417304f, 0.699114f, -0.078718f, 0.113252f, 0.752373f, -0.458512f, -0.574617f, 0.158101f, 0.883132f, -0.464777f, -1.230150f, 0.836538f, -0.055019f, -1.452531f, 0.128637f, 0.925714f, 0.702921f, 0.907589f, -2.791579f, 0.162311f,\n0.759475f, 0.816604f, 0.518998f, -0.979863f, -0.786481f, -1.848021f, -0.519193f, -0.133689f, -0.008137f, 0.111688f, 1.160238f, 0.922745f, 0.582315f, 0.004782f, 0.820553f, 0.211355f, -0.205923f, 0.344279f, -0.430995f, -1.338044f, 0.030550f, 0.946713f,\n-1.520553f, -0.426037f, 0.538128f, -0.291873f, -0.570452f, 1.070462f, 0.366190f, 0.894196f, -0.021719f, 0.140012f, 2.551170f, 1.685223f, 0.218940f, -1.423121f, 0.815291f, -1.201315f, -0.594721f, -0.105533f, 0.205537f, -0.400675f, 0.849374f,\n0.233434f, 0.100202f, 0.863498f, 0.158832f, 1.019772f, 0.567711f, -0.870113f, -0.292666f, 0.732097f, 0.512079f, -0.905787f, -0.764401f, 0.580267f, -0.462518f, -0.190725f, -0.440787f, 1.868272f, 0.493726f, 0.528560f, -0.452571f, -0.759020f, 1.367848f,\n0.594507f, -0.002708f, -0.829857f, -0.194819f, -0.800654f, -1.296506f, -1.697987f, 0.017415f, -0.691744f, 0.673568f, 0.751173f, 0.002247f, 0.718874f, 0.683970f, 0.421824f, 1.498090f, 0.817835f, 0.699276f, -0.107690f, -0.162333f, -0.698289f,\n-1.210587f, -1.149559f, 0.329197f, -0.354551f, -0.335211f, 1.508654f, 0.697945f, 1.429761f, -0.219986f, 0.492042f, 2.510206f, 1.428808f, 0.207827f, -1.978856f, 0.791442f, -1.471637f, -1.266456f, -0.959084f, 0.306695f, -0.197991f, 0.957967f,\n-0.209373f, -0.082448f, 0.789291f, -0.546064f, 0.800434f, 0.561352f, -1.084413f, -0.604747f, 0.695032f, 0.540437f, -0.992211f, -0.429524f, 0.270085f, -0.597042f, 1.246991f, -0.149123f, 1.370493f, 0.480934f, 0.313463f, 0.231324f, -1.335578f,\n1.117088f, 0.684584f, -0.973100f, -0.918539f, -0.645175f, -0.044094f, -1.206152f, -2.405960f, -0.566121f, -1.141374f, 1.369213f, 1.070596f, 1.257515f, 0.815863f, 0.940048f, 0.973284f, 1.736379f, 0.718524f, 0.021335f, 0.875226f, -0.543217f,\n-1.171080f, -1.171986f, -0.082191f, -1.658850f, -0.077429f, 0.377273f, -1.967595f, -0.529797f, -0.909508f, 0.383203f, 1.297072f, 0.297073f, -1.746930f, 0.536120f, 0.645741f, -0.542786f, -0.827432f, 0.722685f, 0.046865f, -0.116257f, -0.042858f,\n-0.245747f, 0.660734f, 0.279736f, -0.582923f, -0.835328f, 0.209145f, 0.995075f, 1.029957f, 0.812725f, 0.528541f, -0.773442f, 0.648417f, -0.054453f, 0.915767f, -0.556900f, 0.516002f, 1.885640f, -0.736488f, -0.253716f, -0.643316f, 0.609261f, 0.312868f,\n-0.766778f, -0.844989f, 1.449648f, 0.994616f, 0.725661f, -1.757066f, 0.539353f, 0.552391f, 0.638786f, -0.732358f, -1.846071f, -1.465297f, -2.259398f, -0.853522f, -0.386754f, -0.121265f, -1.099804f, -2.900389f, 0.835231f, 0.556598f, 1.098184f,\n-0.355076f, 2.056157f, 0.738756f, -1.083238f, 1.187536f, 1.036761f, -1.694478f, -0.088125f, -1.162134f, 0.462742f, 0.269140f, 0.895766f, -0.804539f, 0.220848f, -0.650666f, -1.386948f, 0.834944f, 0.478787f, -0.147871f, -0.089383f, 1.324620f,\n-1.164975f, 2.250782f, 0.825599f, -0.433196f, -1.404661f, 0.541257f, 0.815255f, -0.283771f, 0.936178f, 0.705460f, -0.483810f, -1.312567f, -0.832594f, -0.227640f, -2.711992f, 0.067810f, 0.155734f, 0.053075f, 0.171180f, -1.044269f, -0.844484f,\n0.997791f, -0.532618f, -0.865578f, 0.755742f, 0.798295f, 1.098216f, -0.056910f, 0.241328f, 0.112020f, -0.433113f, 1.202624f, -1.960974f, -0.856319f, -2.610872f, -0.345081f, -1.484677f, -0.238684f, -0.695585f, -2.288967f, 1.237531f, 0.787672f,\n0.691667f, 0.806994f, 1.744298f, 2.129746f, -1.083206f, 1.105169f, 1.429701f, -1.630411f, -0.103149f, -2.610175f, 0.640672f, 1.136775f, 1.694522f, -2.111067f, -0.215793f, 0.392212f, -1.908818f, 1.455937f, 0.844498f, -0.034849f, -1.162878f, 1.354553f,\n-1.580759f, 2.689424f, 0.930084f, -0.442996f, -0.426555f, 0.351450f, 0.535632f, 0.848210f, 0.918183f, 0.561386f, -1.708487f, -0.933943f, -0.012726f, -0.349511f, -1.005845f, -0.741126f, -0.446462f, 0.410084f, -0.183928f, -1.512206f, 0.622709f,\n1.275482f, -1.426264f, -2.292056f, 1.034440f, 0.562294f, 1.210042f, 0.682298f, -1.141951f, 1.649452f, 0.277142f, 1.970508f, -1.287671f, -2.191302f, -2.562559f, -0.267428f, -2.728996f, -0.285352f, -0.042881f, -2.747849f, 1.476207f, 1.191534f,\n0.105312f, 0.843997f, 1.674404f, 0.774752f, -1.668481f, 1.879857f, 1.875389f, -1.299958f, -0.218793f, -2.628052f, 0.868474f, 0.782597f, -0.771164f, -3.144690f, 0.751707f, 0.272642f, -2.126068f, 1.673073f, 1.442234f, -0.651857f, -0.267496f, 1.356884f,\n-1.866771f, 2.485784f, -0.104354f, -0.988602f, 0.051187f, 0.538643f, 0.944472f, 0.273486f, 1.146185f, 0.791471f, -2.075494f, 0.447171f, 0.580487f, -0.706085f, -0.010486f, -0.941413f, 0.457622f, 0.219291f, -0.635388f, -1.910166f, 0.649632f, 0.948635f,\n-1.517905f, -2.167688f, 0.789466f, 1.354073f, 1.764189f, 0.549619f, -1.407988f, 1.164097f, 0.538549f, 0.153892f, -2.717418f, -1.603432f, -2.947952f, -0.521386f, -2.839169f, -0.244459f, -0.717776f, -2.540302f, 2.660294f, 2.040529f, 0.962455f,\n0.870154f, 1.665998f, -0.206481f, 0.679779f, 0.311901f, 0.181899f, 0.725611f, 0.487497f, -0.660171f, 0.501397f, -0.933011f, -1.474499f, -0.344494f, -0.665897f, 0.287082f, 0.190948f, 1.272939f, 0.247024f, -2.078078f, 1.784535f, 1.915877f, -0.373385f,\n0.683188f, 0.186825f, 0.120715f, -0.994847f, 1.146018f, 0.207147f, -1.181479f, 0.154481f, 0.915166f, 0.612186f, 0.697924f, -0.795599f, -0.568124f, -1.289223f, 0.248852f, -0.981815f, 0.979566f, 0.596583f, -0.454470f, -0.342511f, -0.396001f, 0.988556f,\n1.445770f, -0.694139f, -0.972150f, -0.261671f, 0.371258f, -1.799449f, -0.923674f, -0.723983f, 0.777203f, -0.433179f, 1.161140f, -1.000960f, 0.771118f, -1.134751f, 0.008922f, -0.165918f, 0.367819f, 0.496445f, -1.636586f, -0.419208f, 0.957023f,\n-0.503330f, 1.555152f, 1.475556f, -1.332340f, 0.217359f, -0.017950f, -1.058202f, -0.088983f, -0.307767f, 0.207647f, -1.550436f, 1.556249f, 0.131347f, 1.623939f, -0.558783f, 2.007799f, 0.605048f, 0.553690f, 1.340078f, 0.322217f, -1.529465f,\n-0.319893f, 0.333455f, -1.114609f, 0.937084f, 0.364097f, -0.951241f, 0.039925f, 1.421294f, 0.092270f, 0.218805f, 1.516003f, -0.714819f, 0.026887f, -0.444016f, -0.913067f, -0.373147f, 0.045378f, -0.459112f, 0.677870f, 0.005904f, 1.347902f, -0.065786f,\n0.492035f, 0.274742f, -0.840498f, -0.469062f, -1.596422f, -0.068977f, 0.868547f, 0.228297f, 1.307590f, 0.554297f, -0.305758f, -0.458331f, -0.978832f, 0.528948f, -1.004294f, -1.576009f, -0.842953f, -0.293808f, -0.899879f, 0.109118f, 1.953249f,\n0.848196f, 0.715304f, 1.958053f, -2.618490f, -1.102642f, 0.183769f, -0.329805f, 0.687014f, -0.799240f, -0.253710f, -0.834854f, 2.745807f, 0.841321f, 1.148551f, 0.197003f, -0.743878f, -0.795503f, 0.696244f, 0.210056f, -1.592581f, 0.803758f,\n-0.152477f, -0.900948f, -0.143689f, 0.089802f, 0.332316f, -0.751153f, 0.124418f, 0.731006f, 0.215068f, 0.967557f, 1.305825f, -0.896757f, 0.828066f, -0.230524f, -0.913942f, -0.373183f, 0.942907f, -0.345132f, 1.657327f, -0.619805f, 1.059625f,\n0.754891f, 0.539843f, 0.202810f, -1.048633f, -0.202021f, -2.483502f, 0.168519f, 0.457486f, 0.546811f, -0.194286f, 1.986158f, 0.211547f, 0.939295f, -0.687047f, 0.910757f, -0.285812f, -0.708648f, 0.432014f, -0.338491f, -1.338983f, 0.177541f, 0.919858f,\n-0.816891f, -0.167253f, 0.886718f, -1.965054f, -1.108099f, -0.132865f, 0.521166f, 0.877075f, -0.346087f, -0.354582f, 1.413791f, 2.565916f, 0.376864f, 0.416978f, 0.548903f, -0.860126f, -0.884953f, -0.218779f, 0.265726f, -1.728279f, 1.455000f,\n-0.205702f, -0.194145f, 0.558887f, -0.726447f, 0.373259f, 0.065755f, -0.693770f, -0.161859f, 0.137369f, 0.629934f, 0.641251f, -1.013801f, 0.938860f, -0.670714f, -0.417743f, -0.626010f, 0.798924f, 0.376263f, 0.909460f, -0.611472f, -0.436239f,\n1.100313f, 0.568312f, -0.113097f, -1.236591f, -0.214653f, -1.804996f, -0.515327f, -0.400070f, 0.315925f, -0.425782f, 1.528791f, 0.584939f, 1.415285f, 0.424901f, 0.226287f, 0.064328f, 0.469748f, -0.369966f, -0.148388f, -1.142579f, 0.241531f,\n0.464454f, -1.061145f, -1.407126f, 0.169181f, -0.111643f, -0.716406f, 0.285468f, 0.896671f, 0.885052f, 0.168063f, -0.644624f, 3.154695f, 1.346914f, 0.227978f, -0.631701f, 0.403954f, -0.937986f, -0.506764f, -1.366330f, -0.376298f, -0.238446f,\n1.024081f, -0.083181f, 0.892268f, 0.567218f, -1.220059f, 0.220604f, 0.610499f, -1.235606f, -0.949261f, 0.337702f, 0.926439f, -0.417644f, -0.400036f, 1.055712f, -1.239325f, 0.111349f, -0.549677f, 0.889719f, 0.889740f, 0.132937f, 0.068457f, -0.755525f,\n1.139552f, 0.887581f, 0.117541f, -1.348564f, -0.000874f, -0.056523f, -0.637415f, -1.792839f, 0.156235f, -0.400110f, 0.725069f, -0.119834f, -0.041409f, 0.896865f, -0.356276f, 0.671260f, 1.419596f, -0.972573f, 0.560857f, 0.156720f, -0.175110f,\n-0.415119f, -1.078192f, -1.451764f, 0.380453f, 0.522743f, -1.049004f, 0.223569f, 0.941440f, 0.875773f, 0.007664f, -0.549773f, 3.868532f, 1.405673f, -0.073217f, -1.828333f, 0.706427f, -1.413500f, -1.146442f, -1.200960f, -0.629713f, 0.035779f,\n0.942136f, 0.235050f, 1.155260f, 1.099083f, -2.373936f, 0.463792f, 0.572791f, -1.375631f, -1.264073f, 0.623762f, 1.162018f, -2.119483f, -0.333863f, 0.812154f, -2.226829f, 0.878293f, -0.156748f, 1.486547f, 0.778559f, 0.165626f, -1.059107f, -1.207204f,\n1.347438f, 0.961574f, 0.016915f, -1.607359f, -0.403352f, 0.175448f, -0.390495f, -2.652453f, -0.439990f, -0.774840f, 0.839423f, -1.065066f, 0.211834f, 0.875107f, -0.175851f, 0.917829f, 1.639324f, -1.518580f, 0.244971f, 1.049837f, -0.407283f,\n-0.629484f, -0.777788f, -0.840080f, -1.085248f, 0.099557f, -0.595815f, -2.705098f, -0.290750f, -1.710049f, 0.476242f, 1.377587f, -1.417613f, -0.886024f, -0.410152f, -0.294011f, 0.097104f, 0.445783f, 0.897259f, -1.363918f, -0.909585f, 0.599785f,\n0.025430f, 0.526845f, 0.465055f, 0.380573f, -0.683255f, -0.727133f, 0.459277f, 1.257337f, 1.052997f, 0.487583f, -2.026940f, 0.938597f, 1.112189f, 0.578685f, 0.659792f, -1.758666f, -0.248798f, 2.210503f, 0.529084f, -1.228355f, -0.031967f, 1.674340f,\n-0.312372f, -1.473530f, 1.196137f, 0.997659f, 1.060581f, -0.921698f, -2.113776f, 0.667942f, 0.540918f, 1.242873f, -1.460257f, 1.424446f, -2.975386f, -0.386740f, -2.296741f, -0.998816f, -0.222833f, 2.641857f, 0.963585f, 1.292262f, -0.208579f,\n0.892845f, 0.412797f, -0.441876f, -1.046382f, 2.147743f, 0.206541f, -2.793091f, 0.195963f, -1.988529f, 0.481586f, 0.780989f, 0.267056f, -0.791022f, -1.299666f, -1.125279f, -1.188067f, 1.304675f, 1.118860f, -1.668509f, -0.761279f, 1.727026f,\n-1.264852f, 1.650079f, 2.107775f, 0.042371f, -0.502079f, -0.430743f, 0.728670f, 0.965453f, 0.525056f, 0.506787f, -2.279110f, -1.612939f, 1.323728f, 0.205789f, -0.690662f, -2.461222f, -0.834818f, 1.406702f, 0.729273f, -1.817175f, -1.906392f,\n1.835214f, -0.167119f, -1.971440f, 0.753955f, 1.735921f, 1.309357f, 1.346274f, -1.972088f, -0.429494f, -0.633248f, 2.283571f, -1.867630f, 1.450346f, -2.573425f, -0.180159f, -2.891182f, -0.995689f, -0.644200f, 2.628779f, 1.139799f, 0.744029f,\n0.283427f, 0.987063f, -0.277125f, -1.231743f, -1.917712f, 1.558646f, 0.259721f, -2.953927f, -0.277986f, -1.997883f, 0.603534f, 0.849366f, -1.248280f, -2.544911f, -0.187573f, 0.010331f, -1.078941f, 1.256994f, 1.149000f, -1.051623f, -1.963418f,\n1.046437f, -0.024560f, 0.947347f, 1.783977f, -0.512573f, 0.762070f, -1.368861f, 0.766967f, 1.980498f, 0.805659f, -0.415020f, -2.703599f, 1.164869f, 1.714866f, 0.140086f, 1.634865f, -2.756103f, -1.109219f, -0.066704f, 0.393122f, -2.074309f,\n-0.751126f, 1.812253f, -1.239301f, -2.461932f, 0.953094f, 2.931021f, 1.624439f, 1.765619f, -2.382359f, 0.824816f, 0.918744f, 2.003549f, -1.701048f, 0.383446f, -1.140376f, -0.418951f, -3.457283f, -0.994220f, -1.624330f, 2.993169f, -0.045716f,\n1.141823f, 0.758496f, 0.882381f, -0.034421f, -1.985161f, -2.319521f, 2.391420f, 1.113022f, -1.199167f, -0.045514f, -2.267460f, 1.247838f, -0.065876f, -2.436103f, -3.106705f, 0.285604f, -0.194474f, -1.251487f, 1.625208f, 1.409138f, -1.093639f,\n-1.310778f, 1.009777f, -0.531013f, 1.159532f, 1.023899f, -0.623651f, 1.039013f, -0.977116f, 1.079764f, 0.660376f, 0.338185f, 0.316021f, -2.166839f, 0.802719f, 1.209600f, -0.354708f, 1.663218f, -1.779654f, -0.920692f, 0.049583f, 0.066539f, -2.021969f,\n1.063708f, 1.748265f, -1.604571f, -2.208632f, 1.038699f, 2.462167f, 1.711918f, 1.344850f, -2.626008f, 0.177901f, 1.091764f, 1.648776f, -2.288991f, -0.846635f, -2.341354f, -0.238003f, -3.406403f, -0.829590f, -1.164928f, 1.868248f, 0.195702f,\n1.103821f, 1.028918f, 1.463551f, 0.235784f, -1.458084f, 1.437977f, 1.379018f, -1.436483f, 3.110124f, 1.228579f, -0.030264f, 0.321359f, -0.337354f, -2.087756f, -0.146689f, -0.744338f, -2.450837f, 0.349062f, 1.492629f, -0.562942f, -1.408426f,\n2.773650f, 2.487304f, -0.043499f, -0.189989f, -1.225567f, 1.512941f, 0.537769f, 1.305169f, -0.668232f, -0.858241f, -0.737835f, 0.811992f, 0.060974f, -1.895823f, -0.635222f, -1.965479f, -0.350284f, 1.504665f, -0.814206f, 1.713336f, 1.068641f,\n-0.270374f, 2.728398f, 0.028280f, 0.808772f, 2.061955f, -2.596357f, 0.720840f, -0.284438f, 1.925792f, -1.887846f, -1.839931f, -1.807082f, 0.878667f, 0.604215f, 1.937418f, 0.104157f, 1.816048f, -1.033480f, 0.924474f, 1.662835f, 2.309071f, 0.269585f,\n-1.679233f, -2.209085f, 0.997628f, -1.152980f, 0.897539f, 1.098467f, -0.864010f, -0.602236f, 1.543299f, -0.744505f, -0.688081f, -0.730624f, 1.450296f, -3.095692f, 1.517457f, -0.140598f, 0.078164f, -0.985671f, 1.790498f, 0.302759f, 0.838216f,\n1.469597f, 0.949493f, -1.560704f, -1.438312f, -1.174116f, -0.630829f, 2.130599f, 0.162839f, -1.714158f, 1.281007f, 1.173364f, -0.646666f, -0.740656f, 0.504708f, 0.366124f, -0.993495f, 1.243673f, -0.417247f, -0.424117f, 0.535784f, -0.493751f,\n0.590457f, 3.629569f, 1.253898f, -0.393431f, 0.204593f, -0.824687f, 1.940697f, 0.243078f, -0.219190f, -0.173339f, 0.685826f, -0.164615f, 0.686511f, 0.734750f, -0.534391f, 0.843974f, -0.244774f, -0.075274f, -0.714531f, -0.349243f, -0.514827f,\n0.468796f, -0.583806f, -0.974925f, 1.096060f, 1.451272f, -0.389752f, 1.669280f, -1.693950f, -1.634339f, 1.904013f, 0.031509f, 0.730346f, -0.405888f, 0.984222f, -1.450587f, 3.077593f, 1.020428f, -0.570306f, 0.067012f, -0.246752f, -1.003384f,\n0.540607f, 0.368289f, -0.860407f, 0.964047f, -1.012066f, -2.042975f, 0.248587f, 0.774928f, -0.006127f, -1.433890f, 0.866205f, 0.560345f, -0.255699f, 0.080623f, 1.104654f, -0.309399f, -0.670332f, 0.918140f, -0.432292f, 0.002997f, 0.839273f,\n-0.121985f, 1.182817f, 3.393430f, 0.163369f, 0.710627f, 0.172454f, -1.590428f, 1.178373f, 0.156588f, -0.801174f, -0.159367f, 0.151297f, 0.359268f, -0.191109f, 1.668380f, 0.363285f, 2.174566f, 0.186472f, 0.040252f, -0.176172f, 0.489575f, -0.195652f,\n-0.187481f, 0.070917f, -1.086960f, 0.150120f, -0.595372f, -0.731837f, 0.933801f, -1.932438f, -1.235522f, -0.442077f, -0.088126f, -0.294966f, 0.628476f, 0.696967f, -0.490726f, 3.585684f, 1.345003f, 0.119067f, 0.279904f, 0.222206f, -0.322856f,\n0.107667f, 0.186529f, -1.548587f, 1.762915f, -1.034729f, -2.499967f, 0.293264f, -0.871263f, 0.570761f, -0.115014f, 0.399965f, 0.963241f, -0.113112f, -0.095497f, 1.494151f, -1.200482f, -0.626520f, 0.478837f, 0.236760f, 0.013526f, 0.521780f, 0.293497f,\n1.189330f, 2.028713f, -0.760846f, 0.601984f, -0.647807f, -1.516750f, 0.850352f, 0.861188f, -0.516714f, -0.305253f, 0.474328f, 0.916899f, -1.110196f, 0.371443f, -0.213365f, 2.166260f, 0.547638f, -0.043170f, -0.107523f, 0.526965f, -1.773968f,\n-0.587239f, 0.692158f, 0.602554f, 0.969097f, -0.288048f, -1.280353f, 0.137778f, -0.550532f, -0.650481f, -0.814872f, 0.459141f, -0.830933f, 1.124587f, -0.361007f, 0.191052f, 1.095245f, 1.658188f, -0.636214f, -0.112242f, 0.593042f, -0.006238f,\n-0.192374f, -0.421283f, -0.822498f, 0.693484f, -0.662440f, -1.400522f, 0.237620f, -1.796519f, -0.004709f, 0.650038f, -0.375422f, 0.282808f, 0.364612f, 0.223661f, 0.797023f, -0.442972f, -0.170324f, -0.610531f, 0.727974f, -0.629089f, 0.475799f,\n0.518419f, 0.680300f, 0.070709f, -0.930143f, 0.255050f, 0.381751f, -0.657725f, 0.317073f, 1.362956f, 0.637650f, 0.758230f, -0.693292f, 0.755888f, -1.133093f, -0.400929f, -1.150456f, 1.227010f, 0.703212f, -0.147276f, 0.346356f, 0.869479f, -1.896735f,\n-0.127746f, 1.136017f, 0.501555f, 0.451515f, -0.098420f, -0.691314f, 0.716231f, 0.018744f, -0.648981f, 0.023560f, 0.852675f, -0.121039f, 0.441370f, -0.490128f, 2.631592f, 0.804531f, 0.873911f, -1.114094f, 0.196138f, -0.414551f, -0.510298f, 0.935521f,\n-1.419683f, 0.040705f, 1.129768f, 0.955794f, -0.386963f, 0.528805f, -2.418588f, 0.163267f, 0.482423f, -0.434974f, -0.132591f, 0.594886f, 1.092558f, -1.232574f, -0.640360f, -0.037408f, -2.100446f, 1.261851f, -0.193771f, 1.261575f, 0.821745f,\n0.943274f, -1.805920f, -0.969365f, 0.115066f, 0.171520f, -0.963767f, -0.695223f, 0.023279f, 0.948102f, 1.535369f, -0.853733f, -0.199854f, -0.957566f, 0.874893f, -1.277355f, 1.371855f, 0.971935f, -0.045509f, 0.718398f, 0.975881f, -2.992739f,\n-0.592237f, 1.348439f, 0.195565f, 0.483457f, 0.540793f, -1.658263f, -0.795681f, -0.273590f, -0.553655f, -0.681651f, 0.658157f, -1.054443f, 0.712930f, -0.469459f, 0.333199f, -1.962105f, -0.811337f, -0.053301f, 0.603529f, 0.851811f, 0.451919f,\n-1.918654f, -1.585494f, 0.334608f, 0.550023f, -3.217332f, 0.417652f, 0.562733f, -0.524906f, -1.263817f, 0.524401f, -0.391816f, 0.619877f, -0.152503f, -2.047891f, 1.398006f, 1.183519f, 0.952185f, 1.478058f, -2.128806f, -1.229606f, 2.211075f,\n0.666492f, -1.299407f, -1.783674f, 0.512651f, 0.246178f, -1.692106f, 0.333205f, -0.544185f, 0.664014f, 0.569015f, -2.001959f, 0.421349f, 1.055657f, 1.362917f, -0.292810f, 1.686189f, -0.682036f, 0.375611f, -2.422227f, -0.318152f, 0.014898f, 3.780154f,\n-0.547772f, 0.386885f, 0.591675f, 1.528637f, -1.175550f, -1.611272f, -0.664251f, 1.010464f, 0.962717f, -1.477431f, -0.451589f, -1.639575f, -0.174374f, 0.740640f, -0.589085f, -1.818842f, -1.334450f, 0.923688f, -0.295526f, 1.468272f, 1.209214f,\n-1.855380f, -2.057143f, 1.583252f, -0.532828f, -0.431844f, 2.239714f, -0.228003f, 0.690586f, -1.052899f, -0.495280f, 1.273039f, 1.240190f, -0.485147f, -3.287011f, -0.607309f, 2.246059f, 1.471516f, 1.908184f, -3.220648f, -1.027271f, 0.526617f,\n-0.082932f, -2.014678f, -0.096541f, 1.301906f, -0.524178f, -2.138888f, 1.574475f, 1.535297f, 0.521855f, 1.355893f, -1.854961f, 0.621673f, 0.394227f, 2.202367f, 0.074909f, 1.354414f, -1.093604f, -0.389026f, -3.105309f, -1.456278f, -0.912639f,\n3.750326f, 0.102695f, 1.262653f, -0.054035f, 1.016719f, -0.885518f, -2.325809f, -1.938585f, 0.855463f, 1.467952f, -1.960750f, -0.733265f, -2.518590f, 0.425446f, 0.806429f, -2.499086f, -3.436352f, 0.209907f, 0.785167f, -0.152066f, 0.963072f,\n1.316500f, -1.749504f, -2.704767f, 0.708150f, -0.335537f, -0.586137f, 1.998953f, -0.771161f, 1.400166f, -1.525205f, -0.323525f, 1.684280f, 1.148142f, -0.759998f, -3.199715f, 0.257973f, 1.976264f, 1.416053f, 1.997373f, -2.407825f, -1.041189f,\n-0.886326f, -0.297002f, -1.914942f, -0.417369f, 1.452880f, -1.540627f, -2.068801f, 1.754106f, 1.799723f, 1.288489f, 1.610769f, -1.176918f, -0.154802f, 0.770515f, 1.357799f, -1.477466f, 0.485925f, -0.298810f, -0.514699f, -2.576272f, -1.590447f,\n-1.310898f, 2.896299f, -0.219460f, -0.204942f, 0.783091f, 0.510356f, -0.344851f, -2.701928f, -1.866130f, 1.834658f, 1.199617f, -1.961225f, -0.172211f, -2.755874f, 1.232404f, 0.006208f, -2.815534f, -3.220154f, 0.299088f, -0.123219f, -0.222042f,\n1.209046f, 1.210044f, -1.799118f, -2.168779f, 0.544988f, -0.442955f, -1.104521f, 2.596200f, -0.479546f, 1.484103f, -1.030618f, 0.955415f, 0.498915f, 0.684196f, -0.053893f, -2.552685f, -0.493257f, 1.437180f, 1.274599f, 1.539463f, -1.895217f,\n-1.423102f, -0.357219f, 0.343488f, -1.800423f, -0.083558f, 1.842618f, -1.279578f, -2.037842f, 1.872534f, 0.587953f, 1.754248f, 0.828843f, -2.115093f, -1.106832f, 1.090565f, 1.695477f, -2.219583f, 0.338706f, -1.921250f, -0.526905f, -2.959279f,\n-1.125757f, -1.167934f, 2.080698f, 0.023755f, 0.177448f, 1.186323f, 1.696458f, -0.330439f, -1.913390f, 0.911826f, 0.776518f, -0.379115f, 1.238900f, 0.529403f, -0.881927f, 0.564514f, 0.154626f, -0.629534f, 0.130391f, -0.112913f, -2.904636f,\n-0.836467f, 1.066655f, -0.472448f, -2.177166f, 1.923953f, 1.981990f, 0.371016f, -0.342392f, 0.298622f, 0.886099f, 0.926394f, 1.258198f, 0.140817f, -0.597883f, -0.207426f, 0.852114f, -0.721100f, -3.625403f, -0.420037f, -1.869219f, -0.307270f,\n0.355637f, -1.973745f, 1.624474f, 0.882586f, -0.222889f, 2.560458f, -0.427295f, 0.571670f, 0.623590f, -1.131989f, -0.033429f, -0.367495f, 1.361079f, -1.408454f, -2.465129f, -1.127615f, 0.444583f, -0.003327f, 1.449228f, -0.279902f, 1.054354f,\n-1.738561f, 0.122810f, 1.561283f, 1.345098f, 0.577535f, -0.647021f, -1.075730f, 0.599280f, -0.996091f, 0.159191f, 0.931358f, -0.434884f, 0.522233f, 0.945162f, -1.085713f, -0.406165f, -0.665152f, 1.332126f, -1.115807f, 1.195589f, -0.363202f,\n-1.313115f, -2.652922f, 1.028085f, -0.441832f, -1.009922f, 1.136009f, 1.424361f, -0.885432f, 0.027854f, -0.469280f, -0.659429f, 1.559591f, 0.633911f, -1.034494f, 0.589994f, 1.281958f, -0.482302f, -1.174531f, -1.199332f, 0.286387f, -1.127611f,\n1.091035f, -0.673102f, -1.638867f, 0.372793f, -0.479528f, 0.261356f, 3.789243f, -0.035291f, -0.253613f, 0.081910f, -0.992755f, 1.131639f, -0.028291f, -0.468182f, 0.430649f, -1.016997f, -0.622847f, 0.249915f, 0.165018f, -0.391370f, 0.405451f,\n-1.004394f, -0.071248f, -1.038464f, 0.278204f, -0.063220f, 1.010007f, 0.127362f, -0.215987f, 0.235128f, 1.189195f, -0.734952f, 1.152601f, -1.294956f, -0.530544f, 1.446150f, -0.692013f, 0.947709f, -0.548879f, 0.805591f, 0.139717f, 2.097677f,\n0.829182f, -1.754402f, -1.734027f, -0.446307f, -1.425562f, -1.441668f, -0.028898f, -0.557962f, 1.458262f, 0.668918f, -1.715652f, 0.064533f, -0.039660f, 0.397757f, -0.856202f, 0.371252f, 0.635038f, -0.426126f, -0.036141f, -0.355930f, -0.308750f,\n-1.139229f, 0.716418f, -0.096738f, -1.654999f, 0.229564f, -0.742293f, 1.144378f, 3.367708f, -0.668593f, 0.635328f, 0.475890f, -1.867284f, 0.708552f, -0.150064f, -0.767529f, 0.819902f, -1.240451f, -0.159677f, -0.860405f, 1.116107f, -0.418577f,\n1.700665f, 0.010189f, 0.602304f, -0.757552f, 0.846764f, 0.019623f, 0.089312f, 1.430528f, -0.556822f, -0.702131f, -0.426304f, -0.671188f, 0.841373f, -1.255302f, -0.453521f, -0.545374f, -0.395184f, 0.506714f, 0.524569f, 0.017741f, -0.073536f,\n2.773150f, 1.187950f, -0.378931f, -0.958972f, 0.107237f, -0.825448f, -0.235017f, -0.332222f, -1.589448f, 1.661050f, 0.358949f, -2.176911f, -0.012935f, -1.375234f, 0.232116f, 0.160993f, -0.528128f, 0.570022f, -0.622435f, -0.019391f, 0.584657f,\n-1.105147f, -0.961291f, 0.584083f, 0.217782f, -0.927977f, -0.967690f, -0.275855f, 1.453454f, 1.304399f, -1.153819f, 0.643374f, -0.144519f, -1.779236f, 0.291192f, 0.329910f, -0.583006f, 0.683196f, -0.464729f, 0.237826f, -1.750089f, 0.465621f,\n-1.223503f, 2.110464f, 0.012197f, 0.589143f, -0.236033f, 0.318034f, -0.903566f, -0.786949f, 0.951884f, 0.944681f, -0.001375f, -0.013511f, -1.442614f, -0.275701f, -0.132966f, -0.388943f, -1.360263f, 0.248228f, -0.538051f, 1.086636f, -1.263100f,\n-0.160786f, 0.276609f, 0.998422f, 0.191832f, -0.475799f, 0.599600f, 0.031713f, -0.351311f, -0.727408f, -0.858330f, 1.367720f, 0.040369f, -2.097055f, -0.122829f, -1.675033f, -0.465224f, 0.925344f, -1.021115f, 0.032456f, -0.002263f, 0.306394f,\n0.309003f, -0.365907f, 0.024255f, -0.288974f, 0.608217f, -1.541660f, -1.057855f, 0.133425f, 0.714560f, -0.852538f, -1.099972f, -0.092205f, -0.196294f, -0.219306f, 0.480866f, 0.986193f, 0.835923f, 1.052276f, -0.474222f, 0.726873f, -1.367371f,\n-0.945870f, -0.672824f, 1.431266f, 0.325226f, -0.291073f, 0.172001f, 0.519903f, -0.228746f, -0.766291f, 0.668928f, 1.069736f, 0.440022f, -0.351392f, 0.124243f, 0.077222f, -0.707811f, 0.300587f, -1.112865f, -0.613254f, -1.190719f, 0.698469f,\n-0.634510f, -0.305811f, -1.658146f, 1.868991f, 0.864029f, -0.155566f, 0.611082f, -0.006597f, 0.987163f, -1.817991f, -1.401143f, 1.030284f, 1.166651f, -1.901376f, -0.908883f, -1.071354f, -0.649887f, 0.478326f, 0.333459f, 1.166116f, -0.376502f,\n-0.708536f, 0.259395f, 0.015944f, -0.569965f, 0.237331f, 0.251908f, -0.502957f, -0.141382f, -0.701830f, 1.135644f, -1.064063f, 0.127915f, -1.095895f, -1.147143f, 0.337110f, 0.963661f, 0.870204f, -0.006241f, 2.011872f, 1.898455f, 1.453121f,\n-1.560920f, -0.784326f, -1.688248f, 0.975911f, -0.606320f, -0.668594f, -0.121861f, -1.061863f, -1.776847f, -1.654734f, 1.009595f, 1.343685f, 1.083784f, 1.580385f, -1.138915f, -0.204685f, -0.793592f, 0.165069f, 0.893551f, 0.368078f, -0.307887f,\n-0.274487f, -0.823214f, 2.773130f, -1.738673f, -1.133690f, 0.696109f, 0.197267f, 1.047627f, 0.424695f, -1.076119f, -1.900798f, 0.384084f, 0.154380f, -5.180565f, -1.146187f, 0.426237f, 1.503584f, -1.280803f, -0.458351f, -0.904971f, 0.242608f,\n-0.535602f, -0.854401f, 0.764265f, 0.224604f, 1.444768f, 1.223602f, -1.409645f, -0.679785f, 0.060478f, 0.372445f, -1.072555f, -0.307155f, 0.200333f, -0.291970f, -1.268014f, 0.588753f, -0.368404f, 0.141359f, 0.311585f, -1.123527f, -0.034482f,\n0.320461f, 0.870240f, 1.455932f, -0.134495f, 1.006121f, -0.141910f, -0.895775f, -0.255062f, 0.184390f, -0.150910f, -0.552816f, 0.498567f, 0.006648f, 0.946280f, -0.836739f, -2.060691f, -0.337826f, 0.313808f, 0.664624f, 2.294295f, 0.733371f, 0.271646f,\n0.147480f, -0.521632f, 0.953327f, -1.786225f, -1.518174f, 0.948102f, -0.656214f, 1.142731f, 0.839174f, -2.911221f, -1.233324f, 1.318520f, 0.102969f, -3.388280f, 1.166659f, 0.718627f, 1.569366f, -0.984325f, -0.457946f, -0.003471f, 0.191103f,\n-0.693733f, -0.871772f, 0.328455f, 1.355869f, 1.033828f, 2.899013f, -2.061977f, -0.886170f, -1.010329f, 0.523390f, -1.531510f, 0.975874f, 0.419110f, 0.249444f, 0.274992f, 0.367905f, 0.564491f, 0.102436f, 1.732364f, -1.310333f, -0.692943f, -0.192121f,\n1.579031f, 1.207084f, 0.711807f, 0.578923f, 0.479999f, -2.221579f, 0.259776f, 0.922100f, 3.284556f, -0.387133f, 0.098062f, -0.659067f, 0.966181f, -0.934175f, -3.087059f, -1.212166f, 0.779678f, 0.684624f, 1.084477f, 0.791017f, -0.579141f, 0.435360f,\n-0.894871f, -0.302998f, -2.684360f, -0.715925f, 1.000678f, 0.756466f, 0.677338f, 0.576653f, -2.338198f, -1.911117f, 0.695886f, 0.544254f, -1.951339f, 2.216373f, 0.539577f, 1.394682f, -1.014110f, 0.106781f, 0.095728f, -0.273726f, -0.628098f,\n-0.833240f, 0.497605f, 1.033844f, 1.233100f, 1.558232f, -0.430209f, -1.305639f, -1.161634f, 0.916908f, -1.353271f, -0.638361f, 0.947209f, -0.811637f, -0.455672f, 0.758405f, 0.105352f, 0.440154f, 1.938920f, -1.149230f, -0.815821f, 0.464883f,\n1.882245f, 0.432102f, 0.699367f, 0.175597f, 0.659377f, -1.684720f, 0.205128f, 0.475980f, 2.179013f, -0.831130f, -1.808134f, 0.158027f, 1.019346f, -0.904904f, -2.694090f, -1.575779f, 1.685050f, 0.579534f, 0.256334f, 0.590098f, -0.978385f, 0.932496f,\n-0.849183f, -0.756358f, -3.110541f, -0.486661f, 0.312109f, -0.055823f, 0.405286f, 1.112153f, -1.774642f, -1.317048f, 0.506476f, 0.038923f, -2.521143f, 2.847664f, 0.167624f, 1.534959f, -1.328000f, 0.925992f, -0.076188f, -0.671211f, -0.075383f,\n-1.017713f, 0.505916f, 1.359997f, 1.532666f, 0.904325f, -0.327955f, -0.632326f, -0.671822f, 0.925507f, -1.419663f, -0.865240f, 1.222726f, -0.756159f, -0.535195f, 0.983649f, -0.065728f, 1.282701f, 1.789662f, -1.367682f, -0.997477f, 0.658055f,\n1.836509f, -0.305776f, 0.633060f, -0.912121f, 0.224432f, -2.426756f, 0.060659f, 0.035739f, 1.582008f, -0.380899f, -0.009763f, 0.439743f, 0.775187f, -0.823897f, -0.221105f, -0.149300f, -0.079758f, 1.130949f, 1.971372f, 0.797977f, 1.207209f,\n-0.344436f, 0.341342f, 2.049416f, -2.405606f, -0.901107f, -1.929208f, -0.798956f, 0.941600f, 0.767573f, -1.437708f, 1.353413f, 2.150038f, -0.526025f, -0.772613f, 0.102807f, 0.370426f, 0.724975f, 0.487385f, -0.567795f, -0.232622f, -0.467991f,\n-0.119999f, 0.309448f, -2.476555f, 1.162980f, -0.880782f, 0.026068f, 1.791929f, -0.106304f, -0.758780f, -0.202693f, -0.744189f, 0.524955f, -0.678105f, -0.340219f, 1.459386f, -0.796032f, 1.031968f, -0.155068f, 2.166889f, 0.110118f, -0.475032f,\n-1.022115f, -0.213267f, 0.586871f, 0.669243f, 1.048261f, 0.692620f, -0.142699f, 0.719915f, 1.960566f, 0.669199f, 0.701057f, 0.708545f, -1.275202f, -1.500807f, -0.358885f, 0.982709f, -0.019593f, -1.008087f, 1.675587f, 1.571316f, -0.672116f, 0.959170f,\n-0.856094f, 1.096297f, 1.850608f, -0.968155f, -0.954624f, -0.801896f, -0.811372f, 0.888549f, 1.373603f, -1.169247f, 1.062267f, 1.859539f, -1.042689f, -0.614748f, -0.452881f, -0.170212f, -0.034745f, 0.554351f, -1.341318f, 0.679202f, 0.576219f,\n-0.579071f, 0.113456f, -2.309943f, 1.604909f, -1.295044f, 0.644507f, 0.946430f, -0.192055f, -1.352151f, -0.885073f, -0.492266f, 0.791781f, -0.873772f, -0.520023f, 0.581067f, -0.623686f, 1.447701f, -0.023279f, 1.162324f, 0.610642f, 0.173335f,\n-0.669334f, -0.200777f, 0.597086f, -0.337689f, 1.298025f, 0.017442f, 0.541049f, -0.156596f, 0.898934f, -0.239206f, 1.021196f, 0.816782f, -0.814108f, -1.619377f, 1.044233f, 0.306322f, 0.803905f, -1.003378f, 1.285787f, 1.607851f, -0.341033f, 1.118033f,\n-1.122602f, 1.687084f, 2.290035f, 0.912976f, -0.181487f, -1.570133f, -1.067047f, 0.315468f, 0.516181f, -1.117892f, 0.562861f, 0.795604f, 0.213801f, 0.136639f, -1.264143f, 0.184277f, -1.415097f, 0.445163f, -1.289276f, 0.759540f, 0.791912f, -0.655653f,\n0.463186f, -2.856141f, 1.178919f, -1.633694f, 0.633110f, 0.595066f, 0.149164f, -0.733807f, -1.159560f, 0.136576f, 0.568669f, -1.358306f, 0.280551f, 0.979031f, -1.257263f, 1.508472f, -0.138504f, 0.618895f, 0.408482f, -0.050952f, -0.365905f,\n-0.865275f, 1.057796f, -0.335891f, 1.768766f, 0.142408f, 0.275095f, -0.198641f, 1.114227f, -0.123637f, 1.500518f, 1.931698f, -0.993683f, -1.695946f, 0.815441f, -0.039496f, 0.685664f, -2.079727f, 0.002730f, 0.925080f, -0.439595f, 0.560485f,\n-0.080207f, 1.697776f, 1.403237f, 2.297750f, 0.728611f, -1.251218f, -0.850905f, 0.450099f, 0.295838f, -0.417785f, 0.369302f, -0.265435f, 1.145927f, 0.221315f, -2.498593f, 0.114353f, -1.721500f, 0.472948f, -0.780339f, 0.497410f, 0.949847f, -0.319466f,\n-0.441621f, -2.458889f, 0.157200f, -1.938172f, 0.863162f, 0.473122f, 0.630548f, -0.980042f, -1.039531f, 1.100824f, 0.122850f, -1.563930f, 0.420388f, -0.124798f, -1.158413f, 1.272686f, 0.360637f, 0.169498f, 0.292589f, 0.788560f, 0.142956f, -2.073992f,\n0.317010f, -0.725532f, 2.339148f, 0.114342f, -0.041986f, -0.403053f, 1.127306f, -0.128741f, 0.424037f, 1.775353f, 0.457591f, -0.868894f, 1.066695f, -0.254044f, -0.126945f, -1.124831f, -0.129247f, 0.372301f, 0.363886f, -0.269958f, 0.982628f,\n-0.165771f, 0.726767f, 0.074410f, 1.871227f, -0.509445f, -0.526058f, 0.034882f, 0.133042f, -0.352933f, -0.654090f, -1.070230f, 1.975705f, 0.899266f, -3.083405f, 0.415449f, -0.666450f, 0.001776f, 0.826250f, -0.096150f, 0.967378f, 0.006111f,\n-0.089789f, 0.060855f, 0.012654f, -1.124342f, 1.003577f, 1.406903f, -0.605950f, -0.810940f, -0.630564f, 1.316852f, -0.214105f, -1.068866f, 0.231751f, -0.447915f, -0.879256f, 0.799837f, 1.065708f, 0.759493f, 0.042244f, 1.137577f, 1.275171f,\n-2.082844f, -1.152114f, -0.186337f, 2.153417f, 0.750533f, -0.398065f, 0.781277f, 0.772665f, 0.187459f, -0.898784f, 0.641663f, 1.375877f, -0.229222f, 0.277631f, 0.749126f, -0.059557f, -0.931973f, -0.269076f, -0.479147f, -0.400526f, -0.447173f,\n1.026945f, -0.327362f, -0.417186f, -1.204896f, 2.010168f, 0.546066f, 0.502546f, -0.157648f, 0.010623f, 0.602368f, -0.198910f, -1.287486f, 1.526261f, 1.292425f, -2.515142f, -0.739914f, 0.489990f, 0.153938f, 0.673130f, 0.113321f, 0.923425f, -0.138219f,\n0.228696f, 2.154617f, -0.170395f, -1.509746f, 1.195129f, 0.790547f, -0.432044f, 0.192720f, -0.522408f, 1.816527f, -0.692108f, -0.228365f, -0.251615f, -0.440379f, -0.386703f, 0.869294f, 0.841063f, 0.219665f, 0.336743f, 1.617725f, 1.430971f,\n-1.969753f, -1.651570f, -0.061922f, 0.862140f, -0.107240f, 0.250328f, 0.397131f, -1.240761f, 0.893692f, -1.961247f, -0.015782f, 1.476599f, 1.253525f, 0.481826f, -0.948431f, 1.190104f, -0.535426f, -0.723944f, 0.211112f, 0.129118f, 0.624501f,\n-0.281057f, -1.737452f, 1.223300f, -1.054864f, -0.153497f, 0.547740f, 1.208928f, 0.966185f, 0.016041f, -0.782669f, -0.496184f, 0.184609f, 0.683600f, -0.992825f, -1.562640f, 0.435271f, 0.700968f, 0.009085f, -0.723527f, -1.607847f, -0.121748f,\n-0.364462f, 1.396594f, 0.411560f, -1.298773f, -0.400411f, -0.929875f, 0.350203f, -0.170202f, 0.564234f, 0.350096f, 0.225492f, 0.317470f, -0.109923f, 0.346043f, 0.863156f, -0.958946f, -0.156463f, -0.751084f, 0.467888f, -0.959015f, -0.068695f,\n-0.340366f, 0.689334f, 2.475220f, 0.060662f, 1.613375f, 0.200853f, -0.407261f, -0.097298f, 0.173963f, -0.527847f, -1.275670f, -0.665288f, -0.130934f, 1.430267f, -1.034242f, -1.043146f, -0.125318f, -0.312113f, -0.764664f, 0.672040f, 0.868923f,\n1.458291f, -0.805831f, -1.658749f, 1.459244f, -1.747046f, -1.350697f, 2.275939f, 0.390961f, 0.212123f, -0.055558f, -1.541706f, -0.949271f, 0.592083f, 1.070017f, -0.625409f, -0.664485f, 1.070071f, 1.149759f, -1.375814f, -0.443750f, -1.351108f,\n-0.701914f, -0.981902f, 2.039065f, 0.498379f, -0.102860f, 2.032961f, 0.805023f, 0.171374f, -0.833621f, -1.540292f, 1.330375f, -1.041423f, 2.245445f, 0.299050f, 0.712775f, 2.376284f, 1.160399f, -1.619467f, -1.001359f, 0.062937f, 0.944344f, -0.974665f,\n-0.321005f, 1.687063f, 2.162192f, -0.450970f, 1.319602f, 0.869102f, 0.455895f, 0.381060f, -0.093420f, 0.088492f, -0.951095f, -1.760733f, -0.562467f, 1.283857f, -2.196671f, -0.962469f, -0.101020f, 0.070562f, -0.249149f, 1.172189f, 0.736308f,\n0.534315f, 0.129393f, -1.365741f, -0.091670f, -2.424887f, -0.620819f, 1.419584f, 1.115384f, 1.069999f, 0.036185f, -0.685640f, 0.170835f, 0.940246f, -0.401736f, -1.055278f, 0.676255f, 0.816850f, 0.925066f, -0.922037f, -0.574200f, -0.399504f,\n-0.551921f, -0.031867f, 1.305278f, 0.509803f, -0.191756f, 1.115876f, 0.483687f, 0.794765f, -1.088422f, -0.731534f, 0.781590f, -0.625665f, 0.735456f, 1.382061f, -0.465799f, 0.972116f, 1.025092f, 0.135251f, -0.180843f, 0.328388f, 0.367606f, 0.293292f,\n-0.007337f, 1.773881f, 1.266928f, -0.304666f, -0.002363f, 0.457041f, 0.524418f, 0.445386f, -0.292034f, 0.213197f, -0.460867f, -3.110149f, -0.597560f, 1.515451f, -1.845790f, -1.735430f, -0.460671f, 1.198805f, 0.319452f, 1.472281f, 0.859249f,\n-0.184503f, 0.377422f, -1.251755f, -0.426784f, -2.803878f, -0.888387f, 1.298455f, -0.112842f, 0.837339f, 0.393250f, -1.035349f, -0.616850f, 0.728576f, -0.593832f, -1.375171f, 1.745803f, 0.836606f, 0.793542f, -1.047293f, 0.358043f, -0.241948f,\n-1.114783f, 0.327502f, 0.838861f, 1.080497f, 0.329636f, 2.048522f, 0.088162f, 0.627765f, -0.869685f, -0.395690f, 1.002838f, -0.850882f, -1.031573f, 1.104298f, -0.692956f, 0.260744f, 1.673509f, 0.424483f, 0.122456f, 0.542447f, 0.037406f, -0.485873f,\n0.010638f, 1.509487f, 1.309909f, 0.469723f, -0.755815f, 0.565794f, -0.376821f, 0.655883f, 0.240766f, 0.707070f, 0.068998f, -1.711710f, -0.803955f, 0.806650f, -1.631965f, -0.101395f, -0.654656f, 0.176490f, 0.644406f, 1.991801f, 0.991062f, 0.784084f,\n-0.823577f, -0.526985f, 1.994918f, -2.933231f, -1.585875f, 0.160545f, -0.508924f, 1.102255f, 0.337496f, -1.138908f, -0.037410f, 1.759883f, -1.440779f, -1.743696f, 0.758008f, 0.768979f, -0.076665f, -0.429689f, -0.672185f, -0.115842f, -0.744773f,\n-0.281720f, 0.988931f, 0.188234f, 0.817904f, 1.379414f, 0.084890f, 1.204376f, -0.037018f, -1.138103f, 0.186860f, -0.955455f, -0.434980f, -0.020027f, -0.695627f, 0.959612f, 0.761880f, 0.861144f, -0.277761f, 1.007886f, -0.249040f, -0.557005f,\n-0.999572f, 0.713419f, 1.767153f, 0.614523f, -0.005856f, 0.582841f, -0.420710f, 0.911304f, 1.127691f, 0.874265f, 0.766268f, -0.190490f, -1.883506f, -0.860486f, -0.529723f, 0.703677f, 0.179176f, -0.581151f, 0.277398f, 0.222596f, 0.194629f, 0.226848f,\n-0.661002f, 0.944637f, 1.468059f, -0.731202f, -0.210115f, 0.108837f, 0.077184f, 0.653888f, 0.671258f, 0.018868f, 0.695821f, 1.042620f, -1.328971f, -1.709988f, 0.122172f, 0.122860f, -0.966126f, 0.464179f, -0.326712f, 0.184790f, 0.318424f, 0.047070f,\n0.101113f, -0.025106f, 0.342739f, 0.709063f, -0.085474f, 0.059012f, 1.775232f, -0.886420f, -0.120715f, -0.201825f, 0.062096f, -0.672892f, -0.208821f, 0.331245f, 1.001138f, 0.457356f, 0.051323f, -0.545596f, 0.081475f, 0.272821f, -0.473565f, 0.153984f,\n0.491775f, -0.049941f, -0.489045f, -0.000323f, -0.039601f, 0.046346f, 0.389690f, 0.150186f, 0.693536f, 0.955144f, -0.648648f, -0.116270f, 0.814570f, 0.351445f, 0.289185f, 0.057098f, -0.365554f, 0.311422f, -0.111170f, 0.007546f, -0.214006f, 1.045920f,\n1.307051f, 0.949064f, -0.088760f, -0.560235f, 0.007414f, 0.748457f, 0.125439f, 0.415301f, 0.873763f, 0.307058f, -0.879365f, -1.463181f, -0.262880f, 0.065384f, -1.508136f, 0.611436f, -0.629563f, 0.701682f, 0.516292f, 0.616193f, 0.454964f, -0.337689f,\n0.156403f, 0.341309f, 0.280644f, -0.143968f, 1.538560f, -0.501722f, -0.334870f, 0.483869f, -0.392227f, -0.694673f, -0.019416f, 0.245696f, 0.191357f, 0.786749f, 0.176675f, -1.006027f, -0.345974f, 0.441753f, -0.030593f, -0.397819f, 0.466602f,\n-0.492576f, -0.392454f, -0.166080f, -0.244829f, 0.015561f, 0.444790f, 0.311911f, 1.335080f, 1.249605f, -0.587586f, 0.145745f, 0.873295f, 0.043382f, 0.092004f, -0.284279f, -0.856334f, 0.112745f, -0.091358f, -0.298131f, 0.279713f, 0.223618f, 0.021545f,\n0.812869f, 0.720289f, 0.351405f, -0.622619f, 0.870184f, 0.143478f, 0.171103f, 0.116796f, -0.627964f, 0.773964f, -0.176967f, -1.901923f, 0.120638f, -1.095849f, -0.061283f, -0.657808f, 0.600838f, 0.979470f, -0.196759f, 0.243234f, 0.392435f, 0.113763f,\n-0.477020f, 0.738870f, 0.262665f, 0.645655f, -0.893143f, -0.573824f, 0.922872f, -0.233768f, -0.592364f, 0.009473f, -0.361758f, -0.680366f, 1.396063f, 0.686836f, 0.273942f, -0.124257f, 1.069189f, 0.737372f, -1.546774f, 0.146896f, -0.535724f,\n0.871786f, 0.092486f, -0.368189f, 0.036574f, 0.164621f, 0.061609f, -0.062258f, 0.569392f, 0.615448f, 0.957178f, 1.126063f, 0.193993f, 0.288802f, -0.942116f, -1.193433f, 0.514166f, 0.346206f, -0.602057f, 0.544753f, -0.279472f, 0.066501f, -0.871311f,\n1.973914f, -0.084824f, -0.767271f, 0.580443f, -0.461905f, -0.388046f, -0.649009f, -0.988786f, 1.422247f, 1.353774f, -2.230599f, 0.277636f, 0.104127f, 0.108021f, -0.226497f, 0.064691f, 1.216828f, -0.094316f, 0.375283f, 0.740813f, -0.137187f,\n-0.223021f, 0.578564f, 1.059945f, -0.554807f, -0.420662f, -0.529230f, 1.401989f, 0.355022f, -0.200838f, -0.160093f, -0.811994f, -0.925939f, 1.661323f, 0.801876f, 0.216334f, -0.273570f, 1.255210f, 1.286929f, -1.834560f, -0.502929f, -0.500804f,\n1.244369f, 0.318538f, -0.847658f, 0.201352f, 0.335261f, 0.021874f, -0.869120f, 0.248990f, 1.099054f, 0.954301f, 0.704976f, 0.865817f, 0.226200f, -0.830342f, -0.730647f, 1.044306f, 0.014417f, -0.081053f, 0.622078f, -0.222553f, -1.339785f, -0.700870f,\n1.973112f, -0.212918f, 0.535839f, -0.297218f, -0.568359f, -0.578252f, -0.545711f, -1.082940f, 1.343622f, 1.981424f, -1.970890f, 0.169071f, 0.573738f, 0.463682f, 0.515884f, -0.072284f, 1.060276f, 0.264576f, 0.773604f, 0.694907f, -0.414160f,\n-0.659477f, 0.401801f, 1.027097f, -1.011323f, 0.506648f, -0.185413f, 1.469676f, 0.004544f, -0.657688f, 0.405885f, -0.829462f, -0.581942f, 0.675462f, 0.562167f, -0.109647f, -0.858765f, 1.042480f, 1.002453f, -1.983198f, -0.644205f, -0.482282f,\n0.461214f, 0.414327f, -0.914250f, 0.621399f, 0.091235f, 1.562770f, -0.828084f, -1.094688f, 1.156534f, 0.803203f, -0.527882f, -0.250648f, 1.230840f, -0.535891f, 0.290766f, -0.872809f, -1.279578f, -0.273965f, -0.669808f, 0.471602f, -2.389492f,\n-2.036190f, 0.125922f, 0.786484f, -0.056139f, 1.151739f, -0.148030f, -0.852287f, 0.298282f, -0.223200f, 0.018504f, 0.406619f, -1.125905f, -0.944872f, -0.638601f, 0.265520f, -1.465064f, 0.342401f, 1.699304f, -0.308713f, 0.575275f, 0.396247f,\n-0.958594f, -0.947036f, -0.688522f, -1.087713f, 0.858163f, 0.643259f, -0.776816f, 0.115941f, 0.517330f, 0.865063f, -0.113004f, 0.327450f, -1.772249f, -0.599110f, -0.121959f, -1.473611f, 0.556238f, 0.658341f, 0.122026f, 0.608664f, 1.349252f,\n0.374748f, 0.126609f, -0.880438f, -0.968793f, -1.247710f, -1.172042f, -0.683183f, 0.024350f, -1.271187f, 0.080873f, 1.690647f, 0.699772f, 1.626628f, 0.416755f, 0.234564f, 0.393015f, -1.894690f, -0.224338f, 1.815085f, -1.540362f, -0.511854f,\n0.984043f, -2.324085f, -1.680739f, 2.837976f, -0.166524f, -0.278987f, -0.058391f, -0.717213f, -0.389477f, 0.589397f, -0.758936f, 0.965594f, 0.246006f, 0.082748f, 1.043679f, -0.753702f, -1.037657f, -1.059203f, -0.494112f, -0.944865f, 2.206897f,\n1.396714f, -0.670537f, 0.795296f, -1.444246f, 0.743884f, -0.933842f, -1.051284f, 0.168714f, -0.522145f, 1.507471f, 0.969419f, -0.208027f, 2.195985f, -0.403760f, -2.746180f, -1.658023f, -0.553871f, 2.382261f, 0.120727f, -1.780519f, 2.097005f,\n2.309470f, -1.083876f, 0.301480f, -0.820696f, 1.222642f, -0.444067f, -0.765034f, -3.427255f, -0.487412f, -2.272359f, -1.032020f, 0.304527f, -0.884730f, 1.471000f, -0.864599f, 1.932054f, 0.860386f, -0.287615f, 0.492719f, 1.769521f, -1.287889f,\n-1.006858f, 0.738818f, -3.470671f, -1.658289f, 1.519314f, -0.250306f, -0.184823f, 0.406279f, -1.887887f, 0.139067f, 1.193338f, -1.303746f, 1.424473f, 1.465195f, 0.531268f, 1.259051f, -0.535875f, -0.054695f, -1.060461f, -1.328703f, 0.035790f,\n2.045236f, 1.088202f, -0.076746f, 1.014568f, -1.181578f, 1.526239f, -1.407372f, -0.989381f, 0.448869f, -1.085715f, 0.438851f, 0.135816f, 0.077761f, 1.543897f, 0.203968f, -1.408877f, -1.675699f, 0.630833f, 1.164782f, -1.179734f, -1.951155f, 1.586909f,\n1.356458f, -1.062804f, -1.206686f, 0.134318f, 1.649812f, 0.609599f, 0.287419f, -4.114184f, 0.136995f, -2.908997f, -0.895954f, -0.646387f, -2.092300f, 0.272434f, -1.156070f, 1.838491f, 0.602211f, 0.822761f, 0.692008f, 1.123683f, -0.201667f,\n-1.043537f, 0.547246f, -2.679304f, -0.998811f, 0.990316f, -0.379734f, -0.494612f, 0.273447f, -1.915584f, 0.144607f, 0.974197f, -0.562940f, 1.249857f, 0.711461f, 0.790960f, 0.522727f, -0.172693f, 0.858930f, -0.871279f, -1.230626f, 0.577164f,\n1.060856f, 0.817851f, 0.133640f, 1.052205f, -0.951642f, 0.937897f, -0.095343f, 0.087421f, 0.383948f, -0.795940f, -0.852792f, 0.102679f, -0.208468f, 0.765496f, 0.422429f, -0.395767f, -0.849560f, 0.535686f, 0.478693f, -1.011250f, -1.248865f, 0.165572f,\n0.681093f, -0.404081f, -1.795299f, 0.289151f, 0.304992f, 0.912776f, 0.418582f, -2.697246f, 0.564039f, -2.258451f, -0.479632f, -0.882428f, -1.527527f, 0.603090f, -0.846747f, 1.035754f, 0.217192f, 0.148471f, 0.594986f, 1.210831f, 0.011549f, -0.144300f,\n1.128214f, -2.557649f, -0.354496f, 0.728200f, 0.361869f, -1.083839f, -0.011368f, 0.122771f, 0.048571f, 0.654332f, 0.117284f, 1.405093f, 0.123452f, 0.684392f, -0.404653f, 0.018812f, 0.951979f, -0.394833f, -0.774126f, 0.076791f, 0.789976f, 0.862010f,\n0.065671f, 0.766643f, -0.555494f, 0.825605f, 0.790078f, -0.141179f, 0.378477f, -0.758033f, -0.563251f, -0.380148f, 0.056899f, 0.629415f, 0.139306f, -0.372126f, -0.597928f, 0.734330f, 0.734008f, -0.320660f, -0.781243f, -0.196469f, 0.204225f,\n0.020295f, -1.160892f, 0.415062f, 0.616553f, 1.114374f, 0.603020f, -2.494003f, -0.452999f, -0.136930f, 0.147415f, -1.152818f, -0.769814f, 1.057886f, -0.167693f, 0.625940f, 0.361463f, -1.004113f, 0.052031f, 0.742447f, -0.478141f, 1.121703f, 1.092220f,\n-0.493965f, -0.478547f, 0.504733f, 0.409691f, -1.044401f, 0.212917f, 1.313732f, 0.666376f, 0.761260f, -0.752391f, 0.980601f, 0.283966f, 0.109536f, -0.061410f, 0.457794f, 0.473624f, 0.208712f, -0.494207f, 0.060336f, 0.287970f, 0.360409f, -0.161722f,\n0.006826f, -0.240112f, 0.109140f, 1.900080f, -0.157945f, 0.183893f, -0.548684f, -0.884992f, -0.718066f, 0.114030f, 0.808668f, -0.271383f, -0.358040f, -0.562380f, 0.114735f, 0.479919f, 0.012673f, -1.036152f, 0.233956f, 0.294413f, 0.017961f,\n-1.275885f, -0.220326f, 1.078082f, 0.300421f, 0.498417f, -1.658632f, 0.209377f, 1.391241f, -0.220812f, -0.975924f, 0.059299f, 0.923825f, -0.260359f, 0.671706f, 0.214251f, -1.071060f, 0.252649f, 0.822314f, -0.102801f, 0.959204f, 1.081949f, 0.269000f,\n-0.384030f, 0.302523f, 0.256433f, -0.929103f, -0.087928f, 0.975457f, 0.459065f, 0.524376f, -0.354458f, 1.157219f, 0.025508f, 0.176281f, -0.357884f, 0.649075f, 0.698570f, 0.638326f, -0.192190f, 0.689576f, 0.269362f, 0.607824f, -0.162166f, 0.439933f,\n-0.320458f, 0.109315f, 1.639503f, -0.243579f, 0.107209f, -0.109844f, -1.096200f, -0.440495f, 0.422732f, 0.085682f, -0.502796f, 0.077838f, -0.001063f, -0.161774f, 0.531627f, -0.049206f, -0.464159f, -0.396859f, -0.103233f, -0.076376f, -0.936566f,\n-0.210161f, 0.662618f, 0.486056f, 0.622061f, -0.706467f, 0.594868f, 1.226936f, -0.269976f, -1.189052f, 0.088047f, 1.106934f, -0.185268f, 0.494601f, -0.006691f, -0.187985f, -0.069802f, 0.315809f, 0.082655f, 1.080265f, 0.275724f, -0.201277f, 0.242420f,\n0.644584f, -0.306291f, -0.112747f, -0.015835f, 0.105897f, -0.049865f, 0.053309f, 0.622731f, 1.754318f, -0.638108f, 0.239854f, -0.575905f, 0.386263f, 0.344450f, 0.543555f, 1.046024f, 0.142168f, 0.051876f, 0.637235f, 0.174985f, -0.003161f, -0.112739f,\n0.406574f, 0.227402f, -0.703133f, -0.660133f, 0.023438f, -0.531351f, -0.040252f, 0.518295f, -0.870668f, -0.982592f, 0.572590f, 0.187677f, -0.017262f, 0.398864f, 0.835431f, 0.195296f, -0.857411f, -0.381522f, -0.364674f, -1.101454f, -0.221162f,\n0.218027f, 0.231425f, 0.185951f, -1.275303f, 0.534388f, -0.020596f, 0.407113f, -0.422942f, 0.949024f, 1.142608f, -0.545898f, -0.070208f, 0.107234f, 0.951343f, 0.021213f, 0.113336f, -0.154017f, 0.296008f, 0.356740f, -1.692763f, 0.708935f, 0.763888f,\n-1.278146f, -0.351967f, -0.196911f, -0.550725f, -1.131549f, -0.366299f, 0.644330f, 2.257607f, -0.603195f, 0.181487f, 0.597846f, -0.076572f, 0.155768f, 0.400627f, 1.012242f, -0.075708f, 0.167331f, 0.912113f, 0.168434f, 0.587073f, -0.057445f,\n0.562581f, -1.546524f, -0.723460f, -0.533181f, 0.295865f, 0.564289f, 0.271464f, 0.147894f, -0.675567f, -0.611243f, 0.886571f, 0.641280f, 0.370871f, 0.040037f, 0.786888f, 0.829250f, -0.787590f, -0.152959f, -0.431046f, -0.912983f, 0.039094f,\n-0.427876f, 0.019915f, 0.219059f, -2.587189f, -0.181097f, 0.087627f, 0.652107f, -0.120310f, -0.127874f, -0.568277f, 0.249607f, -0.304460f, -0.418712f, 1.534969f, 0.476456f, 0.004112f, -0.059104f, 0.189953f, 0.123534f, -0.217845f, 0.793798f,\n-0.245083f, 0.697420f, -0.891005f, -0.718770f, -2.229593f, -0.418356f, -0.047709f, 1.215933f, 2.141773f, -0.630867f, 0.680896f, 0.698052f, 0.394374f, 0.307509f, -0.418436f, 0.133825f, -0.007400f, 0.743993f, 0.006767f, 0.058247f, 0.253908f,\n-0.729841f, 0.269420f, -2.149899f, 1.096075f, 0.528150f, 0.336215f, -0.861080f, -1.008179f, 0.935820f, 0.046221f, -0.119609f, -0.597064f, -0.341061f, 0.599131f, -1.495851f, -1.001068f, 0.285697f, -1.032183f, 0.307544f, 1.681111f, 0.530698f,\n0.766593f, -1.273922f, 0.335427f, 0.642326f, -0.642688f, -1.176819f, -1.070503f, 0.364238f, 0.333282f, -1.789319f};\n            Layer0B = new float[]{0.419949f, 0.136773f, 0.713201f, 0.475930f, 0.092343f, 0.459801f, 0.282257f, 0.754890f, 0.305231f, 0.153451f, 0.993946f, 0.177811f, 0.514947f, 0.094744f, 0.446849f, 0.496650f, 0.453134f, 0.176580f, 0.097344f, 0.091321f,\n0.008072f, 0.496264f, 0.260029f, 0.037524f, 0.137625f, 0.440107f, 0.447468f, 0.174831f, 0.219884f, 0.169567f, 0.244816f, 0.303200f, 0.524091f, 0.149254f, 0.172839f, 0.747513f, -0.056158f, 0.428141f, 0.133288f, 0.010466f, -0.051281f, 0.131548f,\n0.212252f, 0.499144f, 0.135011f, 0.660147f, 0.706255f, 0.321864f, 0.462798f, 0.348411f, 0.090428f, 0.379782f, 0.095190f, 0.426078f, 0.376165f, 0.543914f, 0.210753f, 0.570881f, 0.077481f, 0.280857f, 0.058534f, 0.351292f, 0.207525f, 0.379646f};\n            Layer1W = new float[]{-1.151803f, 3.299508f, 1.384379f, -0.915938f, -4.037308f, 3.259468f, -0.666531f, 2.035537f, -3.307597f, 0.545178f, -2.362362f, -0.121552f, 2.312576f, -0.793848f, -0.624760f, 2.130977f, 1.083904f, -4.912477f, 2.266617f,\n0.031942f, -2.318484f, 1.895474f, -1.078704f, -3.960744f, 0.850847f, -1.852601f, -2.731431f, -0.477014f, 1.222331f, -2.237187f, 1.518114f, -0.410913f, -1.517604f, -0.583300f, -0.551160f, 0.666683f, -1.899984f, -2.810947f, 0.487714f, 1.448159f,\n-1.347014f, -0.162519f, 1.023178f, -0.344349f, -0.167036f, -2.306927f, 4.271956f, -2.795612f, 3.310055f, 1.583438f, -7.273885f, 2.656440f, 0.500476f, 2.315176f, 1.055844f, -0.252948f, -2.530690f, 1.067932f, 0.668538f, 0.191437f, -0.980457f,\n-0.532657f, -0.724451f, 1.109928f, -0.012884f, -0.513783f, -0.334224f, 1.925913f, 3.050774f, 2.474341f, -1.053723f, 0.259057f, -4.627946f, 1.278306f, -0.941788f, -0.556247f, -1.243682f, -0.430617f, -2.100966f, -1.370052f, 0.116465f, -0.132778f,\n0.364513f, 0.595484f, -1.691812f, 0.729092f, 0.729594f, 0.790154f, -0.269065f, 2.159101f, -1.475299f, -3.327104f, 2.402238f, -0.889444f, 0.665850f, 1.945030f, -1.753531f, 2.825061f, -3.584015f, 0.002843f, -4.038966f, 4.557799f, 1.385107f, 0.923644f,\n-7.651406f, -5.570621f, 4.224836f, 2.905856f, 3.161937f, -5.163118f, -1.781844f, 4.503354f, -3.907394f, -0.150091f, 4.034368f, -2.005664f, -5.688086f, 4.191846f, -2.785901f, -1.658463f, -2.438126f, -0.475097f, -0.916291f, -3.340523f, 1.200828f,\n1.413788f, 1.168296f, -0.368259f, -0.807506f, -0.654206f, 1.262669f, 1.161564f, -0.959412f, 2.063789f, -0.395787f, 1.271726f, -2.041284f, 1.004234f, 1.743822f, -2.240048f, -1.195843f, -4.395821f, 1.057930f, -0.366601f, 0.826976f, -1.706676f,\n2.713841f, -2.696195f, 1.667748f, -1.572079f, 1.382020f, 1.376361f, -3.672316f, 0.129653f, -0.543521f, 0.568704f, 0.041426f, -2.561728f, 0.263397f, 0.011603f, 0.524843f, -2.162633f, 0.415664f, -0.287552f, 1.718796f, -0.237750f, -0.735111f,\n-0.102162f, -2.105016f, -1.683676f, -0.423446f, 1.246798f, -1.925310f, 0.146068f, 0.589268f, 0.714847f, -0.588677f, 1.929749f, -4.731515f, 0.614072f, -2.506357f, 3.698083f, -0.313633f, 2.335595f, -2.108468f, -1.814789f, -2.879633f, -0.478878f,\n2.347243f, 1.114030f, -3.517518f, -3.637872f, 2.172626f, -4.276884f, 2.961876f, 1.635867f, 2.139488f, 0.379021f, -0.189803f, 2.550737f, 2.072362f, -4.680473f, -2.284149f, -1.894630f, 0.377250f, -1.066801f, 1.431091f, 1.408150f, -0.736469f,\n-0.030709f, 2.927471f, -3.168015f, 3.940945f, 2.595241f, -3.482076f, -2.350898f, -0.529506f, -0.759713f, 1.279438f, 0.046537f, 0.102916f, -1.312352f, 1.094847f, 3.458839f, -4.546166f, 3.405723f, -2.401025f, -0.595847f, -0.519512f, 2.325109f,\n-6.174557f, -1.002537f, -0.323943f, 0.938029f, -0.802085f, -4.430468f, -2.875613f, 2.086883f, 1.953121f, 1.470135f, -3.879002f, -3.012677f, -0.066699f, -1.371595f, 1.427735f, 1.095305f, 1.233924f, -1.949454f, -1.384813f, 0.223109f, 1.429426f,\n-0.216306f, -1.435646f, 0.140079f, 1.512557f, 3.299783f, -4.228608f, -3.842099f, -4.521607f, 2.357085f, 5.079347f, -2.250724f, -4.274155f, 2.053325f, -0.487095f, 0.463109f, -1.228636f, -2.285873f, -0.543949f, 1.850452f, -2.054068f, 1.561726f,\n0.892100f, 1.855124f, -3.364179f, -0.171803f, -2.696741f, -1.159818f, 1.081886f, -1.093993f, 0.179641f, 0.480389f, -1.038202f, 0.719384f, 0.261489f, -1.459803f, -0.863665f, 1.089309f, -1.859376f, -2.875892f, 2.501139f, -0.549666f, 2.022165f,\n1.049928f, -2.699066f, 1.053724f, 0.450503f, -0.415949f, 1.807813f, -2.325222f, -1.640730f, 1.429567f, 0.497127f, 0.413492f, -1.424472f, -0.859760f, 0.556748f, 0.671014f, -0.297033f, -2.282755f, -0.766319f, -0.707628f, -0.310733f, 0.750747f,\n-0.096767f, 0.189350f, 0.845886f, 0.142565f, -1.636177f, -0.182118f, 0.765884f, 3.822864f, 2.075416f, -1.681949f, 2.907514f, -6.277675f, 1.374721f, 0.950113f, -4.336537f, 0.985603f, 0.055372f, 1.773129f, -1.737413f, 5.155190f, -1.109998f, 0.969455f,\n3.358132f, -5.849509f, -3.097102f, -7.380907f, 1.599235f, -0.096597f, -0.266791f, 1.686465f, -0.322232f, 0.994186f, -4.343148f, 2.182240f, -2.754916f, -0.624214f, 1.594094f, 0.329099f, -0.019967f, -1.339209f, 1.340312f, 1.481827f, 0.051060f,\n-1.923796f, 1.627442f, 0.474731f, 0.134888f, -3.958225f, -0.658535f, -0.280225f, 1.306040f, 2.404527f, 0.331476f, 2.663683f, -5.834100f, 3.821070f, -2.447892f, -5.324117f, -0.429684f, 1.925440f, -0.460667f, -3.343439f, 3.481241f, 1.575440f,\n0.828242f, 1.769185f, -5.027261f, -1.721213f, 0.193550f, 2.873634f, 0.525453f, -0.241833f, -2.201338f, -1.031211f, 0.302656f, -4.110111f, 1.889586f, -0.010220f, 1.535408f, 0.005630f, 0.695398f, -7.765825f, -0.222457f, -3.491589f, -5.621642f,\n-3.909984f, 3.412124f, 2.283319f, -2.559297f, 2.810708f, 2.654808f, -2.186603f, 0.120480f, -0.418090f, -1.078496f, -0.096212f, 0.728882f, -0.553637f, -1.166497f, 0.104761f, 0.462757f, 1.214830f, -0.949030f, 0.272697f, -0.134049f, 2.209681f,\n-2.492700f, 1.915471f, 1.789672f, 2.864246f, -4.608607f, 1.683043f, -1.584636f, -0.593515f, 1.253198f, -0.214488f, 1.201259f, 4.288817f, -2.952058f, 1.560066f, -5.474204f, -0.466739f, 2.857129f, -3.579098f, 5.507315f, -0.893194f, -0.205329f,\n0.143943f, -1.373932f, -4.510856f, 1.188551f, 0.006668f, 1.868596f, 2.368564f, -0.296390f, -5.864704f, 1.525710f, -0.269232f, 1.321202f, -0.118354f, 1.885763f, 1.622588f, -1.133829f, -4.092876f, 1.667455f, 2.112401f, 0.623572f, -4.138431f,\n-0.167733f, 1.580948f, 2.493111f, 1.254483f, -2.485621f, -0.095388f, -4.128543f, 1.635785f, 0.364778f, -0.964579f, 1.862457f, -0.266531f, -3.458249f, -0.866497f, -0.079146f, -2.586182f, -1.508283f, 3.192587f, 0.287699f, -0.001557f, -3.517248f,\n-0.959553f, 0.126876f, -1.276711f, 0.023541f, -5.690566f, 3.022251f, 1.209585f, 1.201692f, -4.227297f, -2.122573f, 3.316189f, 1.414794f, -0.230890f, -2.274623f, -1.566565f, -0.850273f, 1.127301f, -0.283382f, 1.066143f, -2.224574f, 0.122261f,\n0.982320f, 0.896203f, -1.460140f, -2.717263f, 1.270611f, -1.072049f, -0.993244f, -2.396916f, 0.324349f, -4.358601f, 1.019200f, 1.650873f, 1.687477f, 0.091120f, 2.552365f, 1.999675f, 2.609327f, -4.088093f, 3.327652f, 0.899261f, 0.449615f, -7.797306f,\n0.444245f, 0.935478f, -0.684649f, 2.361475f, -2.809600f, -0.551700f, -0.020341f, 2.502141f, 1.643797f, -3.264505f, -3.748740f, 0.355763f, 1.989702f, -0.756512f, -0.300758f, -2.268583f, 0.703524f, 1.399695f, -0.022017f, 0.665550f, -1.739692f,\n-0.472647f, -0.170627f, 1.154826f, -0.427195f, 1.670203f, 0.513326f, -4.208340f, -0.419054f, -1.712727f, 1.174221f, 1.567104f, 0.742150f, -5.713912f, 1.037415f, -0.406848f, 2.932940f, 2.346552f, 1.214878f, 1.804234f, -1.914275f, -2.525795f,\n2.701623f, -1.329223f, -4.783916f, 1.304469f, 0.007475f, 0.138524f, 2.042417f, 3.075576f, -0.705730f, -3.303264f, -4.420027f, 2.376708f, 3.264640f, -3.148494f, -2.517074f, -0.309562f, 3.174340f, 0.644657f, 2.373495f, 1.891853f, -1.248580f,\n-3.812518f, 0.364138f, -2.399663f, 0.891565f, 2.388509f, -0.353715f, -1.817018f, 0.764085f, 0.767326f, 0.562955f, -1.160952f, -1.054858f, -0.549985f, 1.244088f, 1.132388f, 0.308018f, -0.261858f, 3.209691f, 2.215304f, 2.645727f, -4.021620f, 3.458095f,\n-3.841556f, 2.219002f, -2.869892f, -0.638565f, -0.831955f, -0.308201f, -2.659582f, 1.063774f, 1.104872f, 0.194112f, 0.463142f, -1.037185f, -0.396068f, 0.729191f, 0.685953f, -1.334369f, -1.297683f, -1.872200f, 1.758857f, 0.260394f, 1.983498f,\n-2.142646f, -2.309995f, 1.170436f, 0.632415f, 2.047192f, -0.972298f, -1.336401f, -4.316562f, 4.685286f, 5.088614f, 0.764675f, -5.364276f, -4.659016f, 1.653980f, 1.876605f, -6.366984f, -1.456787f, -0.359427f, -1.396966f, 0.690273f, -2.420941f,\n0.514960f, 0.292541f, 0.275753f, 0.376612f, 0.286730f, 0.790013f, -2.449168f, -0.370846f, -3.707654f, -6.293461f, -3.793969f, 2.530932f, -3.936139f, 4.137885f, 4.237751f, -5.553079f, 5.055491f, 2.200235f, -0.858498f, -0.253292f, -2.605131f,\n1.149007f, -0.590716f, 0.884673f, 0.641239f, -2.064553f, -0.072878f, 0.196192f, 0.403070f, -0.458813f, 1.016360f, -2.319632f, -0.959365f, 0.108068f, 0.458530f, 0.468267f, -1.341523f, 0.542485f, -2.374014f, 1.856118f, -0.661564f, 1.874679f,\n-3.179983f, -3.261835f, 0.053124f, 1.814632f, 1.442643f, -1.056749f, -1.795378f, 2.297905f, -2.957956f};\n            Layer1B = new float[] { -0.257535f, -0.215613f, -0.141758f, 0.059927f, 0.033932f, -0.267358f, 0.185377f, -0.086039f, 0.245971f, 0.199849f, 0.133600f };\n        }\n    }\n}\n"
  },
  {
    "path": "sources/vision/ScannerBase.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Drawing;\n\nnamespace FFTriadBuddy\n{\n    public class ScannerBase\n    {\n        public class GameStateBase\n        {\n        }\n\n        protected ScreenAnalyzer screenAnalyzer;\n        protected bool debugMode;\n\n        public List<Rectangle> debugShapes;\n        public List<ImageUtils.HashPreview> debugHashes;\n        public GameStateBase cachedGameStateBase;\n\n        public void Initialize(ScreenAnalyzer parent)\n        {\n            screenAnalyzer = parent;\n            debugShapes = new List<Rectangle>();\n            debugHashes = new List<ImageUtils.HashPreview>();\n            debugMode = false;\n        }\n\n        public virtual void InvalidateCache()\n        {\n            cachedGameStateBase = null;\n        }\n\n        public virtual bool HasValidCache(FastBitmapHSV bitmap, int scannerFlags)\n        {\n            return false;\n        }\n\n        public virtual bool DoWork(FastBitmapHSV bitmap, int scannerFlags, Stopwatch perfTimer, bool debugMode)\n        {\n            this.debugMode = debugMode;\n            debugShapes.Clear();\n            debugHashes.Clear();\n\n            return false;\n        }\n\n        public virtual void AppendDebugShapes(List<Rectangle> shapes, List<ImageUtils.HashPreview> hashes)\n        {\n            shapes.AddRange(debugShapes);\n            hashes.AddRange(debugHashes);\n        }\n\n        public virtual void ValidateScan(string configPath, ScreenAnalyzer.EMode mode, MLDataExporter dataExporter)\n        {\n            throw new Exception(\"Scanner doesn't support tests!\");\n        }\n    }\n}\n"
  },
  {
    "path": "sources/vision/ScannerCactpot.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Drawing;\nusing System.IO;\n\nnamespace FFTriadBuddy\n{\n    public class ScannerCactpot : ScannerBase\n    {\n        public class GameState : GameStateBase\n        {\n            public int[] board;\n            public int numRevealed;\n\n            public GameState()\n            {\n                board = new int[9] { 0, 0, 0, 0, 0, 0, 0, 0, 0 };\n                numRevealed = 0;\n            }\n        }\n\n        private Rectangle cachedBoard;\n        private Rectangle[] cachedCircles;\n        private int cachedOffsetIn;\n\n        public GameState cachedGameState;\n\n        private FastPixelMatch colorMatchBack = new FastPixelMatchMono(0, 100);\n        private FastPixelMatch colorMatchCircleFade = new FastPixelMatchHueMono(30, 60, 40, 140);\n        private FastPixelMatch colorMatchCircleFadeH = new FastPixelMatchHueMono(160, 200, 40, 255);\n        private FastPixelMatch colorMatchCircleOut = new FastPixelMatchHueMono(30, 60, 80, 255);\n        private FastPixelMatch colorMatchCircleOutH = new FastPixelMatchHueMono(160, 200, 80, 255);\n        private FastPixelMatch colorMatchCircleIn = new FastPixelMatchMono(110, 255);\n        private MLClassifierCactpot classifierCactpot;\n\n        public ScannerCactpot()\n        {\n            classifierCactpot = new MLClassifierCactpot();\n            classifierCactpot.InitializeModel();\n        }\n\n        public override void InvalidateCache()\n        {\n            base.InvalidateCache();\n            cachedBoard = Rectangle.Empty;\n            cachedCircles = null;\n            cachedGameState = null;\n        }\n\n        public override bool HasValidCache(FastBitmapHSV bitmap, int scannerFlags)\n        {\n            return (cachedBoard.Width > 0) && (cachedCircles != null) && HasCactpotMatch(bitmap, cachedBoard.Left, cachedBoard.Top, cachedBoard.Width / 3, cachedOffsetIn);\n        }\n\n        public Rectangle GetBoardBox() { return cachedBoard; }\n        public Rectangle GetCircleBox(int idx) { return cachedCircles[idx]; }\n\n        public override void AppendDebugShapes(List<Rectangle> shapes, List<ImageUtils.HashPreview> hashes)\n        {\n            base.AppendDebugShapes(shapes, hashes);\n            if (cachedCircles != null) { shapes.AddRange(cachedCircles); }\n        }\n\n        public override bool DoWork(FastBitmapHSV bitmap, int scannerFlags, Stopwatch perfTimer, bool debugMode)\n        {\n            base.DoWork(bitmap, scannerFlags, perfTimer, debugMode);\n            perfTimer.Restart();\n\n            if (!HasValidCache(bitmap, scannerFlags))\n            {\n                cachedCircles = null;\n                cachedBoard = FindCactpotCoords(bitmap);\n\n                if (cachedBoard.Width > 0)\n                {\n                    screenAnalyzer.currentScanArea = cachedBoard;\n                    cachedCircles = FindCactpotCircleCoords(cachedBoard);\n                }\n            }\n\n            cachedGameState = null;\n            cachedGameStateBase = null;\n\n            if (cachedCircles != null)\n            {\n                screenAnalyzer.ClearKnownHashes();\n                cachedGameState = new GameState();\n                cachedGameStateBase = cachedGameState;\n\n                for (int Idx = 0; Idx < cachedGameState.board.Length; Idx++)\n                {\n                    cachedGameState.board[Idx] = ParseCactpotCircle(bitmap, cachedCircles[Idx], \"board\" + Idx);\n                    cachedGameState.numRevealed += (cachedGameState.board[Idx] != 0) ? 1 : 0;\n                }\n            }\n\n            perfTimer.Stop();\n            if (debugMode) { Logger.WriteLine(\"Parse cactpot board: \" + perfTimer.ElapsedMilliseconds + \"ms\"); }\n            return cachedGameState != null;\n        }\n\n        #region Image scan\n\n        private Rectangle FindCactpotCoords(FastBitmapHSV bitmap)\n        {\n            Rectangle rect = new Rectangle();\n\n            // find number fields\n            //\n            // scan method similar to triple triad board with matching top left corner and tile size\n\n            int minCellSize = bitmap.Width * 2 / 100;\n            int maxCellSize = bitmap.Width * 6 / 100;\n            int maxScanX = bitmap.Width - (maxCellSize * 3) - 20;\n            int maxScanY = bitmap.Height - (maxCellSize * 2) - 20;\n\n            Stopwatch stopwatch = new Stopwatch();\n            stopwatch.Start();\n\n            bool[] highlights = new bool[9];\n            bool bScanning = true;\n            for (int IdxY = 0; IdxY < maxScanY && bScanning; IdxY++)\n            {\n                int ScanY = (bitmap.Height / 2) + ((IdxY / 2) * ((IdxY % 2 == 0) ? 1 : -1));\n                for (int IdxX = 10; IdxX < maxScanX && bScanning; IdxX++)\n                {\n                    bool debugGridMatch = false;\n                    //debugGridMatch = (IdxX == 585) && (ScanY == 495);\n                    if (HasCactpotCircleEdgeV(bitmap, IdxX, ScanY, -1, debugGridMatch))\n                    {\n                        for (int cellSize = minCellSize; cellSize < maxCellSize && bScanning; cellSize++)\n                        {\n                            if (HasCactpotCellMatch(bitmap, IdxX, ScanY, cellSize, out int CellPosX, out int CellPosY, out int offsetIn, debugGridMatch))\n                            {\n                                if (HasCactpotCircleMatch(bitmap, CellPosX, CellPosY, cellSize, offsetIn, debugGridMatch))\n                                {\n                                    if (HasCactpotMatch(bitmap, CellPosX, CellPosY, cellSize, offsetIn, debugGridMatch))\n                                    {\n                                        rect = new Rectangle(CellPosX, CellPosY, cellSize * 3, cellSize * 3);\n                                        cachedOffsetIn = offsetIn;\n\n                                        bScanning = false;\n                                        break;\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n\n            stopwatch.Stop();\n            if (debugMode) { Logger.WriteLine(\"FindCactpotCoords: \" + stopwatch.ElapsedMilliseconds + \"ms\"); }\n\n            return rect;\n        }\n\n        private bool HasCactpotCircleMatch(FastBitmapHSV bitmap, int posX, int posY, int cellSize, int offsetIn, bool bDebugDetection = false)\n        {\n            int sizeA = cellSize;\n            int offsetB = Math.Max(5, offsetIn - 2);\n            int sizeB = sizeA - (offsetB * 2);\n\n            Point[] testPoints = new Point[4];\n            int numHighlights = 0;\n\n            // 4 points: corners of cell => dark background\n            {\n                testPoints[0].X = posX; testPoints[0].Y = posY;\n                testPoints[1].X = posX + sizeA; testPoints[1].Y = posY;\n                testPoints[2].X = posX; testPoints[2].Y = posY + sizeA;\n                testPoints[3].X = posX + sizeA; testPoints[3].Y = posY + sizeA;\n\n                for (int Idx = 0; Idx < 4; Idx++)\n                {\n                    FastPixelHSV testPx = bitmap.GetPixel(testPoints[Idx].X, testPoints[Idx].Y);\n                    //if (bDebugDetection) { Logger.WriteLine(\"[\" + posX + \", \" + posY + \"] testing A[\" + testPoints[Idx].X + \",\" + testPoints[Idx].Y + \"]: \" + testPx); }\n\n                    if (!colorMatchBack.IsMatching(testPx))\n                    {\n                        if (bDebugDetection) { Logger.WriteLine(\"[\" + posX + \", \" + posY + \"] failed, not background: A[\" + testPoints[Idx].X + \",\" + testPoints[Idx].Y + \"]: \" + testPx); }\n                        return false;\n                    }\n                }\n            }\n\n            // 4 points: corners with offset B => dark background\n            {\n                testPoints[0].X = posX + offsetB; testPoints[0].Y = posY + offsetB;\n                testPoints[1].X = posX + sizeA - offsetB; testPoints[1].Y = posY + offsetB;\n                testPoints[2].X = posX + offsetB; testPoints[2].Y = posY + sizeA - offsetB;\n                testPoints[3].X = posX + sizeA - offsetB; testPoints[3].Y = posY + sizeA - offsetB;\n\n                for (int Idx = 0; Idx < 4; Idx++)\n                {\n                    FastPixelHSV testPx = bitmap.GetPixel(testPoints[Idx].X, testPoints[Idx].Y);\n                    //if (bDebugDetection) { Logger.WriteLine(\"[\" + posX + \", \" + posY + \"] testing B1[\" + testPoints[Idx].X + \",\" + testPoints[Idx].Y + \"]: \" + testPx); }\n\n                    if (!colorMatchBack.IsMatching(testPx))\n                    {\n                        if (bDebugDetection) { Logger.WriteLine(\"[\" + posX + \", \" + posY + \"] failed, not background: B1[\" + testPoints[Idx].X + \",\" + testPoints[Idx].Y + \"]: \" + testPx); }\n                        return false;\n                    }\n                }\n            }\n\n            Point[] testPointCloser = new Point[4];\n\n            // 4 points: midpoints with offset B => yellow/white\n            {\n                testPoints[0].X = posX + offsetB; testPoints[0].Y = posY + (sizeA / 2);\n                testPoints[1].X = posX + sizeA - offsetB; testPoints[1].Y = posY + (sizeA / 2);\n                testPoints[2].X = posX + (sizeA / 2); testPoints[2].Y = posY + offsetB;\n                testPoints[3].X = posX + (sizeA / 2); testPoints[3].Y = posY + sizeA - offsetB;\n\n                for (int Idx = 0; Idx < 4; Idx++)\n                {\n                    FastPixelHSV testPx = bitmap.GetPixel(testPoints[Idx].X, testPoints[Idx].Y);\n                    //if (bDebugDetection) { Logger.WriteLine(\"[\" + posX + \", \" + posY + \"] testing B2[\" + testPoints[Idx].X + \",\" + testPoints[Idx].Y + \"]: \" + testPx); }\n\n                    if (colorMatchCircleOutH.IsMatching(testPx))\n                    {\n                        numHighlights++;\n                    }\n                    else if (!colorMatchCircleOut.IsMatching(testPx))\n                    {\n                        if (bDebugDetection) { Logger.WriteLine(\"[\" + posX + \", \" + posY + \"] failed, not circle: B2[\" + testPoints[Idx].X + \",\" + testPoints[Idx].Y + \"]: \" + testPx); }\n                        return false;\n                    }\n                }\n            }\n\n            // 4 points: midpoints of diagonal lines between midpoints B (C) => yellow/white \n            {\n                testPoints[0].X = posX + offsetB + (sizeB / 4); testPoints[0].Y = posY + offsetB + (sizeB / 4);\n                testPoints[1].X = posX + sizeA - offsetB - (sizeB / 4); testPoints[1].Y = posY + offsetB + (sizeB / 4);\n                testPoints[2].X = posX + offsetB + (sizeB / 4); testPoints[2].Y = posY + sizeA - offsetB - (sizeB / 4);\n                testPoints[3].X = posX + sizeA - offsetB - (sizeB / 4); testPoints[3].Y = posY + sizeA - offsetB - (sizeB / 4);\n\n                for (int Idx = 0; Idx < 4; Idx++)\n                {\n                    FastPixelHSV testPx = bitmap.GetPixel(testPoints[Idx].X, testPoints[Idx].Y);\n                    //if (bDebugDetection) { Logger.WriteLine(\"[\" + posX + \", \" + posY + \"] testing B3[\" + testPoints[Idx].X + \",\" + testPoints[Idx].Y + \"]: \" + testPx); }\n\n                    if (!colorMatchCircleIn.IsMatching(testPx))\n                    {\n                        if (bDebugDetection) { Logger.WriteLine(\"[\" + posX + \", \" + posY + \"] failed, not circle: B3[\" + testPoints[Idx].X + \",\" + testPoints[Idx].Y + \"]: \" + testPx); }\n                        return false;\n                    }\n                }\n            }\n\n            return (numHighlights == 0) || (numHighlights == 4);\n        }\n\n        private bool HasCactpotMatch(FastBitmapHSV bitmap, int posX, int posY, int cellSize, int offsetIn, bool bDebugDetection = false)\n        {\n            if (bDebugDetection) { Logger.WriteLine(\"HasCactpotMatch[\" + posX + \", \" + posY + \"]? testing...\"); }\n\n            // circle at (posX, posY) already matched, test other 8\n            bool bHasMatch = true;\n            bool bHasMatch01 = (bHasMatch || bDebugDetection) && HasCactpotCircleMatch(bitmap, posX + cellSize, posY, cellSize, offsetIn, bDebugDetection);\n            bHasMatch = bHasMatch && bHasMatch01;\n            bool bHasMatch02 = (bHasMatch || bDebugDetection) && HasCactpotCircleMatch(bitmap, posX + (cellSize * 2), posY, cellSize, offsetIn, bDebugDetection);\n            bHasMatch = bHasMatch && bHasMatch02;\n\n            bool bHasMatch10 = (bHasMatch || bDebugDetection) && HasCactpotCircleMatch(bitmap, posX, posY + cellSize, cellSize, offsetIn, bDebugDetection);\n            bHasMatch = bHasMatch && bHasMatch10;\n            bool bHasMatch11 = (bHasMatch || bDebugDetection) && HasCactpotCircleMatch(bitmap, posX + cellSize, posY + cellSize, cellSize, offsetIn, bDebugDetection);\n            bHasMatch = bHasMatch && bHasMatch11;\n            bool bHasMatch12 = (bHasMatch || bDebugDetection) && HasCactpotCircleMatch(bitmap, posX + (cellSize * 2), posY + cellSize, cellSize, offsetIn, bDebugDetection);\n            bHasMatch = bHasMatch && bHasMatch12;\n\n            bool bHasMatch20 = (bHasMatch || bDebugDetection) && HasCactpotCircleMatch(bitmap, posX, posY + (cellSize * 2), cellSize, offsetIn, bDebugDetection);\n            bHasMatch = bHasMatch && bHasMatch20;\n            bool bHasMatch21 = (bHasMatch || bDebugDetection) && HasCactpotCircleMatch(bitmap, posX + cellSize, posY + (cellSize * 2), cellSize, offsetIn, bDebugDetection);\n            bHasMatch = bHasMatch && bHasMatch21;\n            bool bHasMatch22 = (bHasMatch || bDebugDetection) && HasCactpotCircleMatch(bitmap, posX + (cellSize * 2), posY + (cellSize * 2), cellSize, offsetIn, bDebugDetection);\n            bHasMatch = bHasMatch && bHasMatch22;\n\n            if (bDebugDetection)\n            {\n                Logger.WriteLine(\">> [#\" + (bHasMatch01 ? \"#\" : \".\") + (bHasMatch02 ? \"#\" : \".\") +\n                    \"][\" + (bHasMatch10 ? \"#\" : \".\") + (bHasMatch11 ? \"#\" : \".\") + (bHasMatch12 ? \"#\" : \".\") +\n                    \"][\" + (bHasMatch20 ? \"#\" : \".\") + (bHasMatch21 ? \"#\" : \".\") + (bHasMatch22 ? \"#\" : \".\") +\n                    \"] => \" + (bHasMatch ? \"match\" : \"nope\"));\n            }\n\n            return bHasMatch;\n        }\n\n        private bool HasCactpotCircleEdgeV(FastBitmapHSV bitmap, int posX, int posY, int sideX, bool bDebugDetection = false)\n        {\n            const int spreadY = 2;\n            const int offsetDeepX = 5;\n\n            FastPixelHSV testPx = bitmap.GetPixel(posX, posY);\n            if (!colorMatchCircleOut.IsMatching(testPx) && !colorMatchCircleOutH.IsMatching(testPx))\n            {\n                if (bDebugDetection) { Logger.WriteLine(\"HasCactpotCircleEdgeV[\" + posX + \",\" + posY + \"] failed: edge center \" + testPx); }\n                return false;\n            }\n\n            testPx = bitmap.GetPixel(posX - (sideX * offsetDeepX), posY);\n            if (!colorMatchCircleOut.IsMatching(testPx) && !colorMatchCircleOutH.IsMatching(testPx))\n            {\n                if (bDebugDetection) { Logger.WriteLine(\"HasCactpotCircleEdgeV[\" + posX + \",\" + posY + \"] failed: deep center \" + testPx); }\n                return false;\n            }\n\n            testPx = bitmap.GetPixel(posX, posY - spreadY);\n            if (!colorMatchCircleOut.IsMatching(testPx) && !colorMatchCircleOutH.IsMatching(testPx))\n            {\n                if (bDebugDetection) { Logger.WriteLine(\"HasCactpotCircleEdgeV[\" + posX + \",\" + posY + \"] failed: edge top \" + testPx); }\n                return false;\n            }\n\n            testPx = bitmap.GetPixel(posX, posY + spreadY);\n            if (!colorMatchCircleOut.IsMatching(testPx) && !colorMatchCircleOutH.IsMatching(testPx))\n            {\n                if (bDebugDetection) { Logger.WriteLine(\"HasCactpotCircleEdgeV[\" + posX + \",\" + posY + \"] failed: edge bottom \" + testPx); }\n                return false;\n            }\n\n            testPx = bitmap.GetPixel(posX + sideX, posY);\n            if (!colorMatchBack.IsMatching(testPx) && !colorMatchCircleFade.IsMatching(testPx) && !colorMatchCircleFadeH.IsMatching(testPx))\n            {\n                if (bDebugDetection) { Logger.WriteLine(\"HasCactpotCircleEdgeV[\" + posX + \",\" + posY + \"] failed: edge center prev \" + testPx); }\n                return false;\n            }\n\n            testPx = bitmap.GetPixel(posX + sideX, posY - spreadY);\n            if (!colorMatchBack.IsMatching(testPx) && !colorMatchCircleFade.IsMatching(testPx) && !colorMatchCircleFadeH.IsMatching(testPx))\n            {\n                if (bDebugDetection) { Logger.WriteLine(\"HasCactpotCircleEdgeV[\" + posX + \",\" + posY + \"] failed: edge top prev \" + testPx); }\n                return false;\n            }\n\n            testPx = bitmap.GetPixel(posX + sideX, posY + spreadY);\n            if (!colorMatchBack.IsMatching(testPx) && !colorMatchCircleFade.IsMatching(testPx) && !colorMatchCircleFadeH.IsMatching(testPx))\n            {\n                if (bDebugDetection) { Logger.WriteLine(\"HasCactpotCircleEdgeV[\" + posX + \",\" + posY + \"] failed: edge bottom prev \" + testPx); }\n                return false;\n            }\n\n            return true;\n        }\n\n        private bool HasCactpotCellMatch(FastBitmapHSV bitmap, int posX, int posY, int cellSize, out int cellPosX, out int cellPosY, out int offsetIn, bool bDebugDetection = false)\n        {\n            cellPosX = 0;\n            cellPosY = 0;\n            offsetIn = 0;\n\n            bool bHasEdgeV2 = HasCactpotCircleEdgeV(bitmap, posX + cellSize, posY, -1);\n            if (bDebugDetection) { Logger.WriteLine(\"HasCactpotCellMatch[\" + posX + \", \" + posY + \"], cellSize:\" + cellSize + \" => \" + (bHasEdgeV2 ? \"found V2\" : \"nope\")); }\n\n            if (bHasEdgeV2)\n            {\n                for (int Idx = 5; Idx < 20; Idx++)\n                {\n                    int invPosX = posX + cellSize - Idx;\n                    bool bHasEdgeV3 = HasCactpotCircleEdgeV(bitmap, invPosX, posY, 1);\n                    if (bHasEdgeV3)\n                    {\n                        cellPosX = posX - (Idx / 2);\n                        cellPosY = posY - (cellSize / 2);\n                        offsetIn = Idx;\n\n                        if (bDebugDetection) { Logger.WriteLine(\">> spacing check: V3 at X:{0}, offsetIn:{1}\", invPosX, offsetIn); }\n                        return true;\n                    }\n                }\n            }\n\n            return false;\n        }\n\n        private Rectangle[] FindCactpotCircleCoords(Rectangle cactpotBox)\n        {\n            Rectangle[] circleBoxes = new Rectangle[9];\n            int cellSize = cactpotBox.Width / 3;\n\n            for (int IdxY = 0; IdxY < 3; IdxY++)\n            {\n                for (int IdxX = 0; IdxX < 3; IdxX++)\n                {\n                    circleBoxes[IdxX + (IdxY * 3)] = new Rectangle(cactpotBox.Left + (IdxX * cellSize), cactpotBox.Top + (IdxY * cellSize), cellSize, cellSize);\n                }\n            }\n\n            return circleBoxes;\n        }\n\n        private Rectangle GetCirclePatternBounds(Rectangle circleBox)\n        {\n            return new Rectangle(circleBox.X + (circleBox.Width / 4), circleBox.Y + (circleBox.Height / 4), circleBox.Width / 2, circleBox.Height / 2);\n        }\n\n        private float[] ExtractCirclePattern(FastBitmapHSV bitmap, Rectangle circleBox)\n        {\n            return ImageUtils.ExtractImageFeaturesScaled(bitmap, GetCirclePatternBounds(circleBox), 8, 16, ImageUtils.GetPixelFeaturesMono);\n        }\n\n        private int ParseCactpotCircle(FastBitmapHSV bitmap, Rectangle circleBox, string debugName)\n        {\n            float[] values = ExtractCirclePattern(bitmap, circleBox);\n            int result;\n\n            ImageHashData pattern = new ImageHashData() { type = EImageHashType.Cactpot, previewBounds = circleBox, isKnown = true };\n            pattern.CalculateHash(values);\n\n            // allow overwrites in case there is a user defined value, must be exact match\n            ImageHashData overridePattern = ImageHashDB.Get().FindExactMatch(pattern);\n            if (overridePattern != null)\n            {\n                result = (int)overridePattern.ownerOb;\n            }\n            else\n            {\n                result = classifierCactpot.Calculate(values, out float DummyPct);\n                pattern.isAuto = true;\n            }\n\n            pattern.ownerOb = result;\n            screenAnalyzer.AddImageHash(pattern);\n\n            Rectangle previewBounds = new Rectangle(circleBox.X + circleBox.Width, circleBox.Y, 8, 16);\n            debugHashes.Add(new ImageUtils.HashPreview() { hashValues = values, bounds = previewBounds });\n            debugShapes.Add(GetCirclePatternBounds(circleBox));\n\n            return result;\n        }\n\n        #endregion\n\n        #region Validation\n\n        public override void ValidateScan(string configPath, ScreenAnalyzer.EMode mode, MLDataExporter dataExporter)\n        {\n            string testName = Path.GetFileNameWithoutExtension(configPath);\n\n            GameState validationState = LoadValidationConfig(configPath);\n            if (validationState == null || cachedGameState == null)\n            {\n                string exceptionMsg = string.Format(\"Test {0} failed! Scan results:{1}, config path: {2}\", testName, cachedGameState, configPath);\n                throw new Exception(exceptionMsg);\n            }\n\n            if (dataExporter != null && cachedCircles != null)\n            {\n                int numPatterns = 0;\n                for (int idx = 0; idx < 9; idx++)\n                {\n                    // generate additional sample images by offseting source bounds -5..5 px in each direction\n                    // numbers only, empty field doesn't have enough details to care\n                    if (validationState.board[idx] > 0)\n                    {\n                        for (int offsetX = -5; offsetX <= 5; offsetX++)\n                        {\n                            for (int offsetY = -5; offsetY <= 5; offsetY++)\n                            {\n                                Rectangle offsetBounds = cachedCircles[idx];\n                                offsetBounds.Offset(offsetX, offsetY);\n\n                                float[] exportValues = ExtractCirclePattern(screenAnalyzer.cachedFastBitmap, offsetBounds);\n                                dataExporter.ExportValues(exportValues, validationState.board[idx]);\n                                numPatterns++;\n                            }\n                        }\n                    }\n                    else\n                    {\n                        float[] exportValues = ExtractCirclePattern(screenAnalyzer.cachedFastBitmap, cachedCircles[idx]);\n                        dataExporter.ExportValues(exportValues, validationState.board[idx]);\n                        numPatterns++;\n                    }\n                }\n\n                Logger.WriteLine(\"Exported ML entries:{0}\", numPatterns);\n            }\n\n            for (int idx = 0; idx < 9; idx++)\n            {\n                if (cachedGameState.board[idx] != validationState.board[idx])\n                {\n                    string exceptionMsg = string.Format(\"Test {0} failed! Board[{1}] got:{2}, expected:{3}\",\n                        testName, idx,\n                        cachedGameState.board[idx],\n                        validationState.board[idx]);\n                    throw new Exception(exceptionMsg);\n                    //Logger.WriteLine(exceptionMsg);\n                }\n            }\n        }\n\n        private GameState LoadValidationConfig(string configPath)\n        {\n            GameState configData = new GameState();\n            string configText = File.ReadAllText(configPath);\n\n            JsonParser.ObjectValue rootOb = JsonParser.ParseJson(configText);\n\n            JsonParser.ArrayValue ruleArr = rootOb.entries[\"board\"] as JsonParser.ArrayValue;\n            configData.board = new int[ruleArr.entries.Count];\n            for (int idx = 0; idx < ruleArr.entries.Count; idx++)\n            {\n                configData.board[idx] = ruleArr.entries[idx] as JsonParser.IntValue;\n            }\n\n            return configData;\n        }\n\n        #endregion\n    }\n}\n"
  },
  {
    "path": "sources/vision/ScannerTriad.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Drawing;\nusing System.IO;\n\nnamespace FFTriadBuddy\n{\n    public class ScannerTriad : ScannerBase\n    {\n        public enum ETurnState\n        {\n            MissingTimer,\n            Waiting,\n            Active,\n        }\n\n        public class GameState : GameStateBase\n        {\n            public TriadCard[] board;\n            public ETriadCardOwner[] boardOwner;\n            public TriadCard[] blueDeck;\n            public TriadCard[] redDeck;\n            public TriadCard forcedBlueCard;\n            public List<TriadGameModifier> mods;\n            public ETurnState turnState;\n\n            public GameState()\n            {\n                board = new TriadCard[9];\n                boardOwner = new ETriadCardOwner[9];\n                blueDeck = new TriadCard[5];\n                redDeck = new TriadCard[5];\n                forcedBlueCard = null;\n                mods = new List<TriadGameModifier>();\n                turnState = ETurnState.MissingTimer;\n            }\n        }\n\n        public enum ECardLocation\n        {\n            BlueDeck,\n            RedDeck,\n            Board,\n        }\n\n        public enum ECardState\n        {\n            None,\n            Hidden,\n            Locked,\n            Visible,\n            PlacedRed,\n            PlacedBlue,\n        }\n\n        public class CardState : IComparable<CardState>\n        {\n            public struct SideInfo\n            {\n                public float matchPct;\n                public int matchNum;\n                public bool hasOverride;\n                public Rectangle scanBox;\n                public float[] hashValues;\n            }\n\n            public TriadCard card;\n            public string name;\n            public int[] sideNumber;\n            public bool failedMatching;\n\n            public ECardState state;\n            public ECardLocation location;\n            public int locationContext;\n            public Rectangle scanBox;\n            public Rectangle bounds;\n            public Bitmap sourceImage;\n            public ImageHashData cardImageHash;\n            public SideInfo[] sideInfo;\n\n            public int CompareTo(CardState other)\n            {\n                return (failedMatching != other.failedMatching) ? (failedMatching ? -1 : 1) :\n                    (location != other.location) ? location.CompareTo(other.location) :\n                    locationContext.CompareTo(other.locationContext);\n            }\n\n            public override string ToString()\n            {\n                return string.Format(\"{0}: {1} [{2}]\", name, card, state);\n            }\n        }\n\n        [Flags]\n        enum ESide\n        {\n            None = 0,\n            Up = 1,\n            Down = 2,\n            Left = 4,\n            Right = 8,\n        }\n\n        [Flags]\n        public enum EScanMode\n        {\n            Default = 0,\n            TimerOnly = 1,\n        }\n\n        public enum EScanError\n        {\n            NoErrors,\n            Aborted,\n            MissingGrid,\n            MissingCards,\n            FailedCardMatching,\n        }\n\n        private Rectangle cachedGridBox;\n        private Rectangle cachedRuleBox;\n        private Rectangle cachedTimerBox;\n        private Rectangle cachedTimerScanBox;\n        private Rectangle[] cachedBlueCards;\n        private Rectangle[] cachedRedCards;\n        private Rectangle[] cachedBoardCards;\n\n        public GameState cachedGameState;\n        public List<CardState> cachedCardState = new List<CardState>();\n        public EScanError cachedScanError = EScanError.NoErrors;\n\n        private static Size digitHashSize = new Size(10, 10);\n\n        private FastPixelMatch colorMatchGridBorder = new FastPixelMatchMono(0, 150);\n        private FastPixelMatch colorMatchGridField = new FastPixelMatchMono(150, 255);\n        private FastPixelMatch colorMatchRuleBox = new FastPixelMatchMono(20, 80);\n        private FastPixelMatch colorMatchRuleText = new FastPixelMatchMono(150, 255);\n        private FastPixelMatch colorMatchCardBorder = new FastPixelMatchHSV(20, 40, 0, 100, 0, 100);\n        private FastPixelMatch colorMatchCardNumber = new FastPixelMatchMono(220, 255);\n        private FastPixelMatch colorMatchCardOwnerRed1 = new FastPixelMatchHueMono(0, 30, 150, 255);\n        private FastPixelMatch colorMatchCardOwnerRed2 = new FastPixelMatchHueMono(330, 360, 150, 255);\n        private FastPixelMatch colorMatchCardOwnerBlue = new FastPixelMatchHueMono(200, 280, 150, 255);\n        private FastPixelMatch colorMatchTimerBox = new FastPixelMatchHSV(40, 60, 5, 40, 0, 100);\n        private FastPixelMatch colorMatchTimerActive = new FastPixelMatchMono(80, 255);\n        private MLClassifierTriadDigit classifierTriadDigit;\n\n        public ScannerTriad()\n        {\n            classifierTriadDigit = new MLClassifierTriadDigit();\n            classifierTriadDigit.InitializeModel();\n        }\n\n        public override void InvalidateCache()\n        {\n            base.InvalidateCache();\n            cachedGridBox = Rectangle.Empty;\n            cachedBlueCards = null;\n            cachedRedCards = null;\n            cachedBoardCards = null;\n            cachedGameState = null;\n        }\n\n        public override bool HasValidCache(FastBitmapHSV bitmap, int scannerFlags)\n        {\n            if ((scannerFlags & (int)EScanMode.TimerOnly) != 0)\n            {\n                return true;\n            }\n\n            return cachedGridBox.Width > 0 && HasGridMatch(bitmap, cachedGridBox.Left, cachedGridBox.Top, cachedGridBox.Width / 3);\n        }\n\n        public Rectangle GetBoardBox() { return cachedGridBox; }\n        public Rectangle GetRuleBox() { return cachedRuleBox; }\n        public Rectangle GetTimerScanBox() { return cachedTimerScanBox; }\n        public Rectangle GetBlueCardBox(int idx) { return cachedBlueCards[idx]; }\n        public Rectangle GetBoardCardBox(int idx) { return cachedBoardCards[idx]; }\n        public static Size GetDigitHashSize() { return digitHashSize; }\n\n        public override void AppendDebugShapes(List<Rectangle> shapes, List<ImageUtils.HashPreview> hashes)\n        {\n            base.AppendDebugShapes(shapes, hashes);\n            if (cachedGridBox.Width > 0) { shapes.Add(cachedGridBox); }\n            if (cachedRuleBox.Width > 0) { shapes.Add(cachedRuleBox); }\n            if (cachedTimerBox.Width > 0) { shapes.Add(cachedTimerBox); }\n            if (cachedBlueCards != null) { shapes.AddRange(cachedBlueCards); }\n            if (cachedRedCards != null) { shapes.AddRange(cachedRedCards); }\n            if (cachedBoardCards != null) { shapes.AddRange(cachedBoardCards); }\n        }\n\n        public override bool DoWork(FastBitmapHSV bitmap, int scannerFlags, Stopwatch perfTimer, bool debugMode)\n        {\n            base.DoWork(bitmap, scannerFlags, perfTimer, debugMode);\n            perfTimer.Restart();\n\n            bool scanResult = true;\n            cachedScanError = EScanError.NoErrors;\n\n            bool isTimerOnly = (scannerFlags & (int)EScanMode.TimerOnly) != 0;\n            if (isTimerOnly)\n            {\n                if (cachedGameState != null)\n                {\n                    Rectangle fastTimerBox = (cachedTimerScanBox.Width > 0) ? FindTimerBox(bitmap, cachedTimerScanBox) : Rectangle.Empty;\n                    if (fastTimerBox.Width > 0)\n                    {\n                        debugShapes.Add(fastTimerBox);\n\n                        bool bHasActiveTimer = ParseTimer(bitmap, fastTimerBox);\n                        cachedGameState.turnState = bHasActiveTimer ? ETurnState.Active : ETurnState.Waiting;\n                    }\n                    else\n                    {\n                        cachedGameState.turnState = ETurnState.MissingTimer;\n                    }\n                }\n            }\n            else\n            {\n                cachedScanError = EScanError.MissingGrid;\n\n                if (!HasValidCache(bitmap, scannerFlags))\n                {\n                    cachedBlueCards = null;\n                    cachedRedCards = null;\n                    cachedBoardCards = null;\n\n                    cachedGridBox = FindGridCoords(bitmap);\n                    if (cachedGridBox.Width > 0)\n                    {\n                        cachedRuleBox = FindRuleBoxCoords(bitmap, cachedGridBox);\n                        cachedTimerBox = FindTimerBox(bitmap, cachedGridBox, cachedRuleBox);\n                        cachedTimerScanBox = new Rectangle(cachedTimerBox.X, cachedTimerBox.Y - 20, cachedTimerBox.Width, cachedTimerBox.Height + 40);\n                        Rectangle scanAreaBox = new Rectangle(cachedGridBox.Left - (cachedGridBox.Width * 85 / 100),\n                            cachedGridBox.Top - (cachedGridBox.Height * 5 / 100),\n                            cachedGridBox.Width * 270 / 100,\n                            cachedGridBox.Height * 110 / 100);\n\n                        cachedBlueCards = FindBlueCardCoords(bitmap, cachedGridBox);\n                        if (cachedBlueCards != null && cachedBlueCards.Length == 5)\n                        {\n                            cachedRedCards = FindRedCardCoords(cachedGridBox, cachedBlueCards);\n                            cachedBoardCards = FindBoardCardCoords(cachedGridBox, cachedBlueCards);\n                            screenAnalyzer.currentScanArea = scanAreaBox;\n\n                            // mark as aborted, will be overwritten when completed\n                            cachedScanError = EScanError.Aborted;\n                        }\n                        else\n                        {\n                            cachedScanError = EScanError.MissingCards;\n                        }\n                    }\n                    else\n                    {\n                        scanResult = false;\n                    }\n                }\n\n                cachedGameStateBase = null;\n                cachedGameState = null;\n\n                if (cachedBoardCards != null)\n                {\n                    screenAnalyzer.ClearKnownHashes();\n                    cachedGameState = new GameState();\n                    cachedGameStateBase = cachedGameState;\n\n                    ParseRules(bitmap, cachedRuleBox, cachedGameState.mods);\n\n                    bool bCanContinue = (screenAnalyzer.GetCurrentState() != ScreenAnalyzer.EState.UnknownHash) || debugMode;\n                    if (bCanContinue)\n                    {\n                        cachedScanError = EScanError.NoErrors;\n                        cachedCardState.Clear();\n\n                        {\n                            perfTimer.Restart();\n\n                            bool hasLockedBlueCards = false;\n                            for (int Idx = 0; Idx < 5; Idx++)\n                            {\n                                CardState blueCardState = ParseCard(bitmap, cachedBlueCards[Idx], \"blue\" + Idx, ECardLocation.BlueDeck);\n                                blueCardState.locationContext = Idx;\n                                cachedCardState.Add(blueCardState);\n\n                                CardState redCardState = ParseCard(bitmap, cachedRedCards[Idx], \"red\" + Idx, ECardLocation.RedDeck);\n                                redCardState.location = ECardLocation.RedDeck;\n                                redCardState.locationContext = Idx;\n                                cachedCardState.Add(redCardState);\n\n                                cachedGameState.blueDeck[Idx] = blueCardState.card;\n                                cachedGameState.redDeck[Idx] = redCardState.card;\n                                if (blueCardState.state == ECardState.Locked)\n                                {\n                                    hasLockedBlueCards = true;\n                                }\n                            }\n\n                            cachedGameState.forcedBlueCard = null;\n                            if (hasLockedBlueCards)\n                            {\n                                for (int Idx = 0; Idx < cachedCardState.Count; Idx++)\n                                {\n                                    if (cachedCardState[Idx].card != null &&\n                                        cachedCardState[Idx].state == ECardState.Visible &&\n                                        cachedCardState[Idx].location == ECardLocation.BlueDeck)\n                                    {\n                                        cachedGameState.forcedBlueCard = cachedCardState[Idx].card;\n                                        break;\n                                    }\n                                }\n                            }\n\n                            perfTimer.Stop();\n                            if (debugMode) { Logger.WriteLine(\"Parse decks: \" + perfTimer.ElapsedMilliseconds + \"ms\"); }\n                        }\n\n                        {\n                            perfTimer.Restart();\n\n                            for (int Idx = 0; Idx < 9; Idx++)\n                            {\n                                CardState boardCardState = ParseCard(bitmap, cachedBoardCards[Idx], \"board\" + Idx, ECardLocation.Board);\n                                boardCardState.locationContext = Idx;\n\n                                cachedGameState.board[Idx] = boardCardState.card;\n                                if (boardCardState.state == ECardState.Visible || boardCardState.card != null)\n                                {\n                                    int gridCellLeft = cachedGridBox.Left + ((Idx % 3) * cachedGridBox.Width / 3);\n                                    cachedGameState.boardOwner[Idx] = ParseCardOwner(bitmap, cachedBoardCards[Idx], gridCellLeft, \"board\" + Idx);\n\n                                    boardCardState.state = (cachedGameState.boardOwner[Idx] == ETriadCardOwner.Blue) ? ECardState.PlacedBlue : ECardState.PlacedRed;\n                                }\n\n                                cachedCardState.Add(boardCardState);\n                            }\n\n                            perfTimer.Stop();\n                            if (debugMode) { Logger.WriteLine(\"Parse board: \" + perfTimer.ElapsedMilliseconds + \"ms\"); }\n                        }\n\n                        foreach (CardState cardState in cachedCardState)\n                        {\n                            if (cardState.failedMatching)\n                            {\n#if DEBUG\n                                if ((screenAnalyzer.currentMode & (ScreenAnalyzer.EMode.Debug | ScreenAnalyzer.EMode.AutoTest)) == ScreenAnalyzer.EMode.None)\n                                {\n                                    // auto collect failed card matching\n                                    string autoSavePath = Path.Combine(screenAnalyzer.GetDefaultScreenshotPath(), \"failed-match-\");\n                                    for (int idx = 1; idx < 1000000; idx++)\n                                    {\n                                        string testPath = autoSavePath + idx + \".jpg\";\n                                        if (!File.Exists(testPath))\n                                        {\n                                            Logger.WriteLine(\"Saving failed match screenshot: \" + Path.GetFileName(testPath));\n                                            screenAnalyzer.screenReader.cachedScreenshot.Save(testPath);\n                                            break;\n                                        }\n                                    }\n                                }\n#endif // DEBUG\n\n                                cachedScanError = EScanError.FailedCardMatching;\n                                break;\n                            }\n                        }\n                    }\n                }\n\n                // at this point, mini game was recognized, but some error occured - stop checking other scanners\n                if (scanResult && cachedScanError != EScanError.NoErrors)\n                {\n                    screenAnalyzer.OnScannerError();\n                }\n            }\n\n            perfTimer.Stop();\n            if (debugMode) { Logger.WriteLine(\"Parse triad board: \" + perfTimer.ElapsedMilliseconds + \"ms\"); }\n            return scanResult;\n        }\n\n        #region Image scan\n\n        private Rectangle FindGridCoords(FastBitmapHSV bitmap)\n        {\n            Rectangle GridRect = new Rectangle();\n\n            // detect intersections of grid lines by color match around that point\n            //\n            // 1. find mid-upper left vertex of grid\n            // 2. adjust Y to be left top corner of grid\n            // 3. check if grid is matching given tile size\n\n            int minTileSize = Math.Min(bitmap.Width, bitmap.Height) * 5 / 100;\n            int maxTileSize = Math.Max(bitmap.Width, bitmap.Height) * 15 / 100;\n            int maxScanX = bitmap.Width - (minTileSize * 3) - 20;\n            int maxScanY = bitmap.Height - (minTileSize * 2) - 20;\n\n            Stopwatch stopwatch = new Stopwatch();\n            stopwatch.Start();\n\n            // since it's all centered by default, scan from center of image\n            bool bScanning = true;\n            for (int IdxY = 0; IdxY < maxScanY && bScanning; IdxY++)\n            {\n                int ScanY = (bitmap.Height / 2) + ((IdxY / 2) * ((IdxY % 2 == 0) ? 1 : -1));\n                for (int IdxX = 0; IdxX < maxScanX && bScanning; IdxX++)\n                {\n                    if (HasGridVertexMatch(bitmap, IdxX, ScanY, ESide.Up | ESide.Down | ESide.Right))\n                    {\n                        for (int IdxTileSize = minTileSize; IdxTileSize < maxTileSize; IdxTileSize++)\n                        {\n                            int maxTileScanX = IdxX + (3 * IdxTileSize) + 10;\n                            int maxTileScanY = ScanY + (2 * IdxTileSize) + 10;\n                            int minTileScanY = IdxTileSize + 10;\n\n                            if (maxTileScanX >= bitmap.Width || maxTileScanY >= bitmap.Height || ScanY < minTileScanY)\n                            {\n                                break;\n                            }\n\n                            if (HasGridMatch(bitmap, IdxX, ScanY - IdxTileSize, IdxTileSize))\n                            {\n                                GridRect = new Rectangle(IdxX, ScanY - IdxTileSize, IdxTileSize * 3, IdxTileSize * 3);\n                                bScanning = false;\n                                break;\n                            }\n                        }\n                    }\n                }\n            }\n\n            stopwatch.Stop();\n            if (debugMode) { Logger.WriteLine(\"FindGridCoords: \" + stopwatch.ElapsedMilliseconds + \"ms\"); }\n            if (Logger.IsSuperVerbose()) { Logger.WriteLine(\"FindGridCoords: \" + GridRect); }\n\n            return GridRect;\n        }\n\n        private bool HasGridVertexMatch(FastBitmapHSV bitmap, int posX, int posY, ESide sides, bool bDebugDetection = false)\n        {\n            const int vertexTestSpacing = 8;\n\n            FastPixelHSV testPx = bitmap.GetPixel(posX, posY);\n            if (!colorMatchGridBorder.IsMatching(testPx))\n            {\n                if (bDebugDetection) { Logger.WriteLine(\"vertex [\" + posX + \", \" + posY + \"] failed: at vertex \" + testPx); }\n                return false;\n            }\n\n            if ((sides & ESide.Up) != ESide.None)\n            {\n                FastPixelHSV testPxBU = bitmap.GetPixel(posX, posY - vertexTestSpacing);\n                if (!colorMatchGridBorder.IsMatching(testPxBU))\n                {\n                    if (bDebugDetection) { Logger.WriteLine(\"vertex [\" + posX + \", \" + posY + \"] failed: border up \" + testPxBU); }\n                    return false;\n                }\n\n                if ((sides & ESide.Left) != ESide.None)\n                {\n                    FastPixelHSV testPxFUL = bitmap.GetPixel(posX - vertexTestSpacing, posY - vertexTestSpacing);\n                    if (!colorMatchGridField.IsMatching(testPxFUL))\n                    {\n                        if (bDebugDetection) { Logger.WriteLine(\"vertex [\" + posX + \", \" + posY + \"] failed: field up-left \" + testPxFUL); }\n                        return false;\n                    }\n                }\n\n                if ((sides & ESide.Right) != ESide.None)\n                {\n                    FastPixelHSV testPxFUR = bitmap.GetPixel(posX + vertexTestSpacing, posY - vertexTestSpacing);\n                    if (!colorMatchGridField.IsMatching(testPxFUR))\n                    {\n                        if (bDebugDetection) { Logger.WriteLine(\"vertex [\" + posX + \", \" + posY + \"] failed: field up-right \" + testPxFUR); }\n                        return false;\n                    }\n                }\n            }\n\n            if ((sides & ESide.Down) != ESide.None)\n            {\n                FastPixelHSV testPxBD = bitmap.GetPixel(posX, posY + vertexTestSpacing);\n                if (!colorMatchGridBorder.IsMatching(testPxBD))\n                {\n                    if (bDebugDetection) { Logger.WriteLine(\"vertex [\" + posX + \", \" + posY + \"] failed: border down \" + testPxBD); }\n                    return false;\n                }\n\n                if ((sides & ESide.Left) != ESide.None)\n                {\n                    FastPixelHSV testPxFDL = bitmap.GetPixel(posX - vertexTestSpacing, posY + vertexTestSpacing);\n                    if (!colorMatchGridField.IsMatching(testPxFDL))\n                    {\n                        if (bDebugDetection) { Logger.WriteLine(\"vertex [\" + posX + \", \" + posY + \"] failed: field down-left \" + testPxFDL); }\n                        return false;\n                    }\n                }\n\n                if ((sides & ESide.Right) != ESide.None)\n                {\n                    FastPixelHSV testPxFDR = bitmap.GetPixel(posX + vertexTestSpacing, posY + vertexTestSpacing);\n                    if (!colorMatchGridField.IsMatching(testPxFDR))\n                    {\n                        if (bDebugDetection) { Logger.WriteLine(\"vertex [\" + posX + \", \" + posY + \"] failed: field down-right \" + testPxFDR); }\n                        return false;\n                    }\n                }\n            }\n\n            if ((sides & ESide.Left) != ESide.None)\n            {\n                FastPixelHSV testPxBL = bitmap.GetPixel(posX - vertexTestSpacing, posY);\n                if (!colorMatchGridBorder.IsMatching(testPxBL))\n                {\n                    if (bDebugDetection) { Logger.WriteLine(\"vertex [\" + posX + \", \" + posY + \"] failed: border left \" + testPxBL); }\n                    return false;\n                }\n            }\n\n            if ((sides & ESide.Right) != ESide.None)\n            {\n                FastPixelHSV testPxBR = bitmap.GetPixel(posX + vertexTestSpacing, posY);\n                if (!colorMatchGridBorder.IsMatching(testPxBR))\n                {\n                    if (bDebugDetection) { Logger.WriteLine(\"vertex [\" + posX + \", \" + posY + \"] failed: border right \" + testPxBR); }\n                    return false;\n                }\n            }\n\n            if (bDebugDetection) { Logger.WriteLine(\"vertex [\" + posX + \", \" + posY + \"] passed (anchors: \" + sides + \")\"); }\n            return true;\n        }\n\n        private bool HasGridMatch(FastBitmapHSV bitmap, int posX, int posY, int cellSize, bool bDebugDetection = false)\n        {\n            bool bMatch = false;\n\n            if (bDebugDetection) { Logger.WriteLine(\"HasGridMatch at [\" + posX + \", \" + posY + \"], tile: \" + cellSize + \"...\"); }\n\n            // find:\n            // - edges, except corners (rounded)\n            // - at least 1 from middle\n\n            int NumEdgeVerts = 1 +\n                // left edge, ignore upper\n                (HasGridVertexMatch(bitmap, posX, posY + (cellSize * 2), ESide.Up | ESide.Down | ESide.Right, bDebugDetection) ? 1 : 0) +\n                // top edge\n                (HasGridVertexMatch(bitmap, posX + cellSize, posY, ESide.Left | ESide.Down | ESide.Right, bDebugDetection) ? 1 : 0) +\n                (HasGridVertexMatch(bitmap, posX + (cellSize * 2), posY, ESide.Left | ESide.Down | ESide.Right, bDebugDetection) ? 1 : 0) +\n                // right edge\n                (HasGridVertexMatch(bitmap, posX + (cellSize * 3), posY + cellSize, ESide.Up | ESide.Down | ESide.Left, bDebugDetection) ? 1 : 0) +\n                (HasGridVertexMatch(bitmap, posX + (cellSize * 3), posY + (cellSize * 2), ESide.Up | ESide.Down | ESide.Left, bDebugDetection) ? 1 : 0) +\n                // bottom edge\n                (HasGridVertexMatch(bitmap, posX + cellSize, posY + (cellSize * 3), ESide.Left | ESide.Up | ESide.Right, bDebugDetection) ? 1 : 0) +\n                (HasGridVertexMatch(bitmap, posX + (cellSize * 2), posY + (cellSize * 3), ESide.Left | ESide.Up | ESide.Right, bDebugDetection) ? 1 : 0);\n\n            if (NumEdgeVerts >= 6)\n            {\n                bool bHasAnyMidVert =\n                    HasGridVertexMatch(bitmap, posX + cellSize, posY + cellSize, ESide.Left | ESide.Right | ESide.Up | ESide.Down, bDebugDetection) ||\n                    HasGridVertexMatch(bitmap, posX + (cellSize * 2), posY + cellSize, ESide.Left | ESide.Right | ESide.Up | ESide.Down, bDebugDetection) ||\n                    HasGridVertexMatch(bitmap, posX + cellSize, posY + (cellSize * 2), ESide.Left | ESide.Right | ESide.Up | ESide.Down, bDebugDetection) ||\n                    HasGridVertexMatch(bitmap, posX + (cellSize * 2), posY + (cellSize * 2), ESide.Left | ESide.Right | ESide.Up | ESide.Down, bDebugDetection);\n\n                if (bHasAnyMidVert)\n                {\n                    bMatch = true;\n                }\n            }\n\n            return bMatch;\n        }\n\n        private Rectangle FindRuleBoxCoords(FastBitmapHSV bitmap, Rectangle gridRect)\n        {\n            Rectangle RuleBoxRect = new Rectangle();\n\n            // 1. scan line from grid's top right to the right\n            // 2. find saturation edges\n            // 3. run vertical line and do the same\n\n            Stopwatch stopwatch = new Stopwatch();\n            stopwatch.Start();\n\n            int traceStart = gridRect.Right + 10;\n            int traceEnd = Math.Min(bitmap.Width, gridRect.Right + gridRect.Width);\n            int traceY = gridRect.Top + (gridRect.Height / 9);\n\n            int AttemptIdx = 0;\n            bool bShouldRetry = true;\n            bool bUsingLowScan = false;\n            while (bShouldRetry)\n            {\n                bShouldRetry = false;\n                AttemptIdx++;\n\n                List<int> segPosH = ImageUtils.TraceLineSegments(bitmap, traceStart, traceY, 1, 0, traceEnd - traceStart, colorMatchRuleBox, 20, 2);\n                if (segPosH.Count == 2)\n                {\n                    int lenSegH = segPosH[1] - segPosH[0];\n                    if (lenSegH < (gridRect.Width / 3))\n                    {\n                        traceY = gridRect.Top + (gridRect.Height * 15 / 100);\n                        traceStart += 50;\n                        bShouldRetry = true;\n                        bUsingLowScan = true;\n                        continue;\n                    }\n\n                    int traceEndY0 = gridRect.Top - (gridRect.Height / 15);\n\n                    List<int> segPosVT = ImageUtils.TraceLineSegments(bitmap, segPosH[0] + 50, traceY, 0, -1, traceY - traceEndY0, colorMatchRuleBox, 5, 2);\n                    if (segPosVT.Count == 2)\n                    {\n                        int traceEndY1 = gridRect.Top + (gridRect.Height / 6);\n\n                        List<int> segPosVB = ImageUtils.TraceLineSegments(bitmap, segPosH[0] + 50, traceY, 0, 1, traceEndY1 - traceY, colorMatchRuleBox, 5, 2);\n                        if (segPosVB.Count == 2 || bUsingLowScan)\n                        {\n                            int ruleBoxWidth = segPosH[1] - segPosH[0];\n                            int ruleBoxHeight = (segPosVB.Count == 2 ? segPosVB[1] : traceY) - segPosVT[1];\n                            float ruleBoxRatio = (float)ruleBoxWidth / ruleBoxHeight;\n                            float expectedRatio = 3.15f;\n                            float diff = Math.Abs(ruleBoxRatio - expectedRatio);\n                            if (diff < 0.25f)\n                            {\n                                RuleBoxRect = new Rectangle(segPosH[0], segPosVT[1], ruleBoxWidth, ruleBoxHeight);\n                            }\n                            else if (AttemptIdx < 10)\n                            {\n                                bShouldRetry = true;\n                                traceY -= 2;\n                            }\n                        }\n                    }\n                }\n            }\n\n            // failsafe: assume predefined location\n            // may not be accurate, but it's much better than having no rules scanned\n            if (RuleBoxRect.Width <= 0)\n            {\n                RuleBoxRect = new Rectangle(\n                    gridRect.Left + (gridRect.Width * 115 / 100),\n                    gridRect.Top - (gridRect.Height * 5 / 100),\n                    gridRect.Width * 64 / 100,\n                    gridRect.Height * 20 / 100\n                    );\n            }\n\n            stopwatch.Stop();\n            if (debugMode) { Logger.WriteLine(\"FindRuleBoxCoords: \" + stopwatch.ElapsedMilliseconds + \"ms\"); }\n            if (Logger.IsSuperVerbose()) { Logger.WriteLine(\"FindRuleBoxCoords: \" + RuleBoxRect); }\n\n            return RuleBoxRect;\n        }\n\n        private Rectangle FindTimerBox(FastBitmapHSV bitmap, Rectangle gridRect, Rectangle ruleRect)\n        {\n            Rectangle result = new Rectangle();\n            int gridMidX = (gridRect.Left + gridRect.Right) / 2;\n            int ruleMidX = (ruleRect.Left + ruleRect.Right) / 2;\n            int traceStartX = gridMidX - (ruleMidX - gridMidX);\n            int traceStartY = ruleRect.Bottom;\n\n            bool bHasTop = ImageUtils.TraceLine(bitmap, traceStartX, traceStartY, 0, 1, gridRect.Height / 3, colorMatchTimerBox, out Point hitTop);\n            if (bHasTop)\n            {\n                bool bHasBottom = ImageUtils.TraceLine(bitmap, traceStartX, hitTop.Y + 10, 0, -1, 10, colorMatchTimerBox, out Point hitBottom);\n                if (bHasBottom)\n                {\n                    int boxRight = gridMidX - (ruleRect.Left - gridMidX);\n                    int boxWidth = ruleRect.Width * 80 / 100;\n                    result = new Rectangle(boxRight - boxWidth, hitTop.Y + 1, boxWidth, hitBottom.Y - hitTop.Y - 2);\n                }\n            }\n\n            if (Logger.IsSuperVerbose()) { Logger.WriteLine(\"FindTimerBox: \" + result); }\n            return result;\n        }\n\n        private Rectangle FindTimerBox(FastBitmapHSV bitmap, Rectangle timerScanRect)\n        {\n            Rectangle result = new Rectangle();\n            int traceStartX = timerScanRect.Width / 2;\n            bool bHasTop = ImageUtils.TraceLine(bitmap, traceStartX, 0, 0, 1, timerScanRect.Height * 2 / 3, colorMatchTimerBox, out Point hitTop);\n            if (bHasTop)\n            {\n                bool bHasBottom = ImageUtils.TraceLine(bitmap, traceStartX, hitTop.Y + 10, 0, -1, 10, colorMatchTimerBox, out Point hitBottom);\n                if (bHasBottom)\n                {\n                    result = new Rectangle(0, hitTop.Y, timerScanRect.Width, hitBottom.Y - hitTop.Y);\n                }\n            }\n\n            if (Logger.IsSuperVerbose()) { Logger.WriteLine(\"FindTimerBox: \" + result); }\n            return result;\n        }\n\n        private Rectangle[] FindBlueCardCoords(FastBitmapHSV bitmap, Rectangle gridRect)\n        {\n            Rectangle[] result = null;\n\n            Stopwatch stopwatch = new Stopwatch();\n            stopwatch.Start();\n\n            Size approxCardSize = new Size((gridRect.Width / 3) * 80 / 100, (gridRect.Height / 3));\n            int midLineTop = (gridRect.Top + gridRect.Bottom) / 2;\n            int midLineBottom = gridRect.Top + (gridRect.Height * 84 / 100);\n            int offsetTop = approxCardSize.Width * 70 / 100;\n            int offsetBottom = approxCardSize.Width * 120 / 100;\n            int traceLen = approxCardSize.Width / 6;\n\n            Point[] scanAnchors = new Point[5]\n            {\n                new Point(gridRect.Left - offsetTop - (approxCardSize.Width * 2), midLineTop),\n                new Point(gridRect.Left - offsetTop - approxCardSize.Width, midLineTop),\n                new Point(gridRect.Left - offsetTop, midLineTop),\n                new Point(gridRect.Left - offsetBottom - approxCardSize.Width, midLineBottom),\n                new Point(gridRect.Left - offsetBottom, midLineBottom),\n            };\n\n            Rectangle[] scanRect = new Rectangle[5];\n\n            int numFoundCards = 0;\n            for (int Idx = 0; Idx < scanAnchors.Length; Idx++)\n            {\n                bool bHasHit0 = ImageUtils.TraceLine(bitmap, scanAnchors[Idx].X, scanAnchors[Idx].Y - (approxCardSize.Height / 2), 0, 1, traceLen, colorMatchCardBorder, out Point hitTop);\n                bool bHasHit1 = ImageUtils.TraceLine(bitmap, scanAnchors[Idx].X, scanAnchors[Idx].Y + (approxCardSize.Height / 2), 0, -1, traceLen, colorMatchCardBorder, out Point hitBottom);\n                bool bHasHit2 = ImageUtils.TraceLine(bitmap, scanAnchors[Idx].X - (approxCardSize.Width / 2), scanAnchors[Idx].Y, 1, 0, traceLen, colorMatchCardBorder, out Point hitLeft);\n                bool bHasHit3 = ImageUtils.TraceLine(bitmap, scanAnchors[Idx].X + (approxCardSize.Width / 2), scanAnchors[Idx].Y, -1, 0, traceLen, colorMatchCardBorder, out Point hitRight);\n\n                if (bHasHit0 && bHasHit1 && bHasHit2 && bHasHit3)\n                {\n                    scanRect[Idx] = new Rectangle(hitLeft.X - scanAnchors[Idx].X, hitTop.Y - scanAnchors[Idx].Y, hitRight.X - hitLeft.X, hitBottom.Y - hitTop.Y);\n                    //if (debugMode) { debugShapes.Add(new Rectangle(hitLeft.X, hitTop.Y, scanRect[Idx].Width, scanRect[Idx].Height)); }\n                    numFoundCards++;\n                }\n            }\n\n            if (numFoundCards > 0)\n            {\n                if (numFoundCards < 5)\n                {\n                    // find medians for each param of scanRect and use them for missing\n                    List<int> sortListX = new List<int>();\n                    List<int> sortListY = new List<int>();\n                    List<int> sortListW = new List<int>();\n                    List<int> sortListH = new List<int>();\n\n                    for (int Idx = 0; Idx < scanRect.Length; Idx++)\n                    {\n                        if (scanRect[Idx].Width > 0)\n                        {\n                            sortListX.Add(scanRect[Idx].X);\n                            sortListY.Add(scanRect[Idx].Y);\n                            sortListW.Add(scanRect[Idx].Width);\n                            sortListH.Add(scanRect[Idx].Height);\n                        }\n                    }\n\n                    sortListX.Sort();\n                    sortListY.Sort();\n                    sortListW.Sort();\n                    sortListH.Sort();\n\n                    int medianX = ((sortListX.Count % 2) == 0) ? ((sortListX[(sortListX.Count / 2) - 1] + sortListX[sortListX.Count / 2]) / 2) : sortListX[sortListX.Count / 2];\n                    int medianY = ((sortListY.Count % 2) == 0) ? ((sortListY[(sortListY.Count / 2) - 1] + sortListY[sortListY.Count / 2]) / 2) : sortListY[sortListY.Count / 2];\n                    int medianW = ((sortListW.Count % 2) == 0) ? ((sortListW[(sortListW.Count / 2) - 1] + sortListW[sortListW.Count / 2]) / 2) : sortListW[sortListW.Count / 2];\n                    int medianH = ((sortListH.Count % 2) == 0) ? ((sortListH[(sortListH.Count / 2) - 1] + sortListH[sortListH.Count / 2]) / 2) : sortListH[sortListH.Count / 2];\n\n                    for (int Idx = 0; Idx < scanRect.Length; Idx++)\n                    {\n                        if (scanRect[Idx].Width == 0)\n                        {\n                            scanRect[Idx] = new Rectangle(medianX, medianY, medianW, medianH);\n                        }\n                    }\n                }\n\n                result = new Rectangle[scanRect.Length];\n                for (int Idx = 0; Idx < scanRect.Length; Idx++)\n                {\n                    result[Idx] = new Rectangle(scanRect[Idx].X + scanAnchors[Idx].X, scanRect[Idx].Y + scanAnchors[Idx].Y, scanRect[Idx].Width, scanRect[Idx].Height);\n                }\n            }\n\n            stopwatch.Stop();\n            if (debugMode) { Logger.WriteLine(\"FindBuleCardCoords: \" + stopwatch.ElapsedMilliseconds + \"ms\"); }\n            if (Logger.IsSuperVerbose())\n            {\n                string desc = \"FindBuleCardCoords: \";\n                foreach (var r in result) { desc += r.ToString() + \" \"; }\n                Logger.WriteLine(desc);\n            }\n\n            return result;\n        }\n\n        private Rectangle[] FindRedCardCoords(Rectangle gridRect, Rectangle[] blueCards)\n        {\n            int gridAxisV = (gridRect.Right + gridRect.Left) / 2;\n\n            Rectangle[] result = new Rectangle[5]\n            {\n                new Rectangle(gridAxisV + (gridAxisV - blueCards[2].Right), blueCards[2].Top, blueCards[0].Width, blueCards[0].Height),\n                new Rectangle(gridAxisV + (gridAxisV - blueCards[1].Right), blueCards[1].Top, blueCards[0].Width, blueCards[0].Height),\n                new Rectangle(gridAxisV + (gridAxisV - blueCards[0].Right), blueCards[0].Top, blueCards[0].Width, blueCards[0].Height),\n                new Rectangle(gridAxisV + (gridAxisV - blueCards[4].Right), blueCards[4].Top, blueCards[0].Width, blueCards[0].Height),\n                new Rectangle(gridAxisV + (gridAxisV - blueCards[3].Right), blueCards[3].Top, blueCards[0].Width, blueCards[0].Height)\n            };\n\n            return result;\n        }\n\n        private Rectangle[] FindBoardCardCoords(Rectangle gridRect, Rectangle[] blueCards)\n        {\n            Rectangle[] result = new Rectangle[9];\n\n            int cardWidth = blueCards[0].Width;\n            int cardHeight = blueCards[0].Height;\n\n            int cellCenterX0 = gridRect.Left + (gridRect.Width / 6);\n            int cellCenterY0 = gridRect.Top + (gridRect.Height / 6);\n\n            for (int IdxY = 0; IdxY < 3; IdxY++)\n            {\n                for (int IdxX = 0; IdxX < 3; IdxX++)\n                {\n                    int cellCenterX = cellCenterX0 + (gridRect.Width / 3) * IdxX;\n                    int cellCenterY = cellCenterY0 + (gridRect.Height / 3) * IdxY;\n\n                    result[IdxX + (IdxY * 3)] = new Rectangle(cellCenterX - (cardWidth / 2), cellCenterY - (cardHeight / 2), cardWidth, cardHeight);\n                }\n            }\n\n            return result;\n        }\n\n        private void ParseRules(FastBitmapHSV bitmap, Rectangle rulesRect, List<TriadGameModifier> rules)\n        {\n            rules.Clear();\n\n            Stopwatch stopwatch = new Stopwatch();\n            stopwatch.Start();\n\n            Rectangle noTokenRect = new Rectangle(rulesRect.Left, rulesRect.Top, rulesRect.Width * 80 / 100, rulesRect.Height);\n            List<Point> spanV = ImageUtils.TraceSpansV(bitmap, noTokenRect, colorMatchRuleText, 5);\n            foreach (Point span in spanV)\n            {\n                Point boundsH = ImageUtils.TraceBoundsH(bitmap, new Rectangle(rulesRect.Left, span.X, rulesRect.Width, span.Y), colorMatchRuleText, span.Y * 2);\n                if (boundsH.Y > 5)\n                {\n                    const int hashWidth = 64;\n                    const int hashHeight = 8;\n\n                    Rectangle ruleTextBox = new Rectangle(boundsH.X, span.X, boundsH.Y, span.Y);\n                    float[] values = ImageUtils.ExtractImageFeaturesScaled(bitmap, ruleTextBox, hashWidth, hashHeight, testPx => colorMatchRuleText.IsMatching(testPx) ? 1.0f : 0.0f);\n\n                    ImageHashData rulePattern = new ImageHashData() { type = EImageHashType.Rule, previewBounds = ruleTextBox, previewContextBounds = rulesRect };\n                    rulePattern.CalculateHash(values);\n\n                    ImageHashData foundPattern = ImageHashDB.Get().FindBestMatch(rulePattern, 100, out int matchDistance);\n                    if (foundPattern != null)\n                    {\n                        rulePattern.ownerOb = foundPattern.ownerOb;\n                        rulePattern.matchDistance = matchDistance;\n                        rulePattern.isKnown = true;\n\n                        rules.Add((TriadGameModifier)foundPattern.ownerOb);\n                    }\n\n                    screenAnalyzer.AddImageHash(rulePattern);\n\n                    Rectangle previewBounds = new Rectangle(ruleTextBox.Right + 10, ruleTextBox.Top, hashWidth, hashHeight);\n                    debugHashes.Add(new ImageUtils.HashPreview() { hashValues = values, bounds = previewBounds });\n                }\n            }\n\n            stopwatch.Stop();\n            if (debugMode) { Logger.WriteLine(\"ParseRules: \" + stopwatch.ElapsedMilliseconds + \"ms\"); }\n        }\n\n        private bool FindExactCardBottom(FastBitmapHSV bitmap, Rectangle cardRect, string debugName, ECardLocation cardLocation, out int exactBottomY)\n        {\n            // look for edge of border, sample 4 points along card width for avg\n            int[] scanX = new int[4]{\n                    cardRect.Left + (cardRect.Width * 20 / 100),\n                    cardRect.Left + (cardRect.Width * 40 / 100),\n                    cardRect.Left + (cardRect.Width * 60 / 100),\n                    cardRect.Left + (cardRect.Width * 80 / 100)};\n\n            int scanEndY = cardRect.Bottom - 10;\n            int scanStartY = cardRect.Bottom + 5;\n\n            bool verboseDebug = screenAnalyzer.debugScannerContext == debugName;\n            if (cardLocation == ECardLocation.Board)\n            {\n                // find bottom of grid cell\n                if (ImageUtils.TraceLine(bitmap, cardRect.Left - 10, scanEndY, 0, 1, 20, colorMatchGridBorder, out Point hitPos, verboseDebug))\n                {\n                    scanStartY = hitPos.Y - 1;\n                }\n            }\n\n            int prevAvgMono = 0;\n            int prevAvgHue = 0;\n\n            for (int scanY = scanStartY; scanY > scanEndY; scanY--)\n            {\n                int avgMono = 0;\n                int avgHue = 0;\n                for (int idxX = 0; idxX < scanX.Length; idxX++)\n                {\n                    FastPixelHSV testPx = bitmap.GetPixel(scanX[idxX], scanY);\n                    avgMono += testPx.GetMonochrome();\n                    avgHue += testPx.GetHue();\n                }\n                avgMono /= scanX.Length;\n                avgHue /= scanX.Length;\n\n                if (prevAvgMono > 0)\n                {\n                    int diffMono = avgMono - prevAvgMono;\n                    int absDiffHue = Math.Abs(avgHue - prevAvgHue);\n\n                    // mono: no abs, look for going darker (low - high = negative)\n                    // hue: abs, jump in hue will be good indicator, esp on red/blue decks\n                    bool foundEdge = (diffMono < -30 && avgMono < 120) || (absDiffHue > 120);\n\n                    if (verboseDebug) { Logger.WriteLine(\"FindExactCardBottom[{0}] scan:{1} = [H:{2},M:{3}] vs prev[H:{4},M:{5}] => {6}\", debugName, scanY, avgHue, avgMono, prevAvgHue, prevAvgMono, foundEdge ? \" EDGE!\" : \"nope\"); }\n                    if (foundEdge)\n                    {\n                        exactBottomY = scanY;\n                        return true;\n                    }\n                }\n\n                prevAvgMono = avgMono;\n                prevAvgHue = avgHue;\n            }\n\n            exactBottomY = cardRect.Bottom;\n            return false;\n        }\n\n        private bool FindExactCardTop(FastBitmapHSV bitmap, Rectangle cardRect, string debugName, ECardLocation cardLocation, out int exactTopY)\n        {\n            // look for edge of border, sample 4 points along card width for avg\n            int[] scanX = new int[4]{\n                    cardRect.Left + (cardRect.Width * 20 / 100),\n                    cardRect.Left + (cardRect.Width * 40 / 100),\n                    cardRect.Left + (cardRect.Width * 60 / 100),\n                    cardRect.Left + (cardRect.Width * 80 / 100)};\n\n            int scanEndY = cardRect.Top + 10;\n            int scanStartY = cardRect.Top - 5;\n\n            bool verboseDebug = screenAnalyzer.debugScannerContext == debugName;\n            if (cardLocation == ECardLocation.Board)\n            {\n                // find top of grid cell\n                if (ImageUtils.TraceLine(bitmap, cardRect.Left - 10, scanEndY, 0, -1, 20, colorMatchGridBorder, out Point hitPos, verboseDebug))\n                {\n                    scanStartY = hitPos.Y + 1;\n                }\n            }\n\n            int prevAvgMono = 0;\n            int prevAvgHue = 0;\n\n            for (int scanY = scanStartY; scanY < scanEndY; scanY++)\n            {\n                int avgMono = 0;\n                int avgHue = 0;\n                for (int idxX = 0; idxX < scanX.Length; idxX++)\n                {\n                    FastPixelHSV testPx = bitmap.GetPixel(scanX[idxX], scanY);\n                    avgMono += testPx.GetMonochrome();\n                    avgHue += testPx.GetHue();\n                }\n                avgMono /= scanX.Length;\n                avgHue /= scanX.Length;\n\n                if (prevAvgMono > 0)\n                {\n                    int diffMono = avgMono - prevAvgMono;\n                    int absDiffHue = Math.Abs(avgHue - prevAvgHue);\n\n                    // mono: no abs, look for going darker (low - high = negative)\n                    // hue: abs, jump in hue will be good indicator, esp on red/blue decks\n                    bool foundEdge = (diffMono < -30 && avgMono < 120) || (absDiffHue > 120);\n\n                    if (verboseDebug) { Logger.WriteLine(\"FindExactCardTop[{0}] scan:{1} = [H:{2},M:{3}] vs prev[H:{4},M:{5}] => {6}\", debugName, scanY, avgHue, avgMono, prevAvgHue, prevAvgMono, foundEdge ? \" EDGE!\" : \"nope\"); }\n                    if (foundEdge)\n                    {\n                        exactTopY = scanY;\n                        return true;\n                    }\n                }\n\n                prevAvgMono = avgMono;\n                prevAvgHue = avgHue;\n            }\n\n            exactTopY = cardRect.Top;\n            return false;\n        }\n\n        private int FindExactCardMidX(FastBitmapHSV bitmap, Rectangle cardRect, string debugName, int exactBottomY)\n        {\n            int scanHeight = Math.Min(5, cardRect.Height * 3 / 100);\n            int midX = cardRect.X + (cardRect.Width / 2);\n            int startX = midX - (cardRect.Width / 8);\n            int endX = midX + (cardRect.Width / 8);\n            int startY = exactBottomY - scanHeight + 1;\n\n            bool verboseDebug = screenAnalyzer.debugScannerContext == debugName;\n\n            //Rectangle avgColorRect = new Rectangle(startX, exactBottomY - 1, (endX - startX) / 3, Math.Max(2, scanHeight - 2));\n            //FastPixelHSV avgColor = ImageUtils.GetAverageColor(bitmap, avgColorRect);\n            //int emptyMonoMin = avgColor.GetMonochrome();\n            //if (verboseDebug) { Logger.WriteLine(\"FindExactCardMidX[{0}] avg: {1}, empty:{2}\", debugName, avgColor, emptyMonoMin); }\n            int emptyMonoMin = 100;\n\n            // follow bottom of card's frame and look for dark line doing ^ shape in the middle\n            // move scan up if bottom part raises and shows dark match immediately (keep raised for everything on right)\n            // abort when 5 samples are lower than best OR scan hits the darker part due to being raised too high\n\n            int testSpanH = scanHeight * 3 / 2;\n            int[] monoVArr = new int[testSpanH];\n            int bestH = 0;\n            int bestXMin = 0;\n            int bestXMax = 0;\n            int abortCounter = 5;\n            bool canContinueX = false;\n\n            int offsetH = 0;\n            for (int scanX = startX; scanX < endX; scanX++)\n            {\n                int spanH = 0;\n                for (; spanH < testSpanH; spanH++)\n                {\n                    int testMono = bitmap.GetPixel(scanX, startY - offsetH - spanH).GetMonochrome();\n                    bool isEmpty = testMono > emptyMonoMin;\n                    if (spanH == 0 && !isEmpty && offsetH < scanHeight)\n                    {\n                        spanH--;\n                        offsetH++;\n                        continue;\n                    }\n\n                    monoVArr[spanH] = testMono;\n\n                    if (!isEmpty)\n                    {\n                        break;\n                    }\n                }\n\n                spanH += offsetH;\n                if (verboseDebug)\n                {\n                    Logger.WriteLine(\"FindExactCardMidX[{0}] scan:[{1},{2}..{3}] = {4} offset:{5}, ac:{6}, list:{7}{8}\", debugName,\n                        scanX, startY - offsetH, startY - testSpanH - offsetH,\n                        spanH, offsetH, abortCounter,\n                        string.Join(\",\", monoVArr),\n                        (spanH == offsetH) || (abortCounter == 0) ? \" <= STOP\" : \"\");\n                }\n                if (spanH == offsetH || abortCounter == 0)\n                {\n                    break;\n                }\n\n                if (bestH < spanH)\n                {\n                    bestH = spanH;\n                    bestXMin = scanX;\n                    bestXMax = scanX;\n                    midX = (bestXMin + bestXMax) / 2;\n                    canContinueX = true;\n                }\n                else if (bestH == spanH && canContinueX)\n                {\n                    bestXMax = scanX;\n                    midX = (bestXMin + bestXMax) / 2;\n                }\n                else\n                {\n                    canContinueX = false;\n                    if (bestH > 1) { abortCounter--; }\n                }\n            }\n\n            return midX;\n        }\n\n        private float[] ExtractCardNumberPattern(FastBitmapHSV bitmap, Rectangle scanBoxNumber)\n        {\n            return ImageUtils.ExtractImageFeaturesScaled(bitmap, scanBoxNumber, digitHashSize.Width, digitHashSize.Height, ImageUtils.GetPixelFeaturesM2V2);\n        }\n\n        private CardState ParseCard(FastBitmapHSV bitmap, Rectangle cardRect, string debugName, ECardLocation cardLocation)\n        {\n            CardState cardState = new CardState();\n            cardState.name = debugName;\n            cardState.location = cardLocation;\n            cardState.bounds = cardRect;\n            cardState.sourceImage = screenAnalyzer.screenReader.cachedScreenshot;\n\n            int exactCardTop = cardRect.Top;\n            bool hasExactBottomY = FindExactCardBottom(bitmap, cardRect, debugName, cardLocation, out int exactCardBottom);\n            bool hasExactTopY = hasExactBottomY ? FindExactCardTop(bitmap, cardRect, debugName, cardLocation, out exactCardTop) : false;\n            if (!hasExactBottomY || !hasExactTopY)\n            {\n                if (debugMode) { Logger.WriteLine(\"ParseCard({0}): empty\", debugName); }\n                return cardState;\n            }\n\n            int exactCardMidX = FindExactCardMidX(bitmap, cardRect, debugName, exactCardBottom);\n\n            // check if card is hidden based on approx numberbox location\n            int exactCardHeight = exactCardBottom - exactCardTop;\n            int numberBoxW = cardRect.Width * 50 / 100;\n            int numberBoxH = exactCardHeight * 18 / 100;\n            Rectangle numberBox = new Rectangle(exactCardMidX - (numberBoxW / 2), exactCardBottom - numberBoxH - (exactCardHeight * 9 / 100), numberBoxW, numberBoxH);\n\n            float borderPct = ImageUtils.CountFillPct(bitmap, numberBox, colorMatchCardBorder);\n            if (borderPct > 0.75f)\n            {\n                if (debugMode) { Logger.WriteLine(\"ParseCard({0}): hidden, fill:{1:P0}\", debugName, borderPct); }\n                cardState.state = ECardState.Hidden;\n                cardState.card = TriadCardDB.Get().hiddenCard;\n\n                return cardState;\n            }\n\n            ImageUtils.FindColorRange(bitmap, numberBox, out int minMono, out int maxMono);\n            FastPixelMatch colorMatchNumAdjusted = (maxMono < 200) ? new FastPixelMatchMono((byte)(maxMono * 85 / 100), 255) : colorMatchCardNumber;\n            cardState.state = (maxMono < 200) ? ECardState.Locked : ECardState.Visible;\n            cardState.scanBox = numberBox;\n\n            // find numbers\n            if (debugMode) { debugShapes.Add(new Rectangle(numberBox.Left - 1, numberBox.Top - 1, numberBox.Width + 2, numberBox.Height + 2)); }\n\n            int numberBoxMidX = (numberBox.Left + numberBox.Right) / 2;\n            int numberBoxMidY = (numberBox.Top + numberBox.Bottom) / 2;\n            int digitHeight = numberBox.Height * 50 / 100;\n            int digitWidth = digitHeight * digitHashSize.Width / digitHashSize.Height;\n\n            cardState.sideNumber = new int[4];\n            cardState.sideInfo = new CardState.SideInfo[4];\n            cardState.sideInfo[0].scanBox = new Rectangle(numberBoxMidX - (digitWidth / 2), numberBox.Top, digitHeight, digitHeight);\n            cardState.sideInfo[1].scanBox = new Rectangle(numberBoxMidX + (digitWidth * 70 / 100), numberBoxMidY - (digitHeight / 2), digitHeight, digitHeight);\n            cardState.sideInfo[2].scanBox = new Rectangle(numberBoxMidX - (digitWidth / 2), numberBox.Bottom - digitHeight, digitHeight, digitHeight);\n            cardState.sideInfo[3].scanBox = new Rectangle(numberBoxMidX - (digitWidth * 150 / 100), numberBoxMidY - (digitHeight / 2), digitHeight, digitHeight);\n\n            for (int idx = 0; idx < 4; idx++)\n            {\n                float[] values = ExtractCardNumberPattern(bitmap, cardState.sideInfo[idx].scanBox);\n                ImageUtils.NormalizeImageFeatures(values);\n\n                ImageHashData digitPattern = new ImageHashData() { type = EImageHashType.CardNumber, previewBounds = cardState.sideInfo[idx].scanBox, previewContextBounds = numberBox, isKnown = true };\n                digitPattern.CalculateHash(values);\n\n                cardState.sideInfo[idx].matchNum = classifierTriadDigit.Calculate(values, out cardState.sideInfo[idx].matchPct);\n\n                // allow overwrites in case there is a user defined value, must be exact match\n                ImageHashData overridePattern = ImageHashDB.Get().FindExactMatch(digitPattern);\n                if (overridePattern != null)\n                {\n                    cardState.sideNumber[idx] = (int)overridePattern.ownerOb;\n                    cardState.sideInfo[idx].hasOverride = true;\n                }\n                else\n                {\n                    cardState.sideNumber[idx] = cardState.sideInfo[idx].matchNum;\n                    digitPattern.isAuto = true;\n                }\n\n                cardState.sideInfo[idx].hashValues = values;\n                screenAnalyzer.AddImageHash(digitPattern);\n\n                Rectangle previewBounds = new Rectangle(cardState.sideInfo[idx].scanBox.X + numberBox.Width, cardState.sideInfo[idx].scanBox.Y, digitHashSize.Width, digitHashSize.Height);\n                debugHashes.Add(new ImageUtils.HashPreview() { hashValues = values, bounds = previewBounds });\n                debugShapes.Add(cardState.sideInfo[idx].scanBox);\n            }\n\n            TriadCard foundCard = TriadCardDB.Get().Find(cardState.sideNumber[0], cardState.sideNumber[1], cardState.sideNumber[2], cardState.sideNumber[3]);\n            if (debugMode)\n            {\n                string descFoundCards = \"\";\n                if (foundCard != null)\n                {\n                    if (foundCard.SameNumberId < 0)\n                    {\n                        descFoundCards = foundCard.Name.GetCodeName();\n                    }\n                    else\n                    {\n                        foreach (TriadCard card in TriadCardDB.Get().sameNumberMap[foundCard.SameNumberId])\n                        {\n                            descFoundCards += card.Name.GetCodeName() + \", \";\n                        }\n\n                        descFoundCards = descFoundCards.Remove(descFoundCards.Length - 2, 2) + \" (needs hash check)\";\n                    }\n                }\n                else\n                {\n                    descFoundCards = \"none\";\n                }\n\n                Logger.WriteLine(\"ParseCard({0}): {1}-{2}-{3}-{4} => {5}{6}\", debugName,\n                    cardState.sideNumber[0], cardState.sideNumber[1], cardState.sideNumber[2], cardState.sideNumber[3],\n                    descFoundCards, (cardState.state == ECardState.Locked) ? \" (locked)\" : \"\");\n            }\n\n            // more than one card found\n            if (foundCard != null && foundCard.SameNumberId >= 0)\n            {\n                Rectangle cardHashBox = new Rectangle(cardRect.Left + (cardRect.Width * 15 / 100), cardRect.Top + (cardRect.Height * 70 / 100), cardRect.Width * 70 / 100, cardRect.Height * 25 / 100);\n                float[] values = ImageUtils.ExtractImageFeaturesScaled(bitmap, cardHashBox, 32, 8, ImageUtils.GetPixelFeaturesMono);\n\n                ImageHashData cardPattern = new ImageHashData() { type = EImageHashType.CardImage, previewBounds = cardHashBox, previewContextBounds = cardRect };\n                cardPattern.CalculateHash(values);\n                cardPattern.ownerOb = foundCard;\n\n                ImageHashData foundPattern = ImageHashDB.Get().FindBestMatch(cardPattern, 100, out int matchDistance);\n                if (foundPattern != null)\n                {\n                    cardPattern.ownerOb = foundPattern.ownerOb;\n                    cardPattern.matchDistance = matchDistance;\n                    cardPattern.isKnown = true;\n                }\n\n                cardState.cardImageHash = cardPattern;\n                screenAnalyzer.AddImageHash(cardPattern);\n\n                debugHashes.Add(new ImageUtils.HashPreview() { hashValues = values, bounds = new Rectangle(cardHashBox.X, cardHashBox.Y, 32, 8) });\n            }\n\n            cardState.name = debugName;\n            cardState.card = foundCard;\n            cardState.failedMatching = (foundCard == null);\n            return cardState;\n        }\n\n        private ETriadCardOwner ParseCardOwner(FastBitmapHSV bitmap, Rectangle cardRect, int gridCellLeft, string debugName)\n        {\n            ETriadCardOwner owner = ETriadCardOwner.Unknown;\n\n            int testWidth = 10;\n            int testHeight = cardRect.Height / 3;\n\n            Rectangle testBounds = new Rectangle(gridCellLeft + 5, cardRect.Top + ((cardRect.Height - testHeight) / 2), Math.Min(testWidth, cardRect.Left - gridCellLeft - 10), testHeight);\n            if (debugMode) { debugShapes.Add(testBounds); }\n\n            int counterRed = 0;\n            int counterBlue = 0;\n\n            for (int IdxY = testBounds.Top; IdxY < testBounds.Bottom; IdxY++)\n            {\n                for (int IdxX = testBounds.Left; IdxX < testBounds.Right; IdxX++)\n                {\n                    FastPixelHSV testPx = bitmap.GetPixel(IdxX, IdxY);\n                    counterRed += (colorMatchCardOwnerRed1.IsMatching(testPx) || colorMatchCardOwnerRed2.IsMatching(testPx)) ? 1 : 0;\n                    counterBlue += colorMatchCardOwnerBlue.IsMatching(testPx) ? 1 : 0;\n                }\n            }\n\n            owner = (counterRed > 10 || counterBlue > 10) ? (counterRed > counterBlue ? ETriadCardOwner.Red : ETriadCardOwner.Blue) : ETriadCardOwner.Unknown;\n            if (debugMode) { Logger.WriteLine(\">> owner: \" + owner + \" (\" + counterRed + \" red vs \" + counterBlue + \" blue)\"); }\n            return owner;\n        }\n\n        private bool ParseTimer(FastBitmapHSV bitmap, Rectangle timerRect)\n        {\n            int scanY = (timerRect.Top + timerRect.Bottom) / 2;\n            bool bHasActiveTimer = ImageUtils.TraceLine(bitmap, timerRect.Left, scanY, 1, 0, timerRect.Width, colorMatchTimerActive, out Point dummyHit);\n            if (debugMode) { Logger.WriteLine(\"ParseTimer: \" + (bHasActiveTimer ? \"blue turn\" : \"waiting...\")); }\n            return bHasActiveTimer;\n        }\n\n        #endregion\n\n        #region Validation\n\n        public class VerifyCard\n        {\n            public ECardState state;\n            public int[] sides;\n            public string name;\n            public int mod;\n\n            public VerifyCard()\n            {\n                state = ECardState.None;\n                sides = new int[4] { 0, 0, 0, 0 };\n                mod = 0;\n            }\n\n            public override string ToString()\n            {\n                return string.Format(\"{0} [{1},{2},{3},{4}] {5}\",\n                    state, sides[0], sides[1], sides[2], sides[3],\n                    mod == 0 ? \"\" : (mod > 0 ? (\"+\" + mod) : mod.ToString()));\n            }\n\n            public void Load(JsonParser.ObjectValue cardOb)\n            {\n                string stateDesc = cardOb[\"state\"] as JsonParser.StringValue;\n                if (stateDesc == \"empty\") state = ECardState.None;\n                else if (stateDesc == \"hidden\") state = ECardState.Hidden;\n                else if (stateDesc == \"locked\") state = ECardState.Locked;\n                else if (stateDesc == \"visible\") state = ECardState.Visible;\n                else if (stateDesc == \"red\") state = ECardState.PlacedRed;\n                else if (stateDesc == \"blue\") state = ECardState.PlacedBlue;\n\n                if (cardOb.entries.ContainsKey(\"sides\"))\n                {\n                    JsonParser.ArrayValue sidesArr = cardOb.entries[\"sides\"] as JsonParser.ArrayValue;\n                    for (int idx = 0; idx < 4; idx++)\n                    {\n                        sides[idx] = sidesArr.entries[idx] as JsonParser.IntValue;\n                    }\n                }\n\n                if (cardOb.entries.ContainsKey(\"name\"))\n                {\n                    name = cardOb[\"name\"] as JsonParser.StringValue;\n                }\n\n                if (cardOb.entries.ContainsKey(\"mod\"))\n                {\n                    mod = cardOb[\"mod\"] as JsonParser.IntValue;\n                }\n            }\n        }\n\n        public class VerifyConfig\n        {\n            public string npc;\n            public string[] rules;\n            public VerifyCard[] deckBlue;\n            public VerifyCard[] deckRed;\n            public VerifyCard[] board;\n\n            public VerifyConfig()\n            {\n                rules = null;\n                deckBlue = new VerifyCard[5];\n                deckRed = new VerifyCard[5];\n                board = new VerifyCard[9];\n            }\n\n            public override string ToString()\n            {\n                return (rules == null) ? \"No rules\" : string.Join(\", \", rules);\n            }\n\n            public virtual void Load(JsonParser.ObjectValue configOb)\n            {\n                JsonParser.ArrayValue ruleArr = configOb.entries[\"rules\"] as JsonParser.ArrayValue;\n                rules = new string[ruleArr.entries.Count];\n                for (int idx = 0; idx < ruleArr.entries.Count; idx++)\n                {\n                    rules[idx] = ruleArr.entries[idx] as JsonParser.StringValue;\n                }\n\n                if (configOb.entries.ContainsKey(\"npc\"))\n                {\n                    npc = configOb[\"npc\"] as JsonParser.StringValue;\n                }\n\n                if (configOb.entries.ContainsKey(\"deckRed\"))\n                {\n                    JsonParser.ArrayValue deckRedArr = configOb.entries[\"deckRed\"] as JsonParser.ArrayValue;\n                    for (int idx = 0; idx < deckRedArr.entries.Count; idx++)\n                    {\n                        deckRed[idx] = new VerifyCard();\n                        deckRed[idx].Load(deckRedArr.entries[idx] as JsonParser.ObjectValue);\n                    }\n                }\n\n                if (configOb.entries.ContainsKey(\"deckBlue\"))\n                {\n                    JsonParser.ArrayValue deckBlueArr = configOb.entries[\"deckBlue\"] as JsonParser.ArrayValue;\n                    for (int idx = 0; idx < deckBlueArr.entries.Count; idx++)\n                    {\n                        deckBlue[idx] = new VerifyCard();\n                        deckBlue[idx].Load(deckBlueArr.entries[idx] as JsonParser.ObjectValue);\n                    }\n                }\n\n                JsonParser.ArrayValue boardArr = configOb.entries[\"board\"] as JsonParser.ArrayValue;\n                for (int idx = 0; idx < boardArr.entries.Count; idx++)\n                {\n                    board[idx] = new VerifyCard();\n                    board[idx].Load(boardArr.entries[idx] as JsonParser.ObjectValue);\n                }\n            }\n        }\n\n        private static Dictionary<string, TriadGameModifier> mapValidationRules;\n\n        public override void ValidateScan(string configPath, ScreenAnalyzer.EMode mode, MLDataExporter dataExporter)\n        {\n            string testName = Path.GetFileNameWithoutExtension(configPath);\n\n            if (cachedGameState == null)\n            {\n                string exceptionMsg = string.Format(\"Test {0} failed! Scan results:{1}, config path: {2}\", testName, cachedGameState, configPath);\n                throw new Exception(exceptionMsg);\n            }\n\n            string configText = File.ReadAllText(configPath);\n            JsonParser.ObjectValue rootOb = JsonParser.ParseJson(configText);\n            VerifyConfig configData = new VerifyConfig();\n            configData.Load(rootOb);\n\n            List<ImageHashData> unknownRulePatterns = new List<ImageHashData>();\n            for (int idx = 0; idx < screenAnalyzer.unknownHashes.Count; idx++)\n            {\n                ImageHashData testHash = screenAnalyzer.unknownHashes[idx];\n                switch (testHash.type)\n                {\n                    case EImageHashType.Rule: unknownRulePatterns.Add(testHash); break;\n                    default: break;\n                }\n            }\n\n            List<ImageHashData> matchedRulePatterns = new List<ImageHashData>();\n            for (int idx = 0; idx < screenAnalyzer.currentHashMatches.Count; idx++)\n            {\n                ImageHashData testHash = screenAnalyzer.currentHashMatches[idx];\n                switch (testHash.type)\n                {\n                    case EImageHashType.Rule: matchedRulePatterns.Add(testHash); break;\n                    default: break;\n                }\n            }\n\n            int numRulesScanned = cachedGameState.mods.Count + unknownRulePatterns.Count;\n            if (numRulesScanned != configData.rules.Length)\n            {\n                string exceptionMsg = string.Format(\"Test {0} failed! Rules known:{1} + unknown:{2}, total:{3}, expected:{4}\",\n                    testName,\n                    cachedGameState.mods.Count, unknownRulePatterns.Count, numRulesScanned,\n                    configData.rules.Length);\n                throw new Exception(exceptionMsg);\n            }\n\n            if (dataExporter != null)\n            {\n                int numAddedRules = 0;\n                for (int ruleIdx = 0; ruleIdx < configData.rules.Length; ruleIdx++)\n                {\n                    int readRuleIdx = ruleIdx - numAddedRules;\n                    bool hasMatchingRule = false;\n\n                    if (readRuleIdx < cachedGameState.mods.Count)\n                    {\n                        string readRuleName = cachedGameState.mods[readRuleIdx].GetCodeName();\n                        hasMatchingRule = readRuleName == configData.rules[ruleIdx];\n                    }\n\n                    if (!hasMatchingRule)\n                    {\n                        if (mapValidationRules == null)\n                        {\n                            mapValidationRules = new Dictionary<string, TriadGameModifier>();\n                            foreach (TriadGameModifier mod in ImageHashDB.Get().modObjects)\n                            {\n                                mapValidationRules.Add(mod.GetCodeName(), mod);\n                            }\n                        }\n\n                        ImageHashData rulePattern;\n                        if (numAddedRules >= unknownRulePatterns.Count)\n                        {\n                            rulePattern = matchedRulePatterns[ruleIdx];\n                            // don't increment numAddedRules, it's for unknown hashes only\n                        }\n                        else\n                        {\n                            rulePattern = unknownRulePatterns[numAddedRules];\n                            numAddedRules++;\n                        }\n\n                        rulePattern.ownerOb = mapValidationRules[configData.rules[ruleIdx]];\n                        PlayerSettingsDB.Get().AddKnownHash(rulePattern);\n                        PlayerSettingsDB.Get().Save();\n\n                        Logger.WriteLine(\"Exported rule pattern:{0}\", rulePattern.ownerOb);\n                    }\n                }\n            }\n            else if (unknownRulePatterns.Count > 0)\n            {\n                string exceptionMsg = string.Format(\"Test {0} failed! Rules not recognized, unknown:{1}\", testName, unknownRulePatterns.Count);\n                throw new Exception(exceptionMsg);\n            }\n\n            Action<VerifyCard, ECardLocation, int> ValidateCard = (verifyCard, cardLocation, locationCtx) =>\n            {\n                CardState detectedCard = null;\n                foreach (var testState in cachedCardState)\n                {\n                    if (testState.location == cardLocation && testState.locationContext == locationCtx)\n                    {\n                        detectedCard = testState;\n                        break;\n                    }\n                }\n\n                string debugCardName = ((cardLocation == ECardLocation.BlueDeck) ? \"blue\" : (cardLocation == ECardLocation.RedDeck) ? \"red\" : \"board\") + locationCtx;\n                if (detectedCard == null)\n                {\n                    screenAnalyzer.debugScannerContext = debugCardName;\n                    string exceptionMsg = string.Format(\"Test {0} failed! {1}[{2}] is missing card, expected:{3}\",\n                        testName, cardLocation, locationCtx, verifyCard.state);\n\n                    throw new Exception(exceptionMsg);\n                }\n\n                if (detectedCard.state != verifyCard.state)\n                {\n                    screenAnalyzer.debugScannerContext = debugCardName;\n                    string exceptionMsg = string.Format(\"Test {0} failed! {1}[{2}] got:{3}, expected:{4}\",\n                        testName, cardLocation, locationCtx, detectedCard.state, verifyCard.state);\n\n                    throw new Exception(exceptionMsg);\n                }\n\n                if (verifyCard.state == ECardState.Locked ||\n                    verifyCard.state == ECardState.Visible ||\n                    verifyCard.state == ECardState.PlacedBlue ||\n                    verifyCard.state == ECardState.PlacedRed)\n                {\n                    if (dataExporter != null)\n                    {\n                        // generate additional sample images by offseting source bounds a few pixels\n                        const int offsetExt = 1;\n                        int numPatterns = 0;\n\n                        for (int offsetX = -offsetExt; offsetX <= offsetExt; offsetX++)\n                        {\n                            for (int offsetY = -offsetExt; offsetY <= offsetExt; offsetY++)\n                            {\n                                for (int sideIdx = 0; sideIdx < 4; sideIdx++)\n                                {\n                                    Rectangle offsetBounds = detectedCard.sideInfo[sideIdx].scanBox;\n                                    offsetBounds.Offset(offsetX, offsetY);\n\n                                    float[] exportValues = ExtractCardNumberPattern(screenAnalyzer.cachedFastBitmap, offsetBounds);\n                                    dataExporter.ExportValues(exportValues, verifyCard.sides[sideIdx]);\n                                    numPatterns++;\n                                }\n                            }\n                        }\n\n                        Logger.WriteLine(\"Exported ML entries:{0}\", numPatterns);\n                    }\n                    else\n                    {\n                        if (detectedCard.sideNumber[0] != verifyCard.sides[0] ||\n                            detectedCard.sideNumber[1] != verifyCard.sides[1] ||\n                            detectedCard.sideNumber[2] != verifyCard.sides[2] ||\n                            detectedCard.sideNumber[3] != verifyCard.sides[3])\n                        {\n                            screenAnalyzer.debugScannerContext = debugCardName;\n                            string exceptionMsg = string.Format(\"Test {0} failed! {1}[{2}] got:[{3},{4},{5},{6}], expected:[{7},{8},{9},{10}]\",\n                                testName, cardLocation, locationCtx,\n                                detectedCard.sideNumber[0], detectedCard.sideNumber[1], detectedCard.sideNumber[2], detectedCard.sideNumber[3],\n                                verifyCard.sides[0], verifyCard.sides[1], verifyCard.sides[2], verifyCard.sides[3]);\n                            //throw new Exception(exceptionMsg);\n                            Logger.WriteLine(exceptionMsg);\n                        }\n                    }\n                }\n            };\n\n            for (int idx = 0; idx < 5; idx++)\n            {\n                ValidateCard(configData.deckBlue[idx], ECardLocation.BlueDeck, idx);\n                ValidateCard(configData.deckRed[idx], ECardLocation.RedDeck, idx);\n            }\n\n            for (int idx = 0; idx < 9; idx++)\n            {\n                ValidateCard(configData.board[idx], ECardLocation.Board, idx);\n            }\n        }\n\n        #endregion\n    }\n}\n"
  },
  {
    "path": "sources/vision/ScreenAnalyzer.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Drawing;\nusing System.Drawing.Imaging;\nusing System.IO;\n\nnamespace FFTriadBuddy\n{\n    public class ScreenAnalyzer\n    {\n        public enum EState\n        {\n            NoErrors,\n            NoInputImage,\n            NoScannerMatch,\n            UnknownHash,\n            ScannerErrors,\n        }\n\n        public enum EStateIcon\n        {\n            None,\n            Info,\n            Waiting,\n            Warning,\n            Error,\n        }\n\n        [Flags]\n        public enum EMode\n        {\n            None = 0x0,\n            Debug = 0x1,\n            DebugScreenshotOnly = 0x2,\n            DebugSaveMarkup = 0x4,\n            AlwaysResetCache = 0x8,\n            NeverResetCache = 0x10,\n            AutoTest = 0x20,\n\n            ScanTriad = 0x100,\n            ScanCactpot = 0x200,\n\n            ScanAll = ScanCactpot | ScanTriad,\n            Default = ScanAll,\n        }\n\n        public ScreenReader screenReader;\n        public ScannerCactpot scannerCactpot;\n        public ScannerTriad scannerTriad;\n\n        public Dictionary<EMode, ScannerBase> mapScanners;\n        public ScannerBase activeScanner;\n\n        public List<ImageHashData> unknownHashes;\n        public List<ImageHashData> currentHashMatches;\n        public FastBitmapHSV cachedFastBitmap;\n        public Rectangle scanClipBounds;\n        public Rectangle currentScanArea;\n        public EMode currentMode;\n\n        public string debugScreenshotPath;\n        public string debugScannerContext;\n\n        private EState currentState = EState.NoInputImage;\n        private Size cachedBitmapSize;\n\n        public ScreenAnalyzer()\n        {\n            screenReader = new ScreenReader();\n            scannerCactpot = new ScannerCactpot();\n            scannerTriad = new ScannerTriad();\n\n            unknownHashes = new List<ImageHashData>();\n            currentHashMatches = new List<ImageHashData>();\n\n            mapScanners = new Dictionary<EMode, ScannerBase>();\n            mapScanners.Add(EMode.ScanTriad, scannerTriad);\n            mapScanners.Add(EMode.ScanCactpot, scannerCactpot);\n\n            foreach (var kvp in mapScanners)\n            {\n                kvp.Value.Initialize(this);\n            }\n        }\n\n        public void DoWork(EMode mode = EMode.Default, int scannerFlags = 0)\n        {\n            Stopwatch timerTotal = new Stopwatch();\n            Stopwatch timerStep = new Stopwatch();\n            timerTotal.Start();\n\n            bool debugMode = (mode & EMode.Debug) != EMode.None;\n            activeScanner = null;\n            currentMode = mode;\n\n            // load input\n            bool hasInputImage = LoadInputImage(mode, timerStep, scanClipBounds);\n            if (hasInputImage)\n            {\n                // convert to HSL representation\n                timerStep.Restart();\n                cachedFastBitmap = ImageUtils.ConvertToFastBitmap(screenReader.cachedScreenshot);\n                timerStep.Stop();\n                if (debugMode) { Logger.WriteLine(\"Screenshot convert: \" + timerStep.ElapsedMilliseconds + \"ms\"); }\n                if (Logger.IsSuperVerbose()) { Logger.WriteLine(\"Screenshot: {0}x{1}\", screenReader.cachedScreenshot.Width, screenReader.cachedScreenshot.Height); }\n\n                // reset scanner's intermediate data\n                bool canResetCache = (mode & EMode.NeverResetCache) == EMode.None;\n                if (canResetCache)\n                {\n                    bool forceResetCache = (mode & EMode.AlwaysResetCache) != EMode.None;\n                    if (forceResetCache)\n                    {\n                        unknownHashes.Clear();\n                        currentHashMatches.Clear();\n                    }\n\n                    // invalidate scanner's cache if input image size has changed\n                    if (forceResetCache || cachedBitmapSize.Width <= 0 || cachedBitmapSize.Width != cachedFastBitmap.Width || cachedBitmapSize.Height != cachedFastBitmap.Height)\n                    {\n                        cachedBitmapSize = new Size(cachedFastBitmap.Width, cachedFastBitmap.Height);\n                        foreach (var kvp in mapScanners)\n                        {\n                            kvp.Value.InvalidateCache();\n                        }\n                    }\n                }\n\n                currentState = EState.NoScannerMatch;\n\n                // pass 1: check if cache is still valid for requested scanner\n                foreach (var kvp in mapScanners)\n                {\n                    if ((kvp.Key & mode) != EMode.None && kvp.Value.HasValidCache(cachedFastBitmap, scannerFlags))\n                    {\n                        bool scanned = false;\n                        try\n                        {\n                            scanned = kvp.Value.DoWork(cachedFastBitmap, scannerFlags, timerStep, debugMode);\n                        }\n                        catch (Exception ex)\n                        {\n                            Logger.WriteLine(\"Failed to scan [1] image! {0}\", ex);\n                            scanned = false;\n                        }\n\n                        if (scanned)\n                        {\n                            activeScanner = kvp.Value;\n                            currentState = (currentState == EState.NoScannerMatch) ? EState.NoErrors : currentState;\n                            if (debugMode) Logger.WriteLine(\"Scan [1] successful, type:{0}, state:{1}\", kvp.Key, currentState);\n                        }\n                        else\n                        {\n                            currentState = EState.ScannerErrors;\n                        }\n\n                        break;\n                    }\n                }\n\n                // pass 2: all requested\n                if (activeScanner == null)\n                {\n                    foreach (var kvp in mapScanners)\n                    {\n                        if ((kvp.Key & mode) != EMode.None)\n                        {\n                            bool scanned = false;\n                            try\n                            {\n                                scanned = kvp.Value.DoWork(cachedFastBitmap, scannerFlags, timerStep, debugMode);\n                            }\n                            catch (Exception ex)\n                            {\n                                Logger.WriteLine(\"Failed to scan [2] image! {0}\", ex);\n                                scanned = false;\n                            }\n\n                            if (scanned)\n                            {\n                                activeScanner = kvp.Value;\n                                currentState = (currentState == EState.NoScannerMatch) ? EState.NoErrors : currentState;\n                                if (debugMode) Logger.WriteLine(\"Scan [2] successful, type:{0}, state:{1}\", kvp.Key, currentState);\n                                break;\n                            }\n                        }\n                    }\n                }\n\n                // save debug markup if needed\n                if ((activeScanner != null) && ((mode & EMode.DebugSaveMarkup) != EMode.None))\n                {\n                    List<Rectangle> debugBounds = new List<Rectangle>();\n                    List<ImageUtils.HashPreview> debugHashes = new List<ImageUtils.HashPreview>();\n                    activeScanner.AppendDebugShapes(debugBounds, debugHashes);\n\n                    if (currentScanArea.Width > 0) { debugBounds.Add(currentScanArea); }\n\n                    timerStep.Restart();\n\n                    string imagePath = GetDefaultScreenshotPath() + \"screenshot-markup.png\";\n                    if (File.Exists(imagePath))\n                    {\n                        File.Delete(imagePath);\n                    }\n\n                    using (Bitmap markupBitmap = ImageUtils.ConvertToBitmap(cachedFastBitmap))\n                    {\n                        ImageUtils.DrawDebugShapes(markupBitmap, debugBounds);\n                        ImageUtils.DrawDebugHashes(markupBitmap, debugHashes);\n\n                        markupBitmap.Save(imagePath, ImageFormat.Png);\n                    }\n\n                    timerStep.Stop();\n                    Logger.WriteLine(\"Screenshot save: \" + timerStep.ElapsedMilliseconds + \"ms\");\n                }\n            }\n            else\n            {\n                currentState = EState.NoInputImage;\n            }\n\n            timerTotal.Stop();\n            if (debugMode) { Logger.WriteLine(\"Screenshot TOTAL: \" + timerTotal.ElapsedMilliseconds + \"ms\"); }\n        }\n\n        public EState GetCurrentState() { return currentState; }\n\n        public void OnScannerError()\n        {\n            if (currentState != EState.UnknownHash)\n            {\n                currentState = EState.ScannerErrors;\n            }\n        }\n\n        public void AddImageHash(ImageHashData hashData)\n        {\n            hashData.sourceImage = screenReader.cachedScreenshot;\n            if (hashData.isKnown)\n            {\n                currentHashMatches.Add(hashData);\n            }\n            else\n            {\n                bool alreadyAdded = false;\n                foreach (ImageHashData testHash in unknownHashes)\n                {\n                    if (testHash.type == hashData.type && testHash.ownerOb == hashData.ownerOb)\n                    {\n                        if (testHash.IsMatching(hashData, 0, out int dummyDistance))\n                        {\n                            alreadyAdded = true;\n                        }\n                    }\n                }\n\n                Logger.WriteLine(\"Unknown image hash, type:{0}, new:{1}\", hashData.type, !alreadyAdded);\n                if (!alreadyAdded)\n                {\n                    unknownHashes.Add(hashData);\n                }\n\n                currentState = EState.UnknownHash;\n            }\n        }\n\n        public void PopUnknownHash()\n        {\n            if (unknownHashes.Count > 0)\n            {\n                unknownHashes.RemoveAt(0);\n            }\n\n            if (unknownHashes.Count == 0)\n            {\n                currentState = EState.NoErrors;\n            }\n        }\n\n        public void ClearAll()\n        {\n            currentHashMatches.Clear();\n            unknownHashes.Clear();\n            currentScanArea = Rectangle.Empty;\n            activeScanner = null;\n\n            currentState = EState.NoErrors;\n        }\n\n        public void ClearKnownHashes()\n        {\n            currentHashMatches.Clear();\n        }\n\n        public Rectangle ConvertGameToScreen(Rectangle gameBounds)\n        {\n            return screenReader.ConvertGameToScreen(gameBounds);\n        }\n\n        public void InitializeScreenData()\n        {\n            screenReader.InitializeScreenData();\n            currentState = (screenReader.currentState == ScreenReader.EState.NoErrors) ? EState.NoErrors : EState.NoInputImage;\n        }\n\n        public string GetDefaultScreenshotPath()\n        {\n            string imagePath = AssetManager.Get().CreateFilePath(\"test/\");\n            if (!Directory.Exists(imagePath))\n            {\n                imagePath = AssetManager.Get().CreateFilePath(null);\n                if (!Directory.Exists(imagePath))\n                {\n                    imagePath = Path.GetTempPath();\n                }\n            }\n\n            return imagePath;\n        }\n\n        private bool LoadInputImage(EMode mode, Stopwatch perfTimer, Rectangle optClipBounds)\n        {\n            string imagePath = GetDefaultScreenshotPath();\n            bool debugMode = (mode & EMode.Debug) != EMode.None;\n            bool result = false;\n            perfTimer.Start();\n\n#if DEBUG\n            if (string.IsNullOrEmpty(debugScreenshotPath))\n            {\n                debugScreenshotPath = imagePath + \"failed-match-1.jpg\";\n            }\n#endif\n\n            if ((mode & EMode.DebugScreenshotOnly) != EMode.None)\n            {\n                result = screenReader.LoadScreenshot(debugScreenshotPath, optClipBounds);\n            }\n            else\n            {\n                result = screenReader.TakeScreenshot(optClipBounds);\n                if (!result)\n                {\n                    result = screenReader.LoadScreenshot(debugScreenshotPath, optClipBounds);\n                }\n                else if (debugMode && screenReader.cachedScreenshot != null)\n                {\n                    for (int idx = 1; idx < 1000000; idx++)\n                    {\n                        string testPath = imagePath + \"screenshot-source-\" + idx + \".jpg\";\n                        if (!File.Exists(testPath))\n                        {\n                            screenReader.cachedScreenshot.Save(testPath);\n                            break;\n                        }\n                    }\n                }\n            }\n\n            perfTimer.Stop();\n            if (debugMode)\n            {\n                string logFile = \"\";\n                if (!string.IsNullOrEmpty(debugScreenshotPath))\n                {\n                    logFile = \" <<= \" + Path.GetFileName(debugScreenshotPath);\n                }\n\n                Logger.WriteLine(\"Screenshot load: {0}ms {1}\", perfTimer.ElapsedMilliseconds, logFile);\n            }\n\n            return result;\n        }\n    }\n}\n"
  },
  {
    "path": "sources/vision/ScreenReader.cs",
    "content": "﻿using MgAl2O4.Utils;\nusing System;\nusing System.Diagnostics;\nusing System.Drawing;\nusing System.Drawing.Imaging;\nusing System.IO;\nusing System.Runtime.InteropServices;\nusing System.Windows.Forms;\n\nnamespace FFTriadBuddy\n{\n    public class ScreenReader\n    {\n        public enum EState\n        {\n            NoErrors,\n            MissingGameProcess,\n            MissingGameWindow,\n            MissingFile,\n        }\n\n        private Process cachedProcess;\n        private Screen cachedScreen;\n        private Rectangle cachedGameWindow;\n        private float cachedScreenScaling;\n\n        public Bitmap cachedScreenshot;\n        public EState currentState;\n\n        public bool TakeScreenshot(Rectangle optClipBounds)\n        {\n            bool result = false;\n\n            HandleRef windowHandle = FindGameWindow();\n            if (windowHandle.Handle.ToInt64() != 0)\n            {\n                result = (optClipBounds.Width > 0) ? CaptureWindowPartial(windowHandle, optClipBounds) : CaptureWindow(windowHandle);\n            }\n\n            return result;\n        }\n\n        public bool LoadScreenshot(string path, Rectangle optClipBounds)\n        {\n            if (File.Exists(path))\n            {\n                //if (cachedScreenshot != null) { cachedScreenshot.Dispose(); }\n\n                cachedScreenshot = new Bitmap(path);\n                if (cachedScreenshot != null)\n                {\n                    if (optClipBounds.Width > 0)\n                    {\n                        Bitmap croppedBitmap = cachedScreenshot.Clone(optClipBounds, cachedScreenshot.PixelFormat);\n                        cachedScreenshot.Dispose();\n                        cachedScreenshot = croppedBitmap;\n                    }\n\n                    cachedGameWindow = new Rectangle(0, 0, cachedScreenshot.Width, cachedScreenshot.Height);\n                    currentState = EState.NoErrors;\n                    return true;\n                }\n            }\n\n            currentState = EState.MissingFile;\n            return false;\n        }\n\n        public void InitializeScreenData()\n        {\n            currentState = EState.NoErrors;\n\n            HandleRef windowHandle = FindGameWindow();\n            if (windowHandle.Handle.ToInt64() != 0)\n            {\n                cachedGameWindow = GetGameWindowBounds(windowHandle);\n            }\n            else\n            {\n                cachedGameWindow = Rectangle.Empty;\n            }\n        }\n\n        public Rectangle ConvertGameToScreen(Rectangle gameBounds)\n        {\n            if (cachedGameWindow.Width <= 0 || cachedScreen == null) { return Rectangle.Empty; }\n\n            return new Rectangle(\n                cachedScreen.Bounds.X + (int)((gameBounds.X + cachedGameWindow.X - cachedScreen.Bounds.X) * cachedScreenScaling),\n                cachedScreen.Bounds.Y + (int)((gameBounds.Y + cachedGameWindow.Y - cachedScreen.Bounds.Y) * cachedScreenScaling),\n                (int)(gameBounds.Width * cachedScreenScaling),\n                (int)(gameBounds.Height * cachedScreenScaling)\n                );\n        }\n\n\n        public Rectangle GetCachedGameWindow()\n        {\n            return cachedGameWindow;\n        }\n\n        private HandleRef FindGameWindow()\n        {\n            HandleRef WindowHandle = new HandleRef();\n            bool useVerboseLogs = Logger.IsSuperVerbose();\n            string wndNamePrefix = loc.strings.Game_WindowNamePrefix;\n            string wndNamePrefixDefault = \"FINAL FANTASY\";\n\n            bool hasCached = false;\n            if (cachedProcess != null)\n            {\n                string cachedWndTitle = cachedProcess.MainWindowTitle;\n                hasCached = cachedWndTitle.StartsWith(wndNamePrefix) || cachedWndTitle.StartsWith(wndNamePrefixDefault);\n            }\n\n            if (!hasCached)\n            {\n                Process[] processes = Process.GetProcessesByName(loc.strings.Game_ProcessName_DX11);\n                if (processes.Length == 0)\n                {\n                    processes = Process.GetProcessesByName(loc.strings.Game_ProcessName_DX9);\n                    if (processes.Length == 0)\n                    {\n                        if (loc.strings.Game_ProcessName_DX11 != \"ffxiv_dx11\")\n                        {\n                            processes = Process.GetProcessesByName(\"ffxiv_dx11\");\n                            if (processes.Length == 0)\n                            {\n                                if (loc.strings.Game_ProcessName_DX9 != \"ffxiv\")\n                                {\n                                    processes = Process.GetProcessesByName(\"ffxiv\");\n                                }\n                            }\n                        }\n                    }\n                }\n\n                if (useVerboseLogs) { Logger.WriteLine(\"FindGameWindow: process list to check: \" + processes.Length); }\n\n                cachedProcess = null;\n                foreach (Process p in processes)\n                {\n                    string procWndTitle = p.MainWindowTitle;\n                    bool hasMatchingTitle = procWndTitle.StartsWith(wndNamePrefix) || procWndTitle.StartsWith(wndNamePrefixDefault);\n                    if (useVerboseLogs)\n                    {\n                        Logger.WriteLine(\">> pid:{0}, name:{1}, window:'{2}', hwnd:0x{3:x} => {4}\",\n                            p.Id, p.ProcessName, p.MainWindowTitle, p.MainWindowHandle.ToInt64(),\n                            (hasMatchingTitle ? \"match!\" : \"nope\"));\n\n                        try { Logger.WriteLine(\"   path:'{0}'\", p.MainModule.FileName); }\n                        catch (Exception ex) { Logger.WriteLine(\"   path: FAILED: \" + ex); }\n                    }\n\n                    if (hasMatchingTitle)\n                    {\n                        cachedProcess = p;\n                        break;\n                    }\n                }\n            }\n\n            if (cachedProcess != null)\n            {\n                if (useVerboseLogs) { Logger.WriteLine(\"FindGameWindow: 0x{0:x}\", cachedProcess.MainWindowHandle.ToInt64()); }\n                WindowHandle = new HandleRef(this, cachedProcess.MainWindowHandle);\n            }\n            else\n            {\n                if (useVerboseLogs) { Logger.WriteLine(\"FindGameWindow: can't find window!\"); }\n                currentState = EState.MissingGameProcess;\n            }\n\n            return WindowHandle;\n        }\n\n        private Rectangle GetGameWindowBoundsRaw(HandleRef windowHandle)\n        {\n            Rectangle result = new Rectangle(0, 0, 0, 0);\n\n            bool bHasWindow = windowHandle.Handle.ToInt64() != 0;\n            if (bHasWindow)\n            {\n                if (GetWindowRect(windowHandle, out RECT windowRectApi))\n                {\n                    result = new Rectangle(windowRectApi.Left, windowRectApi.Top, windowRectApi.Right - windowRectApi.Left, windowRectApi.Bottom - windowRectApi.Top);\n                    if (Logger.IsSuperVerbose())\n                    {\n                        Logger.WriteLine(\"GetGameWindowBoundsRaw: handle:0x{0}, bounds:{1}, api:{2}\", windowHandle.Handle.ToInt64(), result, windowRectApi);\n                    }\n                }\n            }\n\n            return result;\n        }\n\n        private Rectangle GetGameWindowBounds(HandleRef windowHandle)\n        {\n            Rectangle result = GetGameWindowBoundsRaw(windowHandle);\n            Screen activeScreen = Screen.FromHandle(windowHandle.Handle);\n\n            if (activeScreen != cachedScreen)\n            {\n                cachedScreen = activeScreen;\n\n                DEVMODE dm = new DEVMODE();\n                dm.dmSize = (short)Marshal.SizeOf(typeof(DEVMODE));\n                EnumDisplaySettings(cachedScreen.DeviceName, -1, ref dm);\n\n                if (dm.dmPelsWidth == cachedScreen.Bounds.Width)\n                {\n                    cachedScreenScaling = 1.0f;\n                }\n                else\n                {\n                    cachedScreenScaling = (float)cachedScreen.Bounds.Width / (float)dm.dmPelsWidth;\n                }\n\n                if (Logger.IsSuperVerbose())\n                {\n                    Logger.WriteLine(\"GetGameWindowBounds, caching screen data: bounds:{0}, pelsWidth:{1} => scale:{2}\", cachedScreen.Bounds, dm.dmPelsWidth, cachedScreenScaling);\n                }\n            }\n\n            if (cachedScreenScaling != 1.0f)\n            {\n                result.X = cachedScreen.Bounds.X + (int)((result.X - cachedScreen.Bounds.X) / cachedScreenScaling);\n                result.Y = cachedScreen.Bounds.Y + (int)((result.Y - cachedScreen.Bounds.Y) / cachedScreenScaling);\n                result.Width = (int)(result.Width / cachedScreenScaling);\n                result.Height = (int)(result.Height / cachedScreenScaling);\n            }\n\n            return result;\n        }\n\n        private bool CaptureWindow(HandleRef windowHandle)\n        {\n            bool useVerboseLogs = Logger.IsSuperVerbose();\n            bool result = false;\n\n            Rectangle bounds = GetGameWindowBounds(windowHandle);\n            if (bounds.Width > 0)\n            {\n                cachedGameWindow = bounds;\n                if (useVerboseLogs) { Logger.WriteLine(\"TakeScreenshot: bounds \" + cachedGameWindow); }\n\n                //if (cachedScreenshot != null) { cachedScreenshot.Dispose(); }\n                cachedScreenshot = new Bitmap(cachedGameWindow.Width, cachedGameWindow.Height, PixelFormat.Format32bppArgb);\n\n                using (Graphics g = Graphics.FromImage(cachedScreenshot))\n                {\n                    // can't use PrintWindow API, returns black screen\n                    // copy entire screen - will capture all windows on top of game too\n                    g.CopyFromScreen(cachedGameWindow.Location, Point.Empty, cachedGameWindow.Size);\n                    if (useVerboseLogs) { Logger.WriteLine(\">> copied from screen\"); }\n                    result = true;\n                }\n            }\n            else\n            {\n                currentState = EState.MissingGameWindow;\n            }\n\n            return result;\n        }\n\n        private bool CaptureWindowPartial(HandleRef windowHandle, Rectangle innerBounds)\n        {\n            bool useVerboseLogs = Logger.IsSuperVerbose();\n            bool result = false;\n\n            Rectangle bounds = GetGameWindowBounds(windowHandle);\n            if (bounds.Width > 0 && innerBounds.Width > 0)\n            {\n                if (useVerboseLogs) { Logger.WriteLine(\"TakeScreenshotPartial: bounds \" + bounds); }\n\n                //if (cachedScreenshot != null) { cachedScreenshot.Dispose(); }\n                cachedScreenshot = new Bitmap(innerBounds.Width, innerBounds.Height, PixelFormat.Format32bppArgb);\n\n                using (Graphics g = Graphics.FromImage(cachedScreenshot))\n                {\n                    // copy entire screen - will capture all windows on top of game too\n                    Point copyPt = new Point(bounds.Left + innerBounds.Left, bounds.Top + innerBounds.Top);\n                    g.CopyFromScreen(copyPt, Point.Empty, innerBounds.Size);\n                }\n\n                result = true;\n            }\n            else\n            {\n                currentState = EState.MissingGameWindow;\n            }\n\n            return result;\n        }\n\n        [DllImport(\"user32.dll\")]\n        [return: MarshalAs(UnmanagedType.Bool)]\n        static extern bool GetWindowRect(HandleRef hWnd, out RECT lpRect);\n\n        [DllImport(\"user32.dll\")]\n        public static extern bool EnumDisplaySettings(string DeviceName, int ModeNum, ref DEVMODE lpDevMode);\n\n        [StructLayout(LayoutKind.Sequential)]\n        public struct RECT\n        {\n            public int Left;        // x position of upper-left corner\n            public int Top;         // y position of upper-left corner\n            public int Right;       // x position of lower-right corner\n            public int Bottom;      // y position of lower-right corner\n\n            public override string ToString()\n            {\n                return string.Format(\"[L:{0},T:{1},R:{2},B:{3}]\", Left, Top, Right, Bottom);\n            }\n        }\n\n        [StructLayout(LayoutKind.Sequential)]\n        public struct DEVMODE\n        {\n            private const int CCHDEVICENAME = 0x20;\n            private const int CCHFORMNAME = 0x20;\n            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]\n            public string dmDeviceName;\n            public short dmSpecVersion;\n            public short dmDriverVersion;\n            public short dmSize;\n            public short dmDriverExtra;\n            public int dmFields;\n            public int dmPositionX;\n            public int dmPositionY;\n            public ScreenOrientation dmDisplayOrientation;\n            public int dmDisplayFixedOutput;\n            public short dmColor;\n            public short dmDuplex;\n            public short dmYResolution;\n            public short dmTTOption;\n            public short dmCollate;\n            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]\n            public string dmFormName;\n            public short dmLogPixels;\n            public int dmBitsPerPel;\n            public int dmPelsWidth;\n            public int dmPelsHeight;\n            public int dmDisplayFlags;\n            public int dmDisplayFrequency;\n            public int dmICMMethod;\n            public int dmICMIntent;\n            public int dmMediaType;\n            public int dmDitherType;\n            public int dmReserved1;\n            public int dmReserved2;\n            public int dmPanningWidth;\n            public int dmPanningHeight;\n        }\n\n        [DllImport(\"user32.dll\", SetLastError = true)]\n        [return: MarshalAs(UnmanagedType.Bool)]\n        static extern bool PrintWindow(IntPtr hwnd, IntPtr hDC, uint nFlags);\n    }\n}\n"
  }
]