├── .github └── workflows │ ├── build-data-files.yml │ ├── dotnet-core.yml │ └── python-package.yml ├── .gitignore ├── .gitmodules ├── AutoMaxLairUI - Code ├── App.config ├── AutoMaxLair.Designer.cs ├── AutoMaxLair.cs ├── AutoMaxLair.csproj ├── AutoMaxLair.resx ├── AutoMaxLairUI - Code.sln ├── Program.cs ├── Properties │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── README.txt ├── Resources │ ├── Save1.png │ ├── Save2.png │ ├── Settings.png │ └── Settings2.png ├── Settings.Designer.cs ├── Settings.cs ├── Settings.resx └── Utils.cs ├── Config.sample.toml ├── LICENSE ├── README.md ├── RemoteControl ├── .gitkeep ├── Config │ ├── Descriptors.c │ ├── Descriptors.h │ └── LUFAConfig.h ├── HORI_Descriptors ├── Joystick.h ├── LUFA_LICENSE ├── MakeProgram.bat ├── RemoteControl_at90usb1286.hex ├── RemoteControl_atmega16u2.hex ├── RemoteControl_atmega32u4.hex ├── Source │ ├── Config.h │ ├── RemoteControl.c │ ├── uart.c │ └── uart.h ├── makefile └── obj │ └── .gitkeep ├── auto_max_lair.py ├── automaxlair ├── .gitkeep ├── PABotBase_controller.py ├── __init__.py ├── ball.py ├── da_controller.py ├── field.py ├── matchup_scoring.py ├── max_lair_instance.py ├── path_tree.py ├── pokemon_classes.py └── switch_controller.py ├── changelog.md ├── data ├── .gitkeep ├── backpacker-items.txt ├── ball_sprites.pickle ├── boss_colors.json ├── boss_matchup_LUT.json ├── boss_pokemon.json ├── boss_pokemon.txt ├── misc_icons │ ├── .gitkeep │ ├── cheer.png │ └── fight.png ├── path_tree.json ├── pokemon_sprites.pickle ├── rental_matchup_LUT.json ├── rental_pokemon.json ├── rental_pokemon.txt ├── rental_pokemon_scores.json └── type_icons.pickle ├── doc ├── .gitkeep ├── Figure 1 - Initial Pokemon Selection.png ├── Figure 2 - Opponent Detection.png ├── Figure 3 - Ball Selection.png ├── Figure 4 - Post-Battle Pokemon Selection.png ├── Figure 5 - Non-Shiny Pokemon.png ├── Figure 6 - Shiny Pokemon.png └── RemoteControl Documentation.pdf ├── install-requirements.bat ├── logs └── .gitkeep ├── requirements.txt └── scripts ├── .gitkeep ├── OpenCV_demo_staticimage.py ├── auto_gift_pokemon.py ├── build_all_data_files.py ├── build_path_tree.py ├── calyrex.py ├── custom_script_template.py ├── detailed_log.py ├── get_good_color_discord.py ├── import_testing.py ├── package_ball.py ├── package_pokemon.py ├── package_pokemon_sprites.py ├── regis.py └── score_pokemon.py /.github/workflows/build-data-files.yml: -------------------------------------------------------------------------------- 1 | name: Build data files 2 | 3 | on: [workflow_dispatch] 4 | 5 | jobs: 6 | package_ball: 7 | runs-on: windows-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | with: 12 | submodules: 'recursive' 13 | - name: Set up Python 3.9 14 | uses: actions/setup-python@v2 15 | with: 16 | python-version: 3.9 17 | - name: Delete files 18 | run: | 19 | del data/*.json 20 | del data/*.pickle 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install -r requirements.txt 25 | - name: Generate data 26 | run: | 27 | python scripts/package_ball.py 28 | - uses: actions/upload-artifact@v2 29 | with: 30 | name: package_ball 31 | path: | 32 | data/ball_sprites.pickle 33 | 34 | package_pokemon: 35 | runs-on: windows-latest 36 | 37 | steps: 38 | - uses: actions/checkout@v2 39 | with: 40 | submodules: 'recursive' 41 | - name: Set up Python 3.9 42 | uses: actions/setup-python@v2 43 | with: 44 | python-version: 3.9 45 | - name: Delete files 46 | run: | 47 | del data/*.json 48 | del data/*.pickle 49 | - name: Install dependencies 50 | run: | 51 | python -m pip install --upgrade pip 52 | pip install -r requirements.txt 53 | - name: Generate data 54 | run: | 55 | python scripts/package_pokemon.py 56 | - uses: actions/upload-artifact@v2 57 | with: 58 | name: package_pokemon 59 | path: | 60 | data/rental_pokemon.json 61 | data/boss_pokemon.json 62 | 63 | score_pokemon: 64 | runs-on: windows-latest 65 | needs: package_pokemon 66 | 67 | steps: 68 | - uses: actions/checkout@v2 69 | with: 70 | submodules: 'recursive' 71 | - name: Set up Python 3.9 72 | uses: actions/setup-python@v2 73 | with: 74 | python-version: 3.9 75 | - name: Delete files 76 | run: | 77 | del data/*.json 78 | del data/*.pickle 79 | - name: Install dependencies 80 | run: | 81 | python -m pip install --upgrade pip 82 | pip install -r requirements.txt 83 | - uses: actions/download-artifact@v2 84 | with: 85 | name: package_pokemon 86 | path: data/ 87 | - name: Generate data 88 | run: | 89 | python scripts/score_pokemon.py 90 | - uses: actions/upload-artifact@v2 91 | with: 92 | name: score_pokemon 93 | path: | 94 | data/boss_matchup_LUT.json 95 | data/rental_matchup_LUT.json 96 | data/rental_pokemon_scores.json 97 | 98 | package_pokemon_sprites: 99 | runs-on: windows-latest 100 | needs: package_pokemon 101 | 102 | steps: 103 | - uses: actions/checkout@v2 104 | with: 105 | submodules: 'recursive' 106 | - name: Set up Python 3.9 107 | uses: actions/setup-python@v2 108 | with: 109 | python-version: 3.9 110 | - name: Delete files 111 | run: | 112 | del data/*.json 113 | del data/*.pickle 114 | - name: Install dependencies 115 | run: | 116 | python -m pip install --upgrade pip 117 | pip install -r requirements.txt 118 | - uses: actions/download-artifact@v2 119 | with: 120 | name: package_pokemon 121 | path: data/ 122 | - name: Generate data 123 | run: | 124 | python scripts/package_pokemon_sprites.py 125 | - uses: actions/upload-artifact@v2 126 | with: 127 | name: package_pokemon_sprites 128 | path: | 129 | data/pokemon_sprites.pickle 130 | 131 | get_good_color_discord: 132 | runs-on: windows-latest 133 | needs: package_pokemon 134 | 135 | steps: 136 | - uses: actions/checkout@v2 137 | with: 138 | submodules: 'recursive' 139 | - name: Set up Python 3.9 140 | uses: actions/setup-python@v2 141 | with: 142 | python-version: 3.9 143 | - name: Delete files 144 | run: | 145 | del data/*.json 146 | del data/*.pickle 147 | - name: Install dependencies 148 | run: | 149 | python -m pip install --upgrade pip 150 | pip install -r requirements.txt 151 | - uses: actions/download-artifact@v2 152 | with: 153 | name: package_pokemon 154 | path: data/ 155 | - name: Generate data 156 | run: | 157 | python scripts/get_good_color_discord.py 158 | - uses: actions/upload-artifact@v2 159 | with: 160 | name: get_good_color_discord 161 | path: | 162 | data/boss_colors.json 163 | 164 | build_path_tree: 165 | runs-on: windows-latest 166 | needs: package_pokemon 167 | 168 | steps: 169 | - uses: actions/checkout@v2 170 | with: 171 | submodules: 'recursive' 172 | - name: Set up Python 3.9 173 | uses: actions/setup-python@v2 174 | with: 175 | python-version: 3.9 176 | - name: Delete files 177 | run: | 178 | del data/*.json 179 | del data/*.pickle 180 | - name: Install dependencies 181 | run: | 182 | python -m pip install --upgrade pip 183 | pip install -r requirements.txt 184 | - uses: actions/download-artifact@v2 185 | with: 186 | name: package_pokemon 187 | path: data/ 188 | - name: Generate data 189 | run: | 190 | python scripts/build_path_tree.py 191 | - uses: actions/upload-artifact@v2 192 | with: 193 | name: build_path_tree 194 | path: | 195 | data/path_tree.json 196 | 197 | merge_data: 198 | runs-on: windows-latest 199 | needs: score_pokemon 200 | 201 | steps: 202 | - uses: actions/download-artifact@v2 203 | with: 204 | name: package_ball 205 | path: data/ 206 | - uses: actions/download-artifact@v2 207 | with: 208 | name: package_pokemon 209 | path: data/ 210 | - uses: actions/download-artifact@v2 211 | with: 212 | name: score_pokemon 213 | path: data/ 214 | - uses: actions/download-artifact@v2 215 | with: 216 | name: package_pokemon_sprites 217 | path: data/ 218 | - uses: actions/download-artifact@v2 219 | with: 220 | name: get_good_color_discord 221 | path: data/ 222 | - uses: actions/download-artifact@v2 223 | with: 224 | name: build_path_tree 225 | path: data/ 226 | - uses: actions/upload-artifact@v2 227 | with: 228 | name: all_data 229 | path: | 230 | data/* -------------------------------------------------------------------------------- /.github/workflows/dotnet-core.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | defaults: 6 | run: 7 | working-directory: AutoMaxLairUI - Code 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: windows-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Setup .NET Core 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: 3.1 20 | - name: Install dependencies 21 | run: dotnet restore "AutoMaxLairUI - Code.sln" 22 | - name: Build 23 | run: | 24 | dotnet build "AutoMaxLairUI - Code.sln" --configuration Debug --no-restore 25 | dotnet build "AutoMaxLairUI - Code.sln" --configuration Release --no-restore 26 | - name: Publish 27 | run: dotnet publish "AutoMaxLairUI - Code.sln" --configuration Release --no-restore -p:DebugType=None -p:DebugSymbols=false 28 | - uses: actions/upload-artifact@v2 29 | with: 30 | name: Auto Max Lair UI 31 | path: AutoMaxLairUI - Code/bin/Release/netcoreapp3.1/win-x64/publish/AutoMaxLair.exe 32 | - name: Zip UI 33 | if: startsWith(github.ref, 'refs/tags/v') 34 | run: | 35 | compress-archive "bin/Release/netcoreapp3.1/win-x64/publish/AutoMaxLair.exe" AutoMaxLairUI.zip 36 | - name: Upload binaries to release 37 | uses: svenstaro/upload-release-action@v2 38 | if: startsWith(github.ref, 'refs/tags/v') 39 | with: 40 | repo_token: ${{ secrets.GITHUB_TOKEN }} 41 | file: AutoMaxLairUI - Code/AutoMaxLairUI.zip 42 | asset_name: AutoMaxLairUI.zip 43 | tag: ${{ github.ref }} 44 | -------------------------------------------------------------------------------- /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | name: Python package 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ${{ matrix.os }} 9 | 10 | strategy: 11 | matrix: 12 | python-version: [3.6, 3.7, 3.8, 3.9] 13 | os: [ubuntu-latest, macOS-latest, windows-latest] 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Python ${{ matrix.python-version }} 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: ${{ matrix.python-version }} 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | python -m pip install flake8 25 | pip install -r requirements.txt 26 | - name: Lint with flake8 27 | run: | 28 | flake8 --ignore=E125,E402,E501,W503 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | Config.ini 3 | Config.toml 4 | .vscode/* 5 | logs/* 6 | itemsList.txt 7 | data/type_icons 8 | scripts/*.png 9 | AutoMaxLairUI - Code/bin/* 10 | AutoMaxLairUI - Code/obj/* 11 | AutoMaxLairUI - Code/AutoMaxLair.csproj.user 12 | AutoMaxLairUI - Code/.vs 13 | AutoMaxLairUI - Code/Properties/launchSettings.json 14 | AutoMaxLairUI - Code/Properties/PublishProfiles 15 | AutoMaxLair.exe 16 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "RemoteControl/lufa"] 2 | path = RemoteControl/lufa 3 | url = https://github.com/abcminiuser/lufa 4 | [submodule "data/pokesprite"] 5 | path = data/pokesprite 6 | url = https://github.com/msikma/pokesprite.git 7 | [submodule "Packages"] 8 | path = Packages 9 | url = https://github.com/PokemonAutomation/Packages.git 10 | -------------------------------------------------------------------------------- /AutoMaxLairUI - Code/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 |
6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | True 20 | 21 | 22 | 23 | 24 | 25 | 26 | True 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /AutoMaxLairUI - Code/AutoMaxLair.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | netcoreapp3.1 6 | true 7 | true 8 | true 9 | true 10 | win-x64 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | True 38 | True 39 | Resources.resx 40 | 41 | 42 | True 43 | True 44 | Settings.settings 45 | 46 | 47 | 48 | 49 | 50 | ResXFileCodeGenerator 51 | Resources.Designer.cs 52 | 53 | 54 | 55 | 56 | 57 | SettingsSingleFileGenerator 58 | Settings.Designer.cs 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /AutoMaxLairUI - Code/AutoMaxLairUI - Code.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.6.30114.105 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoMaxLair", "AutoMaxLair.csproj", "{C9C1DB0E-FC1D-44C9-A4C3-B2E4C611EEA5}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|Any CPU = Release|Any CPU 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {C9C1DB0E-FC1D-44C9-A4C3-B2E4C611EEA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {C9C1DB0E-FC1D-44C9-A4C3-B2E4C611EEA5}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {C9C1DB0E-FC1D-44C9-A4C3-B2E4C611EEA5}.Debug|x64.ActiveCfg = Debug|Any CPU 24 | {C9C1DB0E-FC1D-44C9-A4C3-B2E4C611EEA5}.Debug|x64.Build.0 = Debug|Any CPU 25 | {C9C1DB0E-FC1D-44C9-A4C3-B2E4C611EEA5}.Debug|x86.ActiveCfg = Debug|Any CPU 26 | {C9C1DB0E-FC1D-44C9-A4C3-B2E4C611EEA5}.Debug|x86.Build.0 = Debug|Any CPU 27 | {C9C1DB0E-FC1D-44C9-A4C3-B2E4C611EEA5}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {C9C1DB0E-FC1D-44C9-A4C3-B2E4C611EEA5}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {C9C1DB0E-FC1D-44C9-A4C3-B2E4C611EEA5}.Release|x64.ActiveCfg = Release|Any CPU 30 | {C9C1DB0E-FC1D-44C9-A4C3-B2E4C611EEA5}.Release|x64.Build.0 = Release|Any CPU 31 | {C9C1DB0E-FC1D-44C9-A4C3-B2E4C611EEA5}.Release|x86.ActiveCfg = Release|Any CPU 32 | {C9C1DB0E-FC1D-44C9-A4C3-B2E4C611EEA5}.Release|x86.Build.0 = Release|Any CPU 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /AutoMaxLairUI - Code/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.Windows.Forms; 6 | 7 | namespace AutoDA 8 | { 9 | static class Program 10 | { 11 | /// 12 | /// The main entry point for the application. 13 | /// 14 | [STAThread] 15 | static void Main() 16 | { 17 | Application.SetHighDpiMode(HighDpiMode.SystemAware); 18 | Application.EnableVisualStyles(); 19 | Application.SetCompatibleTextRenderingDefault(false); 20 | Application.Run(new MainWindow()); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /AutoMaxLairUI - Code/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace AutoMaxLair.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AutoMaxLair.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized resource of type System.Drawing.Bitmap. 65 | /// 66 | internal static System.Drawing.Bitmap Save1 { 67 | get { 68 | object obj = ResourceManager.GetObject("Save1", resourceCulture); 69 | return ((System.Drawing.Bitmap)(obj)); 70 | } 71 | } 72 | 73 | /// 74 | /// Looks up a localized resource of type System.Drawing.Bitmap. 75 | /// 76 | internal static System.Drawing.Bitmap Save2 { 77 | get { 78 | object obj = ResourceManager.GetObject("Save2", resourceCulture); 79 | return ((System.Drawing.Bitmap)(obj)); 80 | } 81 | } 82 | 83 | /// 84 | /// Looks up a localized resource of type System.Drawing.Bitmap. 85 | /// 86 | internal static System.Drawing.Bitmap Settings1 { 87 | get { 88 | object obj = ResourceManager.GetObject("Settings1", resourceCulture); 89 | return ((System.Drawing.Bitmap)(obj)); 90 | } 91 | } 92 | 93 | /// 94 | /// Looks up a localized resource of type System.Drawing.Bitmap. 95 | /// 96 | internal static System.Drawing.Bitmap Settings2 { 97 | get { 98 | object obj = ResourceManager.GetObject("Settings2", resourceCulture); 99 | return ((System.Drawing.Bitmap)(obj)); 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /AutoMaxLairUI - Code/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\Resources\Save1.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 123 | 124 | 125 | ..\Resources\Save2.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 126 | 127 | 128 | ..\Resources\Settings.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 129 | 130 | 131 | ..\Resources\Settings2.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 132 | 133 | -------------------------------------------------------------------------------- /AutoMaxLairUI - Code/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace AutoMaxLair.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.8.1.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | 26 | [global::System.Configuration.UserScopedSettingAttribute()] 27 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 28 | [global::System.Configuration.DefaultSettingValueAttribute("True")] 29 | public bool DarkTheme { 30 | get { 31 | return ((bool)(this["DarkTheme"])); 32 | } 33 | set { 34 | this["DarkTheme"] = value; 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AutoMaxLairUI - Code/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | True 7 | 8 | 9 | -------------------------------------------------------------------------------- /AutoMaxLairUI - Code/README.txt: -------------------------------------------------------------------------------- 1 | For the executable go to: 2 | 3 | |> Actions 4 | 5 | and download the latest Auto Max Lair UI zip via the dotnet core pipeline. 6 | -------------------------------------------------------------------------------- /AutoMaxLairUI - Code/Resources/Save1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PokemonAutomation/AutoMaxLair/39dfc88bde4a92a113af88fd10187a21227eb795/AutoMaxLairUI - Code/Resources/Save1.png -------------------------------------------------------------------------------- /AutoMaxLairUI - Code/Resources/Save2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PokemonAutomation/AutoMaxLair/39dfc88bde4a92a113af88fd10187a21227eb795/AutoMaxLairUI - Code/Resources/Save2.png -------------------------------------------------------------------------------- /AutoMaxLairUI - Code/Resources/Settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PokemonAutomation/AutoMaxLair/39dfc88bde4a92a113af88fd10187a21227eb795/AutoMaxLairUI - Code/Resources/Settings.png -------------------------------------------------------------------------------- /AutoMaxLairUI - Code/Resources/Settings2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PokemonAutomation/AutoMaxLair/39dfc88bde4a92a113af88fd10187a21227eb795/AutoMaxLairUI - Code/Resources/Settings2.png -------------------------------------------------------------------------------- /AutoMaxLairUI - Code/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace AutoMaxLair 3 | { 4 | partial class Settings 5 | { 6 | /// 7 | /// Required designer variable. 8 | /// 9 | private System.ComponentModel.IContainer components = null; 10 | 11 | /// 12 | /// Clean up any resources being used. 13 | /// 14 | /// true if managed resources should be disposed; otherwise, false. 15 | protected override void Dispose(bool disposing) 16 | { 17 | if (disposing && (components != null)) 18 | { 19 | components.Dispose(); 20 | } 21 | base.Dispose(disposing); 22 | } 23 | 24 | #region Windows Form Designer generated code 25 | 26 | /// 27 | /// Required method for Designer support - do not modify 28 | /// the contents of this method with the code editor. 29 | /// 30 | private void InitializeComponent() 31 | { 32 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Settings)); 33 | this.panel1 = new System.Windows.Forms.Panel(); 34 | this.groupTheme = new System.Windows.Forms.GroupBox(); 35 | this.radioDark = new System.Windows.Forms.RadioButton(); 36 | this.radioLight = new System.Windows.Forms.RadioButton(); 37 | this.panel1.SuspendLayout(); 38 | this.groupTheme.SuspendLayout(); 39 | this.SuspendLayout(); 40 | // 41 | // panel1 42 | // 43 | this.panel1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(36)))), ((int)(((byte)(33)))), ((int)(((byte)(40))))); 44 | this.panel1.Controls.Add(this.groupTheme); 45 | this.panel1.Dock = System.Windows.Forms.DockStyle.Top; 46 | this.panel1.Location = new System.Drawing.Point(0, 0); 47 | this.panel1.Name = "panel1"; 48 | this.panel1.Size = new System.Drawing.Size(234, 135); 49 | this.panel1.TabIndex = 0; 50 | // 51 | // groupTheme 52 | // 53 | this.groupTheme.Controls.Add(this.radioDark); 54 | this.groupTheme.Controls.Add(this.radioLight); 55 | this.groupTheme.FlatStyle = System.Windows.Forms.FlatStyle.Flat; 56 | this.groupTheme.Location = new System.Drawing.Point(12, 12); 57 | this.groupTheme.Name = "groupTheme"; 58 | this.groupTheme.Size = new System.Drawing.Size(200, 100); 59 | this.groupTheme.TabIndex = 1; 60 | this.groupTheme.TabStop = false; 61 | this.groupTheme.Text = "Theme"; 62 | // 63 | // radioDark 64 | // 65 | this.radioDark.AutoSize = true; 66 | this.radioDark.Checked = true; 67 | this.radioDark.FlatStyle = System.Windows.Forms.FlatStyle.Flat; 68 | this.radioDark.Location = new System.Drawing.Point(12, 34); 69 | this.radioDark.Name = "radioDark"; 70 | this.radioDark.Size = new System.Drawing.Size(87, 19); 71 | this.radioDark.TabIndex = 0; 72 | this.radioDark.TabStop = true; 73 | this.radioDark.Text = "Dark Theme"; 74 | this.radioDark.UseVisualStyleBackColor = true; 75 | this.radioDark.CheckedChanged += new System.EventHandler(this.radioDark_CheckedChanged); 76 | // 77 | // radioLight 78 | // 79 | this.radioLight.AutoSize = true; 80 | this.radioLight.FlatStyle = System.Windows.Forms.FlatStyle.Flat; 81 | this.radioLight.Location = new System.Drawing.Point(12, 59); 82 | this.radioLight.Name = "radioLight"; 83 | this.radioLight.Size = new System.Drawing.Size(90, 19); 84 | this.radioLight.TabIndex = 0; 85 | this.radioLight.TabStop = true; 86 | this.radioLight.Text = "Light Theme"; 87 | this.radioLight.UseVisualStyleBackColor = true; 88 | this.radioLight.CheckedChanged += new System.EventHandler(this.radioLight_CheckedChanged); 89 | // 90 | // Settings 91 | // 92 | this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); 93 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 94 | this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(11)))), ((int)(((byte)(8)))), ((int)(((byte)(20))))); 95 | this.ClientSize = new System.Drawing.Size(234, 133); 96 | this.Controls.Add(this.panel1); 97 | this.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(250)))), ((int)(((byte)(63)))), ((int)(((byte)(82))))); 98 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; 99 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 100 | this.Name = "Settings"; 101 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; 102 | this.Text = "Settings"; 103 | this.Load += new System.EventHandler(this.Settings_Load); 104 | this.panel1.ResumeLayout(false); 105 | this.groupTheme.ResumeLayout(false); 106 | this.groupTheme.PerformLayout(); 107 | this.ResumeLayout(false); 108 | 109 | } 110 | 111 | #endregion 112 | 113 | private System.Windows.Forms.Panel panel1; 114 | private System.Windows.Forms.GroupBox groupTheme; 115 | private System.Windows.Forms.RadioButton radioDark; 116 | private System.Windows.Forms.RadioButton radioLight; 117 | } 118 | } -------------------------------------------------------------------------------- /AutoMaxLairUI - Code/Settings.cs: -------------------------------------------------------------------------------- 1 | using AutoDA; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Data; 6 | using System.Drawing; 7 | using System.Text; 8 | using System.Windows.Forms; 9 | 10 | namespace AutoMaxLair 11 | { 12 | public partial class Settings : Form 13 | { 14 | List panels, panels2; 15 | List buttons; 16 | List labels; 17 | List comboboxes; 18 | List textboxes; 19 | List checkboxes; 20 | 21 | 22 | 23 | public Settings() 24 | { 25 | InitializeComponent(); 26 | 27 | 28 | } 29 | 30 | 31 | 32 | 33 | void Initialize_Add() 34 | { 35 | panels = new List(); 36 | panels2 = new List(); 37 | buttons = new List(); 38 | labels = new List(); 39 | comboboxes = new List(); 40 | textboxes = new List(); 41 | checkboxes = new List(); 42 | 43 | MainWindow main = (MainWindow)this.Owner; 44 | 45 | panels.Add(main.panelLogo); 46 | panels.Add(main.panelSideMenu); 47 | panels.Add(main.panelRightSide); 48 | panels.Add(main.panelRightTop); 49 | 50 | panels2.Add(main.panelAdvancedSettingsSubmenu); 51 | panels2.Add(main.panelDiscordSubmenu); 52 | panels2.Add(main.panelSettingSubmenu); 53 | panels2.Add(main.panelStatSubmenu); 54 | panels2.Add(panel1); 55 | 56 | buttons.Add(main.btnAdvancedSettings); 57 | buttons.Add(main.btnDiscord); 58 | buttons.Add(main.btnSave); 59 | buttons.Add(main.btnSetting); 60 | buttons.Add(main.btnStats); 61 | buttons.Add(main.btnTesseract); 62 | buttons.Add(main.btnSettings); 63 | 64 | labels.Add(main.labelAttackNeg); 65 | labels.Add(main.labelAttackNeut); 66 | labels.Add(main.labelAttackPos); 67 | labels.Add(main.labelSpeedNeg); 68 | labels.Add(main.labelSpeedNeut); 69 | labels.Add(main.labelSpeedPos); 70 | labels.Add(main.labelBaseBall); 71 | labels.Add(main.labelBossIndex); 72 | labels.Add(main.labelComPort); 73 | labels.Add(main.labelConsecutiveResets); 74 | labels.Add(main.labelDyniteOre); 75 | labels.Add(main.labelMaxDynite); 76 | labels.Add(main.labelGameLanguage); 77 | labels.Add(main.labelHuntingPoke); 78 | labels.Add(main.labelLegendBall); 79 | panels.Add(main.labelLogo); 80 | labels.Add(main.labelMode); 81 | labels.Add(main.labelPokémon); 82 | labels.Add(main.labelRun); 83 | labels.Add(main.labelShinies); 84 | labels.Add(main.labelShiniesFound); 85 | labels.Add(main.labelTessaract); 86 | labels.Add(main.labelVideoDelay); 87 | labels.Add(main.labelVideoIndex); 88 | labels.Add(main.labelVideoScale); 89 | labels.Add(main.labelWinPercentage); 90 | labels.Add(main.labelWebID); 91 | labels.Add(main.labelWebToken); 92 | labels.Add(main.labelUser); 93 | labels.Add(main.labelID); 94 | labels.Add(main.labelMessages); 95 | labels.Add(main.labelAtk); 96 | labels.Add(main.labelSpeed); 97 | labels.Add(main.labelMaxDynite); 98 | labels.Add(main.labelPathWins); 99 | labels.Add(main.labelNonLegend); 100 | 101 | comboboxes.Add(main.boxBossIndex); 102 | comboboxes.Add(main.boxPokemon); 103 | comboboxes.Add(main.boxBaseBall); 104 | comboboxes.Add(main.boxLegendBall); 105 | comboboxes.Add(main.boxMode); 106 | comboboxes.Add(main.boxVideoCapture); 107 | comboboxes.Add(main.boxGameLanguage); 108 | comboboxes.Add(main.boxPingSettings); 109 | comboboxes.Add(main.boxNonLegend); 110 | 111 | textboxes.Add(main.boxComPort); 112 | textboxes.Add(main.boxTesseract); 113 | textboxes.Add(main.boxVideoScale); 114 | textboxes.Add(main.boxVideoDelay); 115 | textboxes.Add(main.boxDyniteOre); 116 | textboxes.Add(main.boxMaxDynite); 117 | textboxes.Add(main.boxConsecutiveResets); 118 | textboxes.Add(main.boxAttackNeg); 119 | textboxes.Add(main.boxAttackNeut); 120 | textboxes.Add(main.boxAttackPos); 121 | textboxes.Add(main.boxSpeedNeg); 122 | textboxes.Add(main.boxSpeedNeut); 123 | textboxes.Add(main.boxSpeedPos); 124 | textboxes.Add(main.boxWebhookID); 125 | textboxes.Add(main.boxWebhookToken); 126 | textboxes.Add(main.boxUserID); 127 | textboxes.Add(main.boxPingName); 128 | textboxes.Add(main.boxMaxDynite); 129 | textboxes.Add(main.boxPathWins); 130 | 131 | checkboxes.Add(main.checkBoxDebugLogs); 132 | checkboxes.Add(main.boxCheckAttack); 133 | checkboxes.Add(main.boxCheckSpeed); 134 | } 135 | 136 | public void ApplyTheme(Color back, Color pan, Color btn, Color lab, Color cbox, Color tbox, Color textC) 137 | { 138 | this.BackColor = lab; 139 | this.ForeColor = textC; 140 | 141 | foreach (Control item in panels) 142 | { 143 | item.BackColor = pan; 144 | item.ForeColor = textC; 145 | } 146 | 147 | foreach (Control item in panels2) 148 | { 149 | item.BackColor = tbox; 150 | item.ForeColor = textC; 151 | } 152 | 153 | foreach (Control item in buttons) 154 | { 155 | item.BackColor = btn; 156 | item.ForeColor = textC; 157 | } 158 | 159 | foreach (Control item in labels) 160 | { 161 | item.BackColor = lab; 162 | item.ForeColor = textC; 163 | } 164 | 165 | foreach (Control item in comboboxes) 166 | { 167 | item.BackColor = cbox; 168 | item.ForeColor = textC; 169 | } 170 | 171 | foreach (Control item in textboxes) 172 | { 173 | item.BackColor = tbox; 174 | item.ForeColor = textC; 175 | } 176 | 177 | foreach (Control item in checkboxes) 178 | { 179 | item.BackColor = lab; 180 | item.ForeColor = textC; 181 | } 182 | } 183 | 184 | public void ChangeColor() 185 | { 186 | if (radioDark.Checked == true) 187 | { 188 | MainWindow main = (MainWindow)this.Owner; 189 | ApplyTheme(zcolor(11, 8, 20), zcolor(11, 8, 20), zcolor(11, 8, 20), zcolor(36, 33, 40), zcolor(36, 33, 40), zcolor(36, 33, 40), zcolor(250, 63, 82)); 190 | main.btnSave.BackgroundImage = AutoMaxLair.Properties.Resources.Save1; 191 | main.btnSettings.BackgroundImage = AutoMaxLair.Properties.Resources.Settings1; 192 | Properties.Settings.Default.DarkTheme = true; 193 | Properties.Settings.Default.Save(); 194 | } 195 | else if (radioLight.Checked == true) 196 | { 197 | MainWindow main = (MainWindow)this.Owner; 198 | ApplyTheme(Color.White, Color.LightGray, Color.LightGray, Color.White, Color.LightGray, Color.White, Color.Black); 199 | main.btnSave.BackgroundImage = AutoMaxLair.Properties.Resources.Save2; 200 | main.btnSettings.BackgroundImage = AutoMaxLair.Properties.Resources.Settings2; 201 | Properties.Settings.Default.DarkTheme = false; 202 | Properties.Settings.Default.Save(); 203 | } 204 | } 205 | 206 | Color zcolor(int r, int g, int b) 207 | { 208 | return Color.FromArgb(r, g, b); 209 | } 210 | 211 | private void radioDark_CheckedChanged(object sender, EventArgs e) 212 | { 213 | ChangeColor(); 214 | } 215 | 216 | private void radioLight_CheckedChanged(object sender, EventArgs e) 217 | { 218 | ChangeColor(); 219 | } 220 | 221 | private void Settings_Load(object sender, EventArgs e) 222 | { 223 | Initialize_Add(); 224 | 225 | if (Properties.Settings.Default.DarkTheme == true) 226 | radioDark.Checked = true; 227 | else if (Properties.Settings.Default.DarkTheme == false) 228 | radioLight.Checked = true; 229 | 230 | } 231 | } 232 | 233 | } 234 | -------------------------------------------------------------------------------- /AutoMaxLairUI - Code/Settings.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | 61 | 62 | 63 | AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAMMOAADDDgAAAAAAAAAA 64 | AABgcSX/X3Ak/2N1Kf9xhDr/QmQm/2uVUP+s1oX/qNCA/6DEdv+cvnH/jape/3mQRf8iTRT/hJhR/4ac 65 | Vf+Mplz/kq5k/5WzaP+Ytmv/lrJo/4ugWv8nUhn/iqpd/5KyZP+WuGn/oMR1/6TJe/+ozn//rdaG/1WB 66 | RfsAMgAbAAAAAGFzJ/9neS7/dYo//36US/97lE7/I1EY/6LKe/+s1oX/qdKB/6LHef+ewXP/iaRZ/x9L 67 | E/+HnVb/jKNb/5i2a/+cu3D/nLtw/5y8cP+Zt2z/gp1X/zNfI/+WuGr/oMR1/6fPf/+s1oX/sd2L/7Lf 68 | jf+ayXr/BjkFmAAAAAAAAAAAZ3ku/3aMQf9/lkz/hZ1U/4ifV/9IbjD/WodE/6rUg/+r1IP/qNCA/6PJ 69 | ev+cvXD/KFUc/5GrYv+gwnX/o8h6/6PHef+ixnn/osV4/6DDdv9tk0//TXg4/6XOfv+v24n/st+N/7Lf 70 | jP+y343/ptOD/xpMFOIAMgAKAAAAAAAAAAByhzz/gJdN/4egVv+KpFr/j6pg/46sYf84Yyf/hq9l/6nS 71 | gf+p0oH/ncZ4/ztqLP9QfT3/q9WE/7LfjP+y4I3/suCN/7LfjP+w3Yv/r9uJ/6fSgv9JeTn/daNb/7He 72 | i/+y34z/sd6M/6LPf/8sXiP/ADMA3QAxAA4AAAAAAAAAAHuSR/+Iolf/j6pg/5KvZP+Wtmn/mbpt/4uv 73 | ZP87aCv/Q3Ey/0RzNP9RgD7/ZJNO/7Hdi/+z4Y7/s+GO/7Phjv+z4Y7/s+GO/7Phjv+z4Y7/s+GO/36u 74 | ZP9Jejr/UIFA/4OyZ/98q2L/Klsg/4GhWv8zXCL+ADMARQAAAAAAAAAAhZ5T/5GtYv+ds1r/prhV/6G+ 75 | af+gxHX/osd4/6DJev98qmH/qtiG/5/Nfv+w3ov/s+GO/7Phjv+z4Y7/s+GO/7Phjv+z4Y7/s+GO/7Ph 76 | jv+z4Y7/q9qI/5bFd/+ZyHr/LV4j/0h5Of+n0oL/mrhu/3SPTP8GNwSJAAAAAAAAAACPq2D/qrRK/9+/ 77 | Lv/v3pP/59Jr/8zBPv+dt2z/ncSD/7Lgjf+z4Y7/s+GO/7Phjv+z4Y7/rNqJ/5HAc/+Es2j/lcR2/6rY 78 | h/+z4Y7/s+GO/7Phjv+z4Y7/s+GO/7Phjv+z4Y7/s+GO/7LfjP+my3z/kKtj/xpHEbcAAAAAAAAAAJW0 79 | aP+nu1n/4s5j//z56f////7/79+X/0tCRf8oLGf/TWFr/6nVi/+z4Y7/msl7/1OEQv9snFX/ksF0/5LB 80 | dP91pFz/P3Ax/2ubVf+y4I3/s+GO/7Phjv+z4Y7/s+GO/7Phjv+z4Y7/s+GO/63Whf+dvXL/K1cd3wAA 81 | AAAAAAAAmbpt/57Bc/+Pp2T/kYVl/+vcm/9sX0X/i4uu/9/f6f8LDVX/S15q/7Phjv+FtGn/p9aF/7Ph 82 | jv+z4Y7/s+GO/7Phjv+r2Yj/Q3Q2/4m4bf+z4Y7/s+GO/7Ddjf+w3Y3/s+GO/7Phjf+52nj/utJp/6q8 83 | W/9AZyTvACoAAwAAAACbvG//oMR1/z9NYv/S0uD//////1lajP8qK2v/QUN7/wkLVP8LDVX/jbGB/7Ph 84 | jv+z4Y7/s+GO/7Phjv+z4Y7/s+GO/7Phjv+s2oj/sd+M/5nAhf86SWX/GiBa/x0kW/9HWWj/u64u/+vX 85 | e//x4qD/48dI/7GbA/dVXxUDAAAAAJy+cf+iyHn/ICdb/7Ozyv/Fxtf/ICJk/46Psf+io7//Q0V9/wkL 86 | VP9vjHf/s+GO/7Phjv+z4Y7/s+GO/7Phjv+z4Y7/s+GO/7Phjv+QtYL/Gx9f/0xNg/8MDlb/f4Cm/5qb 87 | uP+Ndyf/8OW3//r14P/nzl7/s5wA8pZ4DwEAAAAAnsJ0/6bNff8rNl7/Cw1V/woMVP+Jiq3///////// 88 | ///S0+D/ERNa/3CMd/+z4Y7/s+GO/7Phjv+z4Y7/s+GO/7Phjv+z4Y7/suCO/zxLZf+pqsP//f3+/zk7 89 | dv83OXT/UFGG/w0OUv9NUlb/vdFn/7bEX/8+ZSjGAAAAAAAAAAChxnf/qdKB/1Jmav8JC1T/CQtU/6Sl 90 | wP///////////+Dg6f8MDlb/nMSG/7Phjv+z4Y7/s+GO/7Phjv+z4Y7/s+GO/7Phjv+r14v/FRpY/3Z3 91 | oP+LjK//PT54/66vx/+Ehar/Fxle/x8nXP+s1of/tdSW/xBBDpUAAAAAAAAAAKXMfP+t2Ib/oMmF/yAo 92 | W/8JC1T/QEJ7/+nq8P/m5+7/RUd+/0haaf+z4Y7/s+GO/7Phjv+z4Y7/s+GO/7Phjv+z4Y7/s+GO/67b 93 | jP8dJFv/CQtU/w0PV//u7vP///////39/v9VV4n/HiVb/7DWjP+bvYf/ATMBXQAAAAAAAAAAqdKB/7Dc 94 | iv+x34z/lryD/ztKZf8SFlf/FRdc/xcbW/9TaW3/suCO/7Phjv+z4Y7/s+GO/7Phjv+z4Y7/s+GO/7Ph 95 | jv+z4Y7/s+GO/0ZXaf8JC1T/DhBY/+rq8f///////////01Pg/9KW2z/xOSl/0VwPO4AMAAMAAAAAAAA 96 | AACu2Yf/st+M/7Lgjf+y4I3/sN2N/6HLiP+awoX/qdSK/7Phjv+z4Y7/s+GO/7Phjv+z4Y7/s+GO/7Ph 97 | jv+z4Y7/s+GO/7Phjv+14pD/pcmS/xwhXf8JC1T/bm+a/9jY5P+fn7z/IShe/7ndn/+kxo3/AzUChwAA 98 | AAAAAAAAAAAAALHei/+z4I3/s+GO/7Phjv+z4Y7/s+GO/7Phjv+q2If/c6Jb/1mHQ/9di0b/hLJo/7Ph 99 | jv+z4Y7/s+GO/6zaif+345P/vuec/8ntq//R8bX/q8Sl/0ZQcv8aH13/ExZZ/zY/av+lw5n/wuWk/y1c 100 | JuYAMAALAAAAAAAAAAAAAAAAst+M/7Phjv+z4Y7/s+GO/7Phjv+z4Y7/ptSE/zNbG/9kdCT/cX0p/257 101 | KP9Kbyz/vuec/7zknP9EcTX/KVQZ/4y0d//P8LP/0/K4/9Xzuv/V87v/1PK6/8fjs//F4rH/z++y/8js 102 | qf+BrGz/ADIAbAAAAAAAAAAAAAAAAAAAAACy4I3/s+GO/7Phjv+z4Y7/s+GO/7Phjv+p14b/Qmgl/3J+ 103 | Kf95giz/d4Ar/1F7Pf/I7Kn/YY1S/1VqH/90fyr/QWIh/7TVnP/V87r/1vO7/9bzu//W87v/1fO6/9Ty 104 | uf/T8rf/yOyp/1iHR/4AMQAuAAAAAAAAAAAAAAAAAAAAALLgjf+z4Y7/s+GO/7Phjv+z4Y7/s+GO/7Ph 105 | jv+fzX7/Vno0/1xvIv9UayH/jbZ3/6jOj/8yVBL/eYIs/3qDLf96hjP/QWkw/87ttP/V87v/1vO7/9bz 106 | u//W87v/1fO7/83vsf+345T/TX49/AAyACAAAAAAAAAAAAAAAAAAAAAAxeql/8Xrpf/F66b/weig/7fj 107 | k/+z4Y7/s+GO/7fjkv+44pb/ibNy/4uzdf/J7av/MF0k/3R/Kv9+hzL/i5RA/5WeS/9nfjb/fKNs/9Xz 108 | u//W87v/1vS8/9bzu//F56b/rNSF/7Dbiv9Idzz0ADIAEQAAAAAAAAAAAAAAAAAAAADU8br/x+et/6nN 109 | kv+Ot3j/sNyO/7XikP+14pD/uOSV/77nnf/E6qT/yOyp/4y1eP9MZR3/hI04/5CZRf+ao1D/oapY/6Wv 110 | Xv9IcTn/0vC4/9Xzu//R8Lb/tteS/6HEd/+jx3n/u+CY/zpoMd8AKQACAAAAAAAAAAAAAAAAAAAAAChm 111 | LP8kZCv/J2ct/1aJSP+m0Yj/wemh/8TqpP/E6qT/xuum/8jsqv/J7az/P2ow/4eQPf+Vnkv/n6hW/6av 112 | Xv+xumr/vcZ3/09yNv/A4aj/ttiZ/6XHff+ewHL/n8J0/7TXj//L7K7/EkMPsAAAAAAAAAAAAAAAAAAA 113 | AAAAAAAARpZV/0ibWf9NqmT/SKJd/zd9P/9Qg0f/f6ls/7fcnP/L7q3/zO+v/7rdoP8/YSD/m6RR/6Wu 114 | XP+wuWj/usN0/73HeP+Vp1//FkUP/z5rMP+VuGv/oMJ1/6vMg/++3Zz/1fK6/6TGkP8AMwBvAAAAAAAA 115 | AAAAAAAAAAAAAAAAAABMp2L/VsR4/0+4bv9Kq2P/Tapk/0ifXP81gEL/J2ov/zhwNv9MfUP/RnY9/xRD 116 | Df86XyL/TG0u/VV0NfVEaCvlHEkS2CZVHPlwl1L/qcqD/73cm//L6a3/0/C4/9fzvf/Y9L7/YYpV9wAy 117 | ABMAAAAAAAAAAAAAAAAAAAAAAAAAAD6ZVf9JpmP/Wsl8/1jFef9Xw3f/Vb1z/1O4b/9StG3/Uq9q/1Wv 118 | bP9rxoj/ZLh//w9HE9wAMgAnADEAEwAzAAcAMQALBDYDPhlJFIInViG8Q3A56VV/SvtgiVT/X4hT/0l1 119 | QPUGOAV2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUa1s/3jln/935J3/fuql/4Ltqf+B7Kn/eeag/2/f 120 | lv9w3pb/ac2K/0GMVPwFOQaUADMACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAACADEADgAy 121 | ABYAMQAWADAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACM87T/i/Kz/3zcn/9nvYP/W6x0/kyY 122 | YP48hE31J2ky2BJNF7MEOQaDADEAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 123 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEOQVv0WUh29AjUCfgAz 124 | AFcAMgA4ADIAHQAtAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 125 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADEAIAAu 126 | AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 127 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 128 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 129 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 130 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 131 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 132 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAABAAAAAQAA 133 | AAEAAAADAAAAAwAAAAMAAAADAAAABwAAAAcAAAAPAAAADwAAAA8AAAAPAAAADwAAAB8AAAAfAAAAHwAA 134 | AD8AB/B/AB///wH///8///////////////8= 135 | 136 | 137 | -------------------------------------------------------------------------------- /AutoMaxLairUI - Code/Utils.cs: -------------------------------------------------------------------------------- 1 | namespace AutoMaxLair 2 | { 3 | class Utils 4 | { 5 | static public string UppercaseFirstLetter(string s) 6 | { 7 | // Check for empty string. 8 | if (string.IsNullOrEmpty(s)) 9 | { 10 | return string.Empty; 11 | } 12 | // Return char and concat substring. 13 | return char.ToUpper(s[0]) + s.Substring(1); 14 | } 15 | 16 | static public string ConvertBossNameToBossId(string bossName) 17 | { 18 | return bossName switch 19 | { 20 | "Tornadus" => "tornadus-incarnate", 21 | "Landorus" => "landorus-incarnate", 22 | "Thundurus" => "thundurus-incarnate", 23 | "Giratina" => "giratina-altered", 24 | "Zygarde" => "zygarde-50", 25 | _ => bossName.ToLower(), 26 | }; 27 | } 28 | 29 | static public string ConvertBossIdToBossName(string bossId) 30 | { 31 | return bossId switch 32 | { 33 | "tornadus-incarnate" => "Tornadus", 34 | "landorus-incarnate" => "Landorus", 35 | "thundurus-incarnate" => "Thundurus", 36 | "giratina-altered" => "Giratina", 37 | "zygarde-50" => "Zygarde", 38 | _ => UppercaseFirstLetter(bossId), 39 | }; 40 | } 41 | 42 | static public string ConvertBallNameToBallId(string ballName) 43 | { 44 | return ballName.ToLower() + "-ball"; 45 | } 46 | 47 | static public string ConvertBallIdToBallName(string ballId) 48 | { 49 | return UppercaseFirstLetter(ballId).Replace("-ball", ""); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AutoMaxLair 2 | **NOTE: the functionality of AutoMaxLair has now been implemented in the user-friendly [ComputerControl program suite](https://github.com/PokemonAutomation/ComputerControl).** 3 | We advise using the Python version only if: 4 | * You are a Mac user, 5 | * You want to modify the code yourself, or 6 | * You want to hunt bosses with certain IV distributions. 7 | 8 | All discussion related to both dynamax adventure bots can be found in the [Pokemon Automation Discord](https://discord.gg/PokemonAutomation). 9 | 10 | AutoMaxLair is a bot for shiny hunting legendary Pokemon in Dynamax Adventures found in Pokemon Sword and Shield: The Crown Tundra. The program runs on a computer connected to the Switch through a microcontroller (outgoing controls to the Switch) and an HDMI capture card (incoming video from the Switch). 11 | 12 | We have migrated our setup and operation instructions to a wiki. [Please refer there for detailed setup and troubleshooting instructions](https://github.com/PokemonAutomation/AutoMaxLair/wiki). 13 | 14 | ## Acknowledgements 15 | 16 | Thanks to [PokéSprite](https://github.com/msikma/pokesprite) for hosting the sprites of the Pokémon and balls and [PokéAPI](https://pokeapi.co/) for hosting data on Pokémon, abilities, and moves. We also thank [LUFA](http://www.lufa-lib.org/) for their AVR framework with which the microcontroller code would not work without. Finally, we thank [brianuuu](https://github.com/brianuuu) for the [AutoController](https://github.com/brianuuu/AutoController_swsh) program family on which our legacy microcontroller code is based. 17 | 18 | ## Contributors 19 | AutoMaxLair was initially written by [ercdndrs](https://github.com/ercdndrs). It has been supported by code contributions from [pifopi](https://github.com/pifopi) and [denvoros](https://github.com/denvoros), as well as advice and testing by multiple users in the [Pokemon Automation Discord](https://discord.gg/PokemonAutomation). We thank the server mod team, most notably [Mystical](https://github.com/Mysticial), for cultivating this space for us to meet, discuss, and collaborate. We also thank all the contributors shown by GitHub, and Discord users Miguel90 and fawress. 20 | 21 | ## Supporting us 22 | We do not take donations of any kind for this project. The only support we request is by sharing our work with your friends if you have enjoyed using it. Further, in the spirit of transparency, we would prefer that you disclose the use of our tool (or at least an indication of automation) when sharing photos of Pokemon caught using it. If such a disclosure is not permissible, we ask that you avoid any explicit or implicit claims that such Pokemon were caught manually. 23 | -------------------------------------------------------------------------------- /RemoteControl/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /RemoteControl/Config/Descriptors.c: -------------------------------------------------------------------------------- 1 | #include "Descriptors.h" 2 | 3 | // HID Descriptors. 4 | const USB_Descriptor_HIDReport_Datatype_t PROGMEM JoystickReport[] = { 5 | HID_RI_USAGE_PAGE(8,1), /* Generic Desktop */ 6 | HID_RI_USAGE(8,5), /* Joystick */ 7 | HID_RI_COLLECTION(8,1), /* Application */ 8 | // Buttons (2 bytes) 9 | HID_RI_LOGICAL_MINIMUM(8,0), 10 | HID_RI_LOGICAL_MAXIMUM(8,1), 11 | HID_RI_PHYSICAL_MINIMUM(8,0), 12 | HID_RI_PHYSICAL_MAXIMUM(8,1), 13 | // The Switch will allow us to expand the original HORI descriptors to a full 16 buttons. 14 | // The Switch will make use of 14 of those buttons. 15 | HID_RI_REPORT_SIZE(8,1), 16 | HID_RI_REPORT_COUNT(8,16), 17 | HID_RI_USAGE_PAGE(8,9), 18 | HID_RI_USAGE_MINIMUM(8,1), 19 | HID_RI_USAGE_MAXIMUM(8,16), 20 | HID_RI_INPUT(8,2), 21 | // HAT Switch (1 nibble) 22 | HID_RI_USAGE_PAGE(8,1), 23 | HID_RI_LOGICAL_MAXIMUM(8,7), 24 | HID_RI_PHYSICAL_MAXIMUM(16,315), 25 | HID_RI_REPORT_SIZE(8,4), 26 | HID_RI_REPORT_COUNT(8,1), 27 | HID_RI_UNIT(8,20), 28 | HID_RI_USAGE(8,57), 29 | HID_RI_INPUT(8,66), 30 | // There's an additional nibble here that's utilized as part of the Switch Pro Controller. 31 | // I believe this -might- be separate U/D/L/R bits on the Switch Pro Controller, as they're utilized as four button descriptors on the Switch Pro Controller. 32 | HID_RI_UNIT(8,0), 33 | HID_RI_REPORT_COUNT(8,1), 34 | HID_RI_INPUT(8,1), 35 | // Joystick (4 bytes) 36 | HID_RI_LOGICAL_MAXIMUM(16,255), 37 | HID_RI_PHYSICAL_MAXIMUM(16,255), 38 | HID_RI_USAGE(8,48), 39 | HID_RI_USAGE(8,49), 40 | HID_RI_USAGE(8,50), 41 | HID_RI_USAGE(8,53), 42 | HID_RI_REPORT_SIZE(8,8), 43 | HID_RI_REPORT_COUNT(8,4), 44 | HID_RI_INPUT(8,2), 45 | // ??? Vendor Specific (1 byte) 46 | // This byte requires additional investigation. 47 | HID_RI_USAGE_PAGE(16,65280), 48 | HID_RI_USAGE(8,32), 49 | HID_RI_REPORT_COUNT(8,1), 50 | HID_RI_INPUT(8,2), 51 | // Output (8 bytes) 52 | // Original observation of this suggests it to be a mirror of the inputs that we sent. 53 | // The Switch requires us to have these descriptors available. 54 | HID_RI_USAGE(16,9761), 55 | HID_RI_REPORT_COUNT(8,8), 56 | HID_RI_OUTPUT(8,2), 57 | HID_RI_END_COLLECTION(0), 58 | }; 59 | 60 | // Device Descriptor Structure 61 | const USB_Descriptor_Device_t PROGMEM DeviceDescriptor = { 62 | .Header = {.Size = sizeof(USB_Descriptor_Device_t), .Type = DTYPE_Device}, 63 | 64 | .USBSpecification = VERSION_BCD(2,0,0), 65 | .Class = USB_CSCP_NoDeviceClass, 66 | .SubClass = USB_CSCP_NoDeviceSubclass, 67 | .Protocol = USB_CSCP_NoDeviceProtocol, 68 | 69 | .Endpoint0Size = FIXED_CONTROL_ENDPOINT_SIZE, 70 | 71 | .VendorID = 0x0F0D, 72 | .ProductID = 0x0092, 73 | .ReleaseNumber = VERSION_BCD(1,0,0), 74 | 75 | .ManufacturerStrIndex = STRING_ID_Manufacturer, 76 | .ProductStrIndex = STRING_ID_Product, 77 | .SerialNumStrIndex = NO_DESCRIPTOR, 78 | 79 | .NumberOfConfigurations = FIXED_NUM_CONFIGURATIONS 80 | }; 81 | 82 | // Configuration Descriptor Structure 83 | const USB_Descriptor_Configuration_t PROGMEM ConfigurationDescriptor = { 84 | .Config = 85 | { 86 | .Header = {.Size = sizeof(USB_Descriptor_Configuration_Header_t), .Type = DTYPE_Configuration}, 87 | 88 | .TotalConfigurationSize = sizeof(USB_Descriptor_Configuration_t), 89 | .TotalInterfaces = 1, 90 | 91 | .ConfigurationNumber = 1, 92 | .ConfigurationStrIndex = NO_DESCRIPTOR, 93 | 94 | .ConfigAttributes = 0x80, 95 | 96 | .MaxPowerConsumption = USB_CONFIG_POWER_MA(500) 97 | }, 98 | 99 | .HID_Interface = 100 | { 101 | .Header = {.Size = sizeof(USB_Descriptor_Interface_t), .Type = DTYPE_Interface}, 102 | 103 | .InterfaceNumber = INTERFACE_ID_Joystick, 104 | .AlternateSetting = 0x00, 105 | 106 | .TotalEndpoints = 2, 107 | 108 | .Class = HID_CSCP_HIDClass, 109 | .SubClass = HID_CSCP_NonBootSubclass, 110 | .Protocol = HID_CSCP_NonBootProtocol, 111 | 112 | .InterfaceStrIndex = NO_DESCRIPTOR 113 | }, 114 | 115 | .HID_JoystickHID = 116 | { 117 | .Header = {.Size = sizeof(USB_HID_Descriptor_HID_t), .Type = HID_DTYPE_HID}, 118 | 119 | .HIDSpec = VERSION_BCD(1,1,1), 120 | .CountryCode = 0x00, 121 | .TotalReportDescriptors = 1, 122 | .HIDReportType = HID_DTYPE_Report, 123 | .HIDReportLength = sizeof(JoystickReport) 124 | }, 125 | 126 | .HID_ReportINEndpoint = 127 | { 128 | .Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type = DTYPE_Endpoint}, 129 | 130 | .EndpointAddress = JOYSTICK_IN_EPADDR, 131 | .Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA), 132 | .EndpointSize = JOYSTICK_EPSIZE, 133 | .PollingIntervalMS = 0x05 134 | }, 135 | 136 | .HID_ReportOUTEndpoint = 137 | { 138 | .Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type = DTYPE_Endpoint}, 139 | 140 | .EndpointAddress = JOYSTICK_OUT_EPADDR, 141 | .Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA), 142 | .EndpointSize = JOYSTICK_EPSIZE, 143 | .PollingIntervalMS = 0x05 144 | }, 145 | }; 146 | 147 | // Language Descriptor Structure 148 | const USB_Descriptor_String_t PROGMEM LanguageString = USB_STRING_DESCRIPTOR_ARRAY(LANGUAGE_ID_ENG); 149 | 150 | // Manufacturer and Product Descriptor Strings 151 | const USB_Descriptor_String_t PROGMEM ManufacturerString = USB_STRING_DESCRIPTOR(L"HORI CO.,LTD."); 152 | const USB_Descriptor_String_t PROGMEM ProductString = USB_STRING_DESCRIPTOR(L"POKKEN CONTROLLER"); 153 | 154 | // USB Device Callback - Get Descriptor 155 | uint16_t CALLBACK_USB_GetDescriptor( 156 | const uint16_t wValue, 157 | const uint16_t wIndex, 158 | const void** const DescriptorAddress 159 | ) { 160 | const uint16_t DescriptorType = (wValue >> 8); 161 | const uint16_t DescriptorNumber = (wValue & 0xFF); 162 | 163 | const void* Address = NULL; 164 | uint16_t Size = NO_DESCRIPTOR; 165 | 166 | switch (DescriptorType) 167 | { 168 | case DTYPE_Device: 169 | Address = &DeviceDescriptor; 170 | Size = sizeof(USB_Descriptor_Device_t); 171 | break; 172 | case DTYPE_Configuration: 173 | Address = &ConfigurationDescriptor; 174 | Size = sizeof(USB_Descriptor_Configuration_t); 175 | break; 176 | case DTYPE_String: 177 | switch (DescriptorNumber) 178 | { 179 | case STRING_ID_Language: 180 | Address = &LanguageString; 181 | Size = pgm_read_byte(&LanguageString.Header.Size); 182 | break; 183 | case STRING_ID_Manufacturer: 184 | Address = &ManufacturerString; 185 | Size = pgm_read_byte(&ManufacturerString.Header.Size); 186 | break; 187 | case STRING_ID_Product: 188 | Address = &ProductString; 189 | Size = pgm_read_byte(&ProductString.Header.Size); 190 | break; 191 | } 192 | 193 | break; 194 | case DTYPE_HID: 195 | Address = &ConfigurationDescriptor.HID_JoystickHID; 196 | Size = sizeof(USB_HID_Descriptor_HID_t); 197 | break; 198 | case DTYPE_Report: 199 | Address = &JoystickReport; 200 | Size = sizeof(JoystickReport); 201 | break; 202 | } 203 | 204 | *DescriptorAddress = Address; 205 | return Size; 206 | } 207 | 208 | -------------------------------------------------------------------------------- /RemoteControl/Config/Descriptors.h: -------------------------------------------------------------------------------- 1 | #ifndef _DESCRIPTORS_H_ 2 | #define _DESCRIPTORS_H_ 3 | 4 | // Includes 5 | #include 6 | 7 | #include 8 | 9 | // Type Defines 10 | // Device Configuration Descriptor Structure 11 | typedef struct 12 | { 13 | USB_Descriptor_Configuration_Header_t Config; 14 | 15 | // Joystick HID Interface 16 | USB_Descriptor_Interface_t HID_Interface; 17 | USB_HID_Descriptor_HID_t HID_JoystickHID; 18 | USB_Descriptor_Endpoint_t HID_ReportOUTEndpoint; 19 | USB_Descriptor_Endpoint_t HID_ReportINEndpoint; 20 | } USB_Descriptor_Configuration_t; 21 | 22 | // Device Interface Descriptor IDs 23 | enum InterfaceDescriptors_t 24 | { 25 | INTERFACE_ID_Joystick = 0, /**< Joystick interface descriptor ID */ 26 | }; 27 | 28 | // Device String Descriptor IDs 29 | enum StringDescriptors_t 30 | { 31 | STRING_ID_Language = 0, // Supported Languages string descriptor ID (must be zero) 32 | STRING_ID_Manufacturer = 1, // Manufacturer string ID 33 | STRING_ID_Product = 2, // Product string ID 34 | }; 35 | 36 | // Macros 37 | // Endpoint Addresses 38 | #define JOYSTICK_IN_EPADDR (ENDPOINT_DIR_IN | 1) 39 | #define JOYSTICK_OUT_EPADDR (ENDPOINT_DIR_OUT | 2) 40 | // HID Endpoint Size 41 | // The Switch -needs- this to be 64. 42 | // The Wii U is flexible, allowing us to use the default of 8 (which did not match the original Hori descriptors). 43 | #define JOYSTICK_EPSIZE 64 44 | // Descriptor Header Type - HID Class HID Descriptor 45 | #define DTYPE_HID 0x21 46 | // Descriptor Header Type - HID Class HID Report Descriptor 47 | #define DTYPE_Report 0x22 48 | 49 | // Function Prototypes 50 | uint16_t CALLBACK_USB_GetDescriptor( 51 | const uint16_t wValue, 52 | const uint16_t wIndex, 53 | const void** const DescriptorAddress 54 | ) ATTR_WARN_UNUSED_RESULT ATTR_NON_NULL_PTR_ARG(3); 55 | 56 | #endif 57 | 58 | -------------------------------------------------------------------------------- /RemoteControl/Config/LUFAConfig.h: -------------------------------------------------------------------------------- 1 | // LUFA Library Configuration Header File. Used to configure LUFA's compile time options, as an alternative to the compile-time defines. 2 | #ifndef _LUFA_CONFIG_H_ 3 | #define _LUFA_CONFIG_H_ 4 | #if (ARCH == ARCH_AVR8) 5 | // Non-USB Related Configuration Tokens 6 | // #define DISABLE_TERMINAL_CODES 7 | 8 | // USB Class Driver Related Tokens 9 | // #define HID_HOST_BOOT_PROTOCOL_ONLY 10 | // #define HID_STATETABLE_STACK_DEPTH {Insert Value Here} 11 | // #define HID_USAGE_STACK_DEPTH {Insert Value Here} 12 | // #define HID_MAX_COLLECTIONS {Insert Value Here} 13 | // #define HID_MAX_REPORTITEMS {Insert Value Here} 14 | // #define HID_MAX_REPORT_IDS {Insert Value Here} 15 | // #define NO_CLASS_DRIVER_AUTOFLUSH 16 | 17 | // General USB Driver Related Tokens 18 | // #define ORDERED_EP_CONFIG 19 | #define USE_STATIC_OPTIONS (USB_DEVICE_OPT_FULLSPEED | USB_OPT_REG_ENABLED | USB_OPT_AUTO_PLL) 20 | #define USB_DEVICE_ONLY 21 | // #define USB_HOST_ONLY 22 | // #define USB_STREAM_TIMEOUT_MS {Insert Value Here} 23 | // #define NO_LIMITED_CONTROLLER_CONNECT 24 | // #define NO_SOF_EVENTS 25 | 26 | // USB Device Mode Driver Related Tokens 27 | // #define USE_RAM_DESCRIPTORS 28 | #define USE_FLASH_DESCRIPTORS 29 | // #define USE_EEPROM_DESCRIPTORS 30 | // #define NO_INTERNAL_SERIAL 31 | #define FIXED_CONTROL_ENDPOINT_SIZE 64 32 | // #define DEVICE_STATE_AS_GPIOR {Insert Value Here} 33 | #define FIXED_NUM_CONFIGURATIONS 1 34 | // #define CONTROL_ONLY_DEVICE 35 | // #define INTERRUPT_CONTROL_ENDPOINT 36 | // #define NO_DEVICE_REMOTE_WAKEUP 37 | // #define NO_DEVICE_SELF_POWER 38 | 39 | // USB Host Mode Driver Related Tokens 40 | // #define HOST_STATE_AS_GPIOR {Insert Value Here} 41 | // #define USB_HOST_TIMEOUT_MS {Insert Value Here} 42 | // #define HOST_DEVICE_SETTLE_DELAY_MS {Insert Value Here} 43 | // #define NO_AUTO_VBUS_MANAGEMENT 44 | // #define INVERTED_VBUS_ENABLE_LINE 45 | 46 | #elif (ARCH == ARCH_XMEGA) 47 | // Non-USB Related Configuration Tokens 48 | // #define DISABLE_TERMINAL_CODES 49 | 50 | // USB Class Driver Related Tokens 51 | // #define HID_HOST_BOOT_PROTOCOL_ONLY 52 | // #define HID_STATETABLE_STACK_DEPTH {Insert Value Here} 53 | // #define HID_USAGE_STACK_DEPTH {Insert Value Here} 54 | // #define HID_MAX_COLLECTIONS {Insert Value Here} 55 | // #define HID_MAX_REPORTITEMS {Insert Value Here} 56 | // #define HID_MAX_REPORT_IDS {Insert Value Here} 57 | // #define NO_CLASS_DRIVER_AUTOFLUSH 58 | 59 | // General USB Driver Related Tokens 60 | #define USE_STATIC_OPTIONS (USB_DEVICE_OPT_FULLSPEED | USB_OPT_RC32MCLKSRC | USB_OPT_BUSEVENT_PRIHIGH) 61 | // #define USB_STREAM_TIMEOUT_MS {Insert Value Here} 62 | // #define NO_LIMITED_CONTROLLER_CONNECT 63 | // #define NO_SOF_EVENTS 64 | 65 | // USB Device Mode Driver Related Tokens 66 | // #define USE_RAM_DESCRIPTORS 67 | #define USE_FLASH_DESCRIPTORS 68 | // #define USE_EEPROM_DESCRIPTORS 69 | // #define NO_INTERNAL_SERIAL 70 | #define FIXED_CONTROL_ENDPOINT_SIZE 64 71 | // #define DEVICE_STATE_AS_GPIOR {Insert Value Here} 72 | #define FIXED_NUM_CONFIGURATIONS 1 73 | // #define CONTROL_ONLY_DEVICE 74 | #define MAX_ENDPOINT_INDEX 1 75 | // #define NO_DEVICE_REMOTE_WAKEUP 76 | // #define NO_DEVICE_SELF_POWER 77 | 78 | #else 79 | #error Unsupported architecture for this LUFA configuration file. 80 | #endif 81 | #endif 82 | -------------------------------------------------------------------------------- /RemoteControl/HORI_Descriptors: -------------------------------------------------------------------------------- 1 | ID 0f0d:0092 Hori Co., Ltd 2 | Device Descriptor: 3 | bLength 18 4 | bDescriptorType 1 5 | bcdUSB 2.00 6 | bDeviceClass 0 7 | bDeviceSubClass 0 8 | bDeviceProtocol 0 9 | bMaxPacketSize0 64 10 | idVendor 0x0f0d Hori Co., Ltd 11 | idProduct 0x0092 12 | bcdDevice 1.00 13 | iManufacturer 1 HORI CO.,LTD. 14 | iProduct 2 POKKEN CONTROLLER 15 | iSerial 0 16 | bNumConfigurations 1 17 | Configuration Descriptor: 18 | bLength 9 19 | bDescriptorType 2 20 | wTotalLength 41 21 | bNumInterfaces 1 22 | bConfigurationValue 1 23 | iConfiguration 0 24 | bmAttributes 0x80 25 | (Bus Powered) 26 | MaxPower 500mA 27 | Interface Descriptor: 28 | bLength 9 29 | bDescriptorType 4 30 | bInterfaceNumber 0 31 | bAlternateSetting 0 32 | bNumEndpoints 2 33 | bInterfaceClass 3 Human Interface Device 34 | bInterfaceSubClass 0 35 | bInterfaceProtocol 0 36 | iInterface 0 37 | HID Device Descriptor: 38 | bLength 9 39 | bDescriptorType 33 40 | bcdHID 1.11 41 | bCountryCode 0 Not supported 42 | bNumDescriptors 1 43 | bDescriptorType 34 Report 44 | wDescriptorLength 90 45 | Report Descriptor: (length is 90) 46 | Item(Global): Usage Page, data= [ 0x01 ] 1 47 | Generic Desktop Controls 48 | Item(Local ): Usage, data= [ 0x05 ] 5 49 | Gamepad 50 | Item(Main ): Collection, data= [ 0x01 ] 1 51 | Application 52 | Item(Global): Logical Minimum, data= [ 0x00 ] 0 53 | Item(Global): Logical Maximum, data= [ 0x01 ] 1 54 | Item(Global): Physical Minimum, data= [ 0x00 ] 0 55 | Item(Global): Physical Maximum, data= [ 0x01 ] 1 56 | Item(Global): Report Size, data= [ 0x01 ] 1 57 | Item(Global): Report Count, data= [ 0x0d ] 13 58 | Item(Global): Usage Page, data= [ 0x09 ] 9 59 | Buttons 60 | Item(Local ): Usage Minimum, data= [ 0x01 ] 1 61 | Button 1 (Primary) 62 | Item(Local ): Usage Maximum, data= [ 0x0d ] 13 63 | (null) 64 | Item(Main ): Input, data= [ 0x02 ] 2 65 | Data Variable Absolute No_Wrap Linear 66 | Preferred_State No_Null_Position Non_Volatile Bitfield 67 | Item(Global): Report Count, data= [ 0x03 ] 3 68 | Item(Main ): Input, data= [ 0x01 ] 1 69 | Constant Array Absolute No_Wrap Linear 70 | Preferred_State No_Null_Position Non_Volatile Bitfield 71 | Item(Global): Usage Page, data= [ 0x01 ] 1 72 | Generic Desktop Controls 73 | Item(Global): Logical Maximum, data= [ 0x07 ] 7 74 | Item(Global): Physical Maximum, data= [ 0x3b 0x01 ] 315 75 | Item(Global): Report Size, data= [ 0x04 ] 4 76 | Item(Global): Report Count, data= [ 0x01 ] 1 77 | Item(Global): Unit, data= [ 0x14 ] 20 78 | System: English Rotation, Unit: Degrees 79 | Item(Local ): Usage, data= [ 0x39 ] 57 80 | Hat Switch 81 | Item(Main ): Input, data= [ 0x42 ] 66 82 | Data Variable Absolute No_Wrap Linear 83 | Preferred_State Null_State Non_Volatile Bitfield 84 | Item(Global): Unit, data= [ 0x00 ] 0 85 | System: None, Unit: (None) 86 | Item(Global): Report Count, data= [ 0x01 ] 1 87 | Item(Main ): Input, data= [ 0x01 ] 1 88 | Constant Array Absolute No_Wrap Linear 89 | Preferred_State No_Null_Position Non_Volatile Bitfield 90 | Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 91 | Item(Global): Physical Maximum, data= [ 0xff 0x00 ] 255 92 | Item(Local ): Usage, data= [ 0x30 ] 48 93 | Direction-X 94 | Item(Local ): Usage, data= [ 0x31 ] 49 95 | Direction-Y 96 | Item(Local ): Usage, data= [ 0x32 ] 50 97 | Direction-Z 98 | Item(Local ): Usage, data= [ 0x35 ] 53 99 | Rotate-Z 100 | Item(Global): Report Size, data= [ 0x08 ] 8 101 | Item(Global): Report Count, data= [ 0x04 ] 4 102 | Item(Main ): Input, data= [ 0x02 ] 2 103 | Data Variable Absolute No_Wrap Linear 104 | Preferred_State No_Null_Position Non_Volatile Bitfield 105 | Item(Global): Usage Page, data= [ 0x00 0xff ] 65280 106 | (null) 107 | Item(Local ): Usage, data= [ 0x20 ] 32 108 | (null) 109 | Item(Global): Report Count, data= [ 0x01 ] 1 110 | Item(Main ): Input, data= [ 0x02 ] 2 111 | Data Variable Absolute No_Wrap Linear 112 | Preferred_State No_Null_Position Non_Volatile Bitfield 113 | Item(Local ): Usage, data= [ 0x21 0x26 ] 9761 114 | (null) 115 | Item(Global): Report Count, data= [ 0x08 ] 8 116 | Item(Main ): Output, data= [ 0x02 ] 2 117 | Data Variable Absolute No_Wrap Linear 118 | Preferred_State No_Null_Position Non_Volatile Bitfield 119 | Item(Main ): End Collection, data=none 120 | Endpoint Descriptor: 121 | bLength 7 122 | bDescriptorType 5 123 | bEndpointAddress 0x02 EP 2 OUT 124 | bmAttributes 3 125 | Transfer Type Interrupt 126 | Synch Type None 127 | Usage Type Data 128 | wMaxPacketSize 0x0040 1x 64 bytes 129 | bInterval 5 130 | Endpoint Descriptor: 131 | bLength 7 132 | bDescriptorType 5 133 | bEndpointAddress 0x81 EP 1 IN 134 | bmAttributes 3 135 | Transfer Type Interrupt 136 | Synch Type None 137 | Usage Type Data 138 | wMaxPacketSize 0x0040 1x 64 bytes 139 | bInterval 5 140 | Device Status: 0x0000 141 | (Bus Powered) -------------------------------------------------------------------------------- /RemoteControl/Joystick.h: -------------------------------------------------------------------------------- 1 | /* 2 | LUFA Library 3 | Copyright (C) Dean Camera, 2014. 4 | 5 | dean [at] fourwalledcubicle [dot] com 6 | www.lufa-lib.org 7 | */ 8 | 9 | /* 10 | Copyright 2014 Dean Camera (dean [at] fourwalledcubicle [dot] com) 11 | 12 | Permission to use, copy, modify, distribute, and sell this 13 | software and its documentation for any purpose is hereby granted 14 | without fee, provided that the above copyright notice appear in 15 | all copies and that both that the copyright notice and this 16 | permission notice and warranty disclaimer appear in supporting 17 | documentation, and that the name of the author not be used in 18 | advertising or publicity pertaining to distribution of the 19 | software without specific, written prior permission. 20 | 21 | The author disclaims all warranties with regard to this 22 | software, including all implied warranties of merchantability 23 | and fitness. In no event shall the author be liable for any 24 | special, indirect or consequential damages or any damages 25 | whatsoever resulting from loss of use, data or profits, whether 26 | in an action of contract, negligence or other tortious action, 27 | arising out of or in connection with the use or performance of 28 | this software. 29 | */ 30 | 31 | /** \file 32 | * 33 | * Header file for Joystick.c. 34 | */ 35 | 36 | #ifndef _JOYSTICK_H_ 37 | #define _JOYSTICK_H_ 38 | 39 | /* Includes: */ 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | 53 | #include "Descriptors.h" 54 | 55 | // Type Defines 56 | // Enumeration for joystick buttons. 57 | typedef enum { 58 | SWITCH_Y = 0x01, 59 | SWITCH_B = 0x02, 60 | SWITCH_A = 0x04, 61 | SWITCH_X = 0x08, 62 | SWITCH_L = 0x10, 63 | SWITCH_R = 0x20, 64 | SWITCH_ZL = 0x40, 65 | SWITCH_ZR = 0x80, 66 | SWITCH_MINUS = 0x100, 67 | SWITCH_PLUS = 0x200, 68 | SWITCH_LCLICK = 0x400, 69 | SWITCH_RCLICK = 0x800, 70 | SWITCH_HOME = 0x1000, 71 | SWITCH_CAPTURE = 0x2000, 72 | } JoystickButtons_t; 73 | 74 | #define HAT_TOP 0x00 75 | #define HAT_TOP_RIGHT 0x01 76 | #define HAT_RIGHT 0x02 77 | #define HAT_BOTTOM_RIGHT 0x03 78 | #define HAT_BOTTOM 0x04 79 | #define HAT_BOTTOM_LEFT 0x05 80 | #define HAT_LEFT 0x06 81 | #define HAT_TOP_LEFT 0x07 82 | #define HAT_CENTER 0x08 83 | 84 | #define STICK_MIN 0 85 | #define STICK_CENTER 128 86 | #define STICK_MAX 255 87 | 88 | // Joystick HID report structure. We have an input and an output. 89 | typedef struct { 90 | uint16_t Button; // 16 buttons; see JoystickButtons_t for bit mapping 91 | uint8_t HAT; // HAT switch; one nibble w/ unused nibble 92 | uint8_t LX; // Left Stick X 93 | uint8_t LY; // Left Stick Y 94 | uint8_t RX; // Right Stick X 95 | uint8_t RY; // Right Stick Y 96 | uint8_t VendorSpec; 97 | } USB_JoystickReport_Input_t; 98 | 99 | // The output is structured as a mirror of the input. 100 | // This is based on initial observations of the Pokken Controller. 101 | typedef struct { 102 | uint16_t Button; // 16 buttons; see JoystickButtons_t for bit mapping 103 | uint8_t HAT; // HAT switch; one nibble w/ unused nibble 104 | uint8_t LX; // Left Stick X 105 | uint8_t LY; // Left Stick Y 106 | uint8_t RX; // Right Stick X 107 | uint8_t RY; // Right Stick Y 108 | } USB_JoystickReport_Output_t; 109 | 110 | // Custom button mapping, only provide one combination per enum type 111 | typedef enum { 112 | UP, 113 | DOWN, 114 | LEFT, 115 | RIGHT, 116 | X, 117 | Y, 118 | A, 119 | B, 120 | L, 121 | R, 122 | ZL, 123 | ZR, 124 | MINUS, 125 | PLUS, 126 | LCLICK, 127 | RCLICK, 128 | TRIGGERS, 129 | HOME, 130 | CAPTURE, 131 | NOTHING 132 | } Buttons_t; 133 | 134 | // Structure used for button array sequence 135 | typedef struct { 136 | Buttons_t button; 137 | uint16_t duration; 138 | } Command; 139 | 140 | // Function Prototypes 141 | // Setup all necessary hardware, including USB initialization. 142 | void SetupHardware(void); 143 | 144 | // Process and deliver data from IN and OUT endpoints. 145 | void HID_Task(void); 146 | 147 | // USB device event handlers. 148 | void EVENT_USB_Device_Connect(void); 149 | void EVENT_USB_Device_Disconnect(void); 150 | void EVENT_USB_Device_ConfigurationChanged(void); 151 | void EVENT_USB_Device_ControlRequest(void); 152 | 153 | // Prepare the next report for the host. 154 | void GetNextReport(USB_JoystickReport_Input_t* const ReportData); 155 | #endif 156 | -------------------------------------------------------------------------------- /RemoteControl/LUFA_LICENSE: -------------------------------------------------------------------------------- 1 | LUFA Library 2 | Copyright (C) Dean Camera, 2014. 3 | 4 | dean [at] fourwalledcubicle [dot] com 5 | www.lufa-lib.org 6 | 7 | 8 | Permission to use, copy, modify, and distribute this software 9 | and its documentation for any purpose is hereby granted without 10 | fee, provided that the above copyright notice appear in all 11 | copies and that both that the copyright notice and this 12 | permission notice and warranty disclaimer appear in supporting 13 | documentation, and that the name of the author not be used in 14 | advertising or publicity pertaining to distribution of the 15 | software without specific, written prior permission. 16 | 17 | The author disclaims all warranties with regard to this 18 | software, including all implied warranties of merchantability 19 | and fitness. In no event shall the author be liable for any 20 | special, indirect or consequential damages or any damages 21 | whatsoever resulting from loss of use, data or profits, whether 22 | in an action of contract, negligence or other tortious action, 23 | arising out of or in connection with the use or performance of 24 | this software. 25 | -------------------------------------------------------------------------------- /RemoteControl/MakeProgram.bat: -------------------------------------------------------------------------------- 1 | @echo OFF 2 | make 3 | make clean 4 | pause -------------------------------------------------------------------------------- /RemoteControl/Source/Config.h: -------------------------------------------------------------------------------- 1 | // WARNING: You are not adviced to change this manually 2 | // Please run AutoControllerHelper tool!!! 3 | 4 | -------------------------------------------------------------------------------- /RemoteControl/Source/RemoteControl.c: -------------------------------------------------------------------------------- 1 | /* 2 | Pokemon Sword & Shield controller abstraction 3 | Eric Donders 4 | 2020-11-19 5 | 6 | Based on the LUFA library's Low-Level Joystick Demo 7 | (C) Dean Camera 8 | Based on the HORI's Pokken Tournament Pro Pad design 9 | (C) HORI 10 | 11 | This project implements a modified version of HORI's Pokken Tournament Pro Pad 12 | USB descriptors to allow for the creation of custom controllers for the 13 | Nintendo Switch. This also works to a limited degree on the PS3. 14 | 15 | Since System Update v3.0.0, the Nintendo Switch recognizes the Pokken 16 | Tournament Pro Pad as a Pro Controller. Physical design limitations prevent 17 | the Pokken Controller from functioning at the same level as the Pro 18 | Controller. However, by default most of the descriptors are there, with the 19 | exception of Home and Capture. Descriptor modification allows us to unlock 20 | these buttons for our use. 21 | 22 | This project is based on auto-controller code written by brianuuuuSonic 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include "Joystick.h" 29 | #include "Config.h" 30 | #include "uart.h" 31 | 32 | #define CPU_PRESCALE(n) (CLKPR = 0x80, CLKPR = (n)) 33 | 34 | // Main entry point. 35 | int main(void) { 36 | // We'll start by performing hardware and peripheral setup. 37 | SetupHardware(); 38 | // We'll then enable global interrupts for our use. 39 | GlobalInterruptEnable(); 40 | // Then we'll initialize the serial communications with the external computer 41 | 42 | // Once that's done, we'll enter an infinite loop. 43 | for (;;) 44 | { 45 | // We need to run our task to process and deliver data for our IN and OUT endpoints. 46 | HID_Task(); 47 | // We also need to run the main USB management task. 48 | USB_USBTask(); 49 | } 50 | } 51 | 52 | // Configures hardware and peripherals, such as the USB peripherals. 53 | void SetupHardware(void) { 54 | // We need to disable watchdog if enabled by bootloader/fuses. 55 | MCUSR &= ~(1 << WDRF); 56 | wdt_disable(); 57 | 58 | // We need to disable clock division before initializing the USB hardware. 59 | //clock_prescale_set(clock_div_1); 60 | // We can then initialize our hardware and peripherals, including the USB stack. 61 | CPU_PRESCALE(0); // run at 16 MHz 62 | uart_init(9600); 63 | #ifdef ALERT_WHEN_DONE 64 | // Both PORTD and PORTB will be used for the optional LED flashing and buzzer. 65 | #warning LED and Buzzer functionality enabled. All pins on both PORTB and \ 66 | PORTD will toggle when printing is done. 67 | DDRD = 0xFF; //Teensy uses PORTD 68 | PORTD = 0x0; 69 | //We'll just flash all pins on both ports since the UNO R3 70 | DDRB = 0xFF; //uses PORTB. Micro can use either or, but both give us 2 LEDs 71 | PORTB = 0x0; //The ATmega328P on the UNO will be resetting, so unplug it? 72 | #endif 73 | // The USB stack should be initialized last. 74 | USB_Init(); 75 | } 76 | 77 | // Fired to indicate that the device is enumerating. 78 | void EVENT_USB_Device_Connect(void) { 79 | // We can indicate that we're enumerating here (via status LEDs, sound, etc.). 80 | } 81 | 82 | // Fired to indicate that the device is no longer connected to a host. 83 | void EVENT_USB_Device_Disconnect(void) { 84 | // We can indicate that our device is not ready (via status LEDs, sound, etc.). 85 | } 86 | 87 | // Fired when the host set the current configuration of the USB device after enumeration. 88 | void EVENT_USB_Device_ConfigurationChanged(void) { 89 | bool ConfigSuccess = true; 90 | 91 | // We setup the HID report endpoints. 92 | ConfigSuccess &= Endpoint_ConfigureEndpoint(JOYSTICK_OUT_EPADDR, EP_TYPE_INTERRUPT, JOYSTICK_EPSIZE, 1); 93 | ConfigSuccess &= Endpoint_ConfigureEndpoint(JOYSTICK_IN_EPADDR, EP_TYPE_INTERRUPT, JOYSTICK_EPSIZE, 1); 94 | 95 | // We can read ConfigSuccess to indicate a success or failure at this point. 96 | } 97 | 98 | // Process control requests sent to the device from the USB host. 99 | void EVENT_USB_Device_ControlRequest(void) { 100 | // We can handle two control requests: a GetReport and a SetReport. 101 | 102 | // Not used here, it looks like we don't receive control request from the Switch. 103 | } 104 | 105 | typedef enum { 106 | PROCESS, 107 | DONE 108 | } State_t; 109 | State_t state = PROCESS; 110 | char received_command = 'a'; 111 | unsigned int command_duration = 1; 112 | 113 | // Process and deliver data from IN and OUT endpoints. 114 | void HID_Task(void) { 115 | // If the device isn't connected and properly configured, we can't do anything here. 116 | if (USB_DeviceState != DEVICE_STATE_Configured) 117 | return; 118 | 119 | // We'll start with the OUT endpoint. 120 | Endpoint_SelectEndpoint(JOYSTICK_OUT_EPADDR); 121 | // We'll check to see if we received something on the OUT endpoint. 122 | if (Endpoint_IsOUTReceived()) 123 | { 124 | // If we did, and the packet has data, we'll react to it. 125 | if (Endpoint_IsReadWriteAllowed()) 126 | { 127 | // We'll create a place to store our data received from the host. 128 | USB_JoystickReport_Output_t JoystickOutputData; 129 | // We'll then take in that data, setting it up in our storage. 130 | while(Endpoint_Read_Stream_LE(&JoystickOutputData, sizeof(JoystickOutputData), NULL) != ENDPOINT_RWSTREAM_NoError); 131 | // At this point, we can react to this data. 132 | 133 | // However, since we're not doing anything with this data, we abandon it. 134 | } 135 | // Regardless of whether we reacted to the data, we acknowledge an OUT packet on this endpoint. 136 | Endpoint_ClearOUT(); 137 | } 138 | 139 | // We'll then move on to the IN endpoint. 140 | Endpoint_SelectEndpoint(JOYSTICK_IN_EPADDR); 141 | // We first check to see if the host is ready to accept data. 142 | if (Endpoint_IsINReady()) 143 | { 144 | // We'll create an empty report. 145 | USB_JoystickReport_Input_t JoystickInputData; 146 | // We'll then populate this report with what we want to send to the host. 147 | GetNextReport(&JoystickInputData); 148 | // Once populated, we can output this data to the host. We do this by first writing the data to the control stream. 149 | while(Endpoint_Write_Stream_LE(&JoystickInputData, sizeof(JoystickInputData), NULL) != ENDPOINT_RWSTREAM_NoError); 150 | // We then send an IN packet on this endpoint. 151 | Endpoint_ClearIN(); 152 | } 153 | // Lastly we'll check the serial port for incoming messages from the computer 154 | if (uart_available() > 1) { 155 | received_command = uart_getchar(); 156 | command_duration = (unsigned int) uart_getchar(); 157 | state = PROCESS; 158 | uart_putchar(received_command); // DEBUG echo incoming messages back to computer 159 | uart_putchar(command_duration); 160 | } 161 | } 162 | 163 | unsigned int echoes = 10; 164 | USB_JoystickReport_Input_t last_report; 165 | 166 | // Prepare the next report for the host. 167 | void GetNextReport(USB_JoystickReport_Input_t* const ReportData) { 168 | 169 | // Prepare an empty report 170 | memset(ReportData, 0, sizeof(USB_JoystickReport_Input_t)); 171 | ReportData->LX = STICK_CENTER; 172 | ReportData->LY = STICK_CENTER; 173 | ReportData->RX = STICK_CENTER; 174 | ReportData->RY = STICK_CENTER; 175 | ReportData->HAT = HAT_CENTER; 176 | 177 | // Repeat ECHOES times the last report 178 | if (echoes > 0) 179 | { 180 | memcpy(ReportData, &last_report, sizeof(USB_JoystickReport_Input_t)); 181 | echoes--; 182 | return; 183 | } 184 | 185 | // States and moves management 186 | switch (state) 187 | { 188 | case PROCESS: 189 | switch (received_command) 190 | { 191 | case '^': 192 | ReportData->LY = STICK_MIN; 193 | break; 194 | 195 | case '<': 196 | ReportData->LX = STICK_MIN; 197 | break; 198 | 199 | case 'v': 200 | ReportData->LY = STICK_MAX; 201 | break; 202 | 203 | case '>': 204 | ReportData->LX = STICK_MAX; 205 | break; 206 | 207 | case '8': 208 | ReportData->RY = STICK_MIN; 209 | break; 210 | 211 | case '4': 212 | ReportData->RX = STICK_MIN; 213 | break; 214 | 215 | case '2': 216 | ReportData->RY = STICK_MAX; 217 | break; 218 | 219 | case '6': 220 | ReportData->RX = STICK_MAX; 221 | break; 222 | 223 | case 'x': 224 | ReportData->Button |= SWITCH_X; 225 | break; 226 | 227 | case 'y': 228 | ReportData->Button |= SWITCH_Y; 229 | break; 230 | 231 | case 'a': 232 | ReportData->Button |= SWITCH_A; 233 | break; 234 | 235 | case 'b': 236 | ReportData->Button |= SWITCH_B; 237 | break; 238 | 239 | case 'l': 240 | ReportData->Button |= SWITCH_L; 241 | break; 242 | 243 | case 'r': 244 | ReportData->Button |= SWITCH_R; 245 | break; 246 | 247 | case 'L': 248 | ReportData->Button |= SWITCH_ZL; 249 | break; 250 | 251 | case 'R': 252 | ReportData->Button |= SWITCH_ZR; 253 | break; 254 | 255 | case '-': 256 | ReportData->Button |= SWITCH_MINUS; 257 | break; 258 | 259 | case '+': 260 | ReportData->Button |= SWITCH_PLUS; 261 | break; 262 | 263 | case 'C': 264 | ReportData->Button |= SWITCH_LCLICK; 265 | break; 266 | 267 | case 'c': 268 | ReportData->Button |= SWITCH_RCLICK; 269 | break; 270 | 271 | case 't': 272 | ReportData->Button |= SWITCH_L | SWITCH_R; 273 | break; 274 | 275 | case 'h': 276 | ReportData->Button |= SWITCH_HOME; 277 | break; 278 | 279 | case 'p': 280 | ReportData->Button |= SWITCH_CAPTURE; 281 | break; 282 | 283 | default: 284 | // really nothing lol 285 | command_duration = 1; 286 | break; 287 | } 288 | 289 | state = DONE; 290 | //uart_putchar('d'); 291 | received_command = '0'; 292 | break; 293 | 294 | case DONE: return; 295 | } 296 | 297 | // Prepare to echo this report 298 | memcpy(&last_report, ReportData, sizeof(USB_JoystickReport_Input_t)); 299 | 300 | echoes = command_duration * 10; 301 | } -------------------------------------------------------------------------------- /RemoteControl/Source/uart.c: -------------------------------------------------------------------------------- 1 | /* UART Example for Teensy USB Development Board 2 | * http://www.pjrc.com/teensy/ 3 | * Copyright (c) 2009 PJRC.COM, LLC 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | * THE SOFTWARE. 22 | */ 23 | 24 | // Version 1.0: Initial Release 25 | // Version 1.1: Add support for Teensy 2.0, minor optimizations 26 | 27 | 28 | #include 29 | #include 30 | 31 | #include "uart.h" 32 | 33 | // These buffers may be any size from 2 to 256 bytes. 34 | #define RX_BUFFER_SIZE 64 35 | #define TX_BUFFER_SIZE 40 36 | 37 | static volatile uint8_t tx_buffer[TX_BUFFER_SIZE]; 38 | static volatile uint8_t tx_buffer_head; 39 | static volatile uint8_t tx_buffer_tail; 40 | static volatile uint8_t rx_buffer[RX_BUFFER_SIZE]; 41 | static volatile uint8_t rx_buffer_head; 42 | static volatile uint8_t rx_buffer_tail; 43 | 44 | // Initialize the UART 45 | void uart_init(uint32_t baud) 46 | { 47 | cli(); 48 | UBRR1 = (F_CPU / 4 / baud - 1) / 2; 49 | UCSR1A = (1<= TX_BUFFER_SIZE) i = 0; 64 | while (tx_buffer_tail == i) ; // wait until space in buffer 65 | //cli(); 66 | tx_buffer[i] = c; 67 | tx_buffer_head = i; 68 | UCSR1B = (1<= RX_BUFFER_SIZE) i = 0; 80 | c = rx_buffer[i]; 81 | rx_buffer_tail = i; 82 | return c; 83 | } 84 | 85 | // Return the number of bytes waiting in the receive buffer. 86 | // Call this before uart_getchar() to check if it will need 87 | // to wait for a byte to arrive. 88 | uint8_t uart_available(void) 89 | { 90 | uint8_t head, tail; 91 | 92 | head = rx_buffer_head; 93 | tail = rx_buffer_tail; 94 | if (head >= tail) return head - tail; 95 | return RX_BUFFER_SIZE + head - tail; 96 | } 97 | 98 | // Transmit Interrupt 99 | ISR(USART1_UDRE_vect) 100 | { 101 | uint8_t i; 102 | 103 | if (tx_buffer_head == tx_buffer_tail) { 104 | // buffer is empty, disable transmit interrupt 105 | UCSR1B = (1<= TX_BUFFER_SIZE) i = 0; 109 | UDR1 = tx_buffer[i]; 110 | tx_buffer_tail = i; 111 | } 112 | } 113 | 114 | // Receive Interrupt 115 | ISR(USART1_RX_vect) 116 | { 117 | uint8_t c, i; 118 | 119 | c = UDR1; 120 | i = rx_buffer_head + 1; 121 | if (i >= RX_BUFFER_SIZE) i = 0; 122 | if (i != rx_buffer_tail) { 123 | rx_buffer[i] = c; 124 | rx_buffer_head = i; 125 | } 126 | } 127 | 128 | -------------------------------------------------------------------------------- /RemoteControl/Source/uart.h: -------------------------------------------------------------------------------- 1 | #ifndef _uart_included_h_ 2 | #define _uart_included_h_ 3 | 4 | #include 5 | 6 | void uart_init(uint32_t baud); 7 | void uart_putchar(uint8_t c); 8 | uint8_t uart_getchar(void); 9 | uint8_t uart_available(void); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /RemoteControl/makefile: -------------------------------------------------------------------------------- 1 | # 2 | # LUFA Library 3 | # Copyright (C) Dean Camera, 2014. 4 | # 5 | # dean [at] fourwalledcubicle [dot] com 6 | # www.lufa-lib.org 7 | # 8 | # -------------------------------------- 9 | # LUFA Project Makefile. 10 | # -------------------------------------- 11 | 12 | # MCU Types: 13 | # at90usb1286 for Teensy 2.0++ 14 | # atmega16u2 for Arduino UNO R3 15 | # atmega32u4 for Arduino Micro/Teensy 2.0 16 | 17 | MCU = atmega16u2 18 | ARCH = AVR8 19 | F_CPU = 16000000 20 | F_USB = $(F_CPU) 21 | OPTIMIZATION = s 22 | TARGET = RemoteControl_$(MCU) 23 | UART_PATH = ./Source/uart 24 | SRC = ./Source/RemoteControl.c ./Config/Descriptors.c $(LUFA_SRC_USB) $(UART_PATH).c 25 | LUFA_PATH = ./LUFA/LUFA 26 | CC_FLAGS = -DUSE_LUFA_CONFIG_HEADER -IConfig/ 27 | LD_FLAGS = 28 | 29 | # Default target 30 | all: 31 | 32 | # Include LUFA build script makefiles 33 | include $(LUFA_PATH)/Build/lufa_core.mk 34 | include $(LUFA_PATH)/Build/lufa_sources.mk 35 | include $(LUFA_PATH)/Build/lufa_build.mk 36 | include $(LUFA_PATH)/Build/lufa_cppcheck.mk 37 | include $(LUFA_PATH)/Build/lufa_doxygen.mk 38 | include $(LUFA_PATH)/Build/lufa_dfu.mk 39 | include $(LUFA_PATH)/Build/lufa_hid.mk 40 | include $(LUFA_PATH)/Build/lufa_avrdude.mk 41 | include $(LUFA_PATH)/Build/lufa_atprogram.mk 42 | 43 | clean: 44 | rm $(TARGET).elf 45 | rm $(TARGET).map 46 | -------------------------------------------------------------------------------- /RemoteControl/obj/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PokemonAutomation/AutoMaxLair/39dfc88bde4a92a113af88fd10187a21227eb795/RemoteControl/obj/.gitkeep -------------------------------------------------------------------------------- /automaxlair/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /automaxlair/__init__.py: -------------------------------------------------------------------------------- 1 | """Init file for AutoMaxLair 2 | Establishes logger and exposes functions to the `automaxlair` 3 | name space. 4 | """ 5 | 6 | # Original code by denvoros 7 | 8 | import logging as __logging 9 | 10 | from .da_controller import DAController # noqa: F401 11 | 12 | # Define the logger for the package so that it can be imported and used 13 | # elsewhere. 14 | logger = __logging.getLogger("automaxlair") 15 | -------------------------------------------------------------------------------- /automaxlair/ball.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | 3 | 4 | class Ball(): 5 | """Represents a ball. Contains one id and names in multiple languages.""" 6 | 7 | def __init__( 8 | self, 9 | name_id: str, 10 | names: Dict[str, str] 11 | ) -> None: 12 | self.name_id = name_id 13 | self.names = names 14 | 15 | def __str__(self) -> str: 16 | return self.name_id 17 | -------------------------------------------------------------------------------- /automaxlair/field.py: -------------------------------------------------------------------------------- 1 | class Field(object): 2 | def __init__(self) -> None: 3 | self.weather = "clear" 4 | self.terrain = "clear" 5 | 6 | def is_weather_clear(self) -> bool: 7 | return self.weather == "clear" 8 | 9 | def is_weather_sunlight(self) -> bool: 10 | return self.weather == "sunlight" 11 | 12 | def is_weather_rain(self) -> bool: 13 | return self.weather == "rain" 14 | 15 | def is_weather_sandstorm(self) -> bool: 16 | return self.weather == "sandstorm" 17 | 18 | def is_weather_hail(self) -> bool: 19 | return self.weather == "hail" 20 | 21 | def set_weather_clear(self) -> bool: 22 | self.weather = "clear" 23 | 24 | def set_weather_sunlight(self) -> bool: 25 | self.weather = "sunlight" 26 | 27 | def set_weather_rain(self) -> bool: 28 | self.weather = "rain" 29 | 30 | def set_weather_sandstorm(self) -> bool: 31 | self.weather = "sandstorm" 32 | 33 | def set_weather_hail(self) -> bool: 34 | self.weather = "hail" 35 | 36 | def is_terrain_clear(self) -> bool: 37 | return self.terrain == "clear" 38 | 39 | def is_terrain_electric(self) -> bool: 40 | return self.terrain == "electric" 41 | 42 | def is_terrain_grassy(self) -> bool: 43 | return self.terrain == "grassy" 44 | 45 | def is_terrain_misty(self) -> bool: 46 | return self.terrain == "misty" 47 | 48 | def is_terrain_psychic(self) -> bool: 49 | return self.terrain == "psychic" 50 | 51 | def set_terrain_clear(self) -> bool: 52 | self.terrain = "clear" 53 | 54 | def set_terrain_electric(self) -> bool: 55 | self.terrain = "electric" 56 | 57 | def set_terrain_grassy(self) -> bool: 58 | self.terrain = "grassy" 59 | 60 | def set_terrain_misty(self) -> bool: 61 | self.terrain = "misty" 62 | 63 | def set_terrain_psychic(self) -> bool: 64 | self.terrain = "psychic" 65 | -------------------------------------------------------------------------------- /automaxlair/path_tree.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pickle 3 | import sys 4 | import os 5 | 6 | from typing import List, Tuple 7 | # We need to import some things from the parent directory. 8 | base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 9 | sys.path.insert(1, base_dir) 10 | sys.path.insert(1, os.path.join(base_dir, 'automaxlair')) 11 | 12 | # Needs to be lower than path insert. 13 | from automaxlair import matchup_scoring # noqa: E402 14 | 15 | 16 | TYPE_LIST = [ 17 | 'normal', 'fire', 'water', 'electric', 'grass', 'ice', 'fighting', 18 | 'poison', 'ground', 'flying', 'psychic', 'bug', 'rock', 'ghost', 19 | 'dragon', 'dark', 'steel', 'fairy'] 20 | 21 | 22 | class PathTree(): 23 | """A basic tree object for determining a better path to follow 24 | 25 | Upon initialization, it takes all of the available rental pokemon and boss 26 | pokemon and builds up a tree that gives a score for each possible path 27 | of three types (for example, 'fire', 'grass', 'water') for each boss 28 | Pokemon available. 29 | 30 | Do note that this tree does *not* take into account potential party 31 | members! This is merely a basic tree that provides simple pre-calculated 32 | scores to help determine a path that's potentially more likely than always 33 | choosing the left-most path. 34 | 35 | There is certainly room here to improve upon this algorithm by including 36 | the current team, the chance that someone will take the pokemon, and more. 37 | But for now, a simple tree structure that stores scores for each is better 38 | than nothing. 39 | 40 | As for using this tree, it should be trained automatically with a 41 | distributed pickle file for loading in later. Once the pickle is loaded (or 42 | the tree trained) it is just a matter of calling the `score_path` method 43 | with a list that contains your path. Make sure the path is formatted in the 44 | following way: ['boss', 'type1', 'type2', 'type3'] with strings identical 45 | to those found in TYPE_LIST (shouldn't be a problem for the rest of the 46 | script). 47 | """ 48 | 49 | def __init__( 50 | self, 51 | load_tree_path=None, 52 | rental_pokemon=None, 53 | boss_pokemon=None 54 | ) -> None: 55 | 56 | # TREE DEPTH OVERRIDE 57 | self.tree_depth = 1 58 | 59 | # find all of the pokemon by type, both primary and secondary 60 | if rental_pokemon is not None: 61 | self.rental_by_type = {} 62 | for type_name in TYPE_LIST: 63 | pokemon_names_by_type = [] 64 | logging.info(f"Now finding Pokemon by {type_name} type!") 65 | for pokemon_name, pokemon in rental_pokemon.items(): 66 | if type_name in pokemon.type_ids: 67 | pokemon_names_by_type.append(pokemon_name) 68 | self.rental_by_type[type_name] = pokemon_names_by_type 69 | elif load_tree_path is None: 70 | raise AttributeError( 71 | "Either rental pokemon or load tree needs to be added!") 72 | 73 | if load_tree_path is None: 74 | self._build_tree(rental_pokemon, boss_pokemon) 75 | else: 76 | self.base_node = pickle.load(load_tree_path) 77 | 78 | def score_path(self, legendary: str, path: list) -> float: 79 | """Return the pre-calculated score for a particular path 80 | 81 | This method tells the stored tree to traverse its paths for 82 | the current legendary, the first type in the path, the second 83 | type in the path, and then the third type in the path. 84 | 85 | The results can then be combined 86 | 87 | boss: 'articuno' 88 | path: ['type1', 'type2', 'type3'] 89 | """ 90 | 91 | if self.tree_depth == 1: 92 | legendary_tree_node = self.base_node.get_node_for_key(legendary) 93 | 94 | # then build up the scores and scale them based on position 95 | outscore = 0.0 96 | for ii, type_name in enumerate(path): 97 | base_score = legendary_tree_node.get_node_for_key(type_name) 98 | # the formula for now helps weight the order you find 99 | # the type in, so deeper paths might be more useful 100 | # since you get a PP restore *and* type advantage 101 | outscore += (1.0 + (ii * 0.1)) * base_score 102 | 103 | return outscore 104 | 105 | else: 106 | return self.base_node.traverse_node([legendary] + path, 0) 107 | 108 | def get_best_path( 109 | self, legendary: str, path_list: List[List[str]] 110 | ) -> Tuple[float, List[str], List[float]]: 111 | """Choose the best path out of a list of paths.""" 112 | path_scores = [] 113 | for path in path_list: 114 | path_scores.append(self.score_path(legendary, path)) 115 | best_index = path_scores.index(max(path_scores)) 116 | return best_index, path_list[best_index], path_scores 117 | 118 | def _build_tree(self, rental_pokemon, boss_pokemon): 119 | self.base_node = TreeNode(legendary=True) 120 | 121 | for ii, (legendary_name, legendary) in enumerate(boss_pokemon.items()): 122 | print( 123 | f"{ii+1:02d}/{len(boss_pokemon)} : Calculating for {legendary_name}") 124 | self.base_node.add_node(legendary_name, self._build_node_for_types( 125 | current_score=0.0, legendary=legendary, rental_pokemon=rental_pokemon, path_num=0 126 | )) 127 | 128 | def _build_node_for_types(self, current_score, legendary, rental_pokemon, path_num=0): 129 | 130 | current_node = TreeNode(legendary=False) 131 | 132 | for type_name in TYPE_LIST: 133 | # calculate the score 134 | # if path_num == 2: 135 | # print(f"{type_name} ", end="" if type_name != 'fairy' else "\n") 136 | # else: 137 | # print(f"{' '*path_num} path {path_num+1} : {type_name}", 138 | # end="\n" if path_num < 1 else "\n ") 139 | 140 | possible_pokemon = [] 141 | for pokemon_name in self.rental_by_type[type_name]: 142 | possible_pokemon.append(rental_pokemon[pokemon_name]) 143 | 144 | type_score = self.calculate_score(possible_pokemon, legendary) 145 | # there are only a total of three paths, so we only add nodes to the tree 146 | # if we're less than 2 (ex. 0 and 1 for first two paths) 147 | # if path_num < 2: 148 | # # then pass through the tree to the next node through a recursive process to 149 | # # build up the tree 150 | # current_node.add_node(type_name, self._build_node_for_types( 151 | # current_score=current_score+type_score, legendary=legendary, rental_pokemon=rental_pokemon, 152 | # path_num=path_num+1 153 | # )) 154 | # else: 155 | # current_node.add_node(type_name, current_score + type_score) 156 | 157 | # FOR NOW, NO RECURSION - TODO: modify algorithm for this to make sense 158 | current_node.add_node(type_name, type_score) 159 | 160 | return current_node 161 | 162 | def calculate_score(self, possible_pokemon, legendary): 163 | # just pass things through some kind of algorithm that determines an average score 164 | # *just* based on type 165 | 166 | the_score = 0.0 167 | 168 | for pokemon in possible_pokemon: 169 | the_score += matchup_scoring.evaluate_matchup(pokemon, legendary) 170 | 171 | the_score /= len(possible_pokemon) 172 | 173 | return the_score 174 | 175 | 176 | class TreeNode: 177 | def __init__(self, legendary=False): 178 | if legendary: 179 | self.hash_table = {} 180 | else: 181 | self.hash_table = {key: None for key in TYPE_LIST} 182 | 183 | def add_node(self, key, value): 184 | self.hash_table[key] = value 185 | 186 | def traverse_node(self, list_of_decisions, current_idx=0): 187 | 188 | if type(self.hash_table[list_of_decisions[current_idx]]) == TreeNode: 189 | return self.hash_table[list_of_decisions[current_idx]].traverse_node(list_of_decisions, current_idx + 1) 190 | else: 191 | return self.hash_table[list_of_decisions[current_idx]] 192 | 193 | def get_node_for_key(self, key): 194 | # no matter what, this only returns what is assigned to the hash table 195 | # for that particular key 196 | return self.hash_table[key] 197 | -------------------------------------------------------------------------------- /automaxlair/pokemon_classes.py: -------------------------------------------------------------------------------- 1 | # Pokemon 2 | # Eric Donders 3 | # 2020-11-27 4 | import math 5 | import copy 6 | from typing import Dict, List, Tuple 7 | 8 | 9 | class Move(): 10 | """Representation of a Pokemon Move, including name, type, category, 11 | base power, accuracy, PP, effect, probability effect, whether the move hits 12 | multiple opponents, and a correction factor (applicable for example to 13 | multi-hit moves). 14 | """ 15 | 16 | def __init__( 17 | self, 18 | id_num: int, 19 | name_id: str, 20 | names: Dict[str, str], 21 | type_id: str, 22 | category: str, 23 | base_power: float, 24 | accuracy: float, 25 | PP: int, 26 | effect: str, 27 | probability: float, 28 | is_spread: bool = False, 29 | correction_factor: float = 1 30 | ) -> None: 31 | self.id_num = id_num 32 | self.name_id = name_id 33 | self.names = names 34 | self.type_id = type_id 35 | self.category = category 36 | self.base_power = base_power 37 | self.accuracy = accuracy 38 | self.PP = PP 39 | self.effect = effect 40 | self.probability = probability 41 | self.is_spread = is_spread 42 | self.correction_factor = correction_factor 43 | 44 | self.power = base_power * correction_factor 45 | 46 | def __str__(self): 47 | return self.name_id 48 | 49 | def __copy__(self): 50 | return type(self)( 51 | self.id_num, self.name_id, self.names, self.type_id, self.category, 52 | self.base_power, self.accuracy, self.PP, self.effect, 53 | self.probability, self.is_spread, self.correction_factor 54 | ) 55 | 56 | def print_verbose(self): 57 | """Print a detailed summary of the Move.""" 58 | print(f'ID number: {self.id_num}') 59 | print(f'Name identifier: {self.name_id}') 60 | print('Translations') 61 | for language, name in self.names.items(): 62 | print(f'\t{language}: {name}') 63 | print(f'Type identifier: {self.type_id}') 64 | print(f'Category: {self.category}') 65 | print(f'BP: {self.base_power}') 66 | print(f'Accuracy: {int(self.accuracy*100)}') 67 | print(f'PP: {self.PP}') 68 | print(f'Effect: {self.effect}') 69 | print(f'Probability: {self.probability}') 70 | print(f'Spread move: {self.is_spread}') 71 | print(f'Correction factor: {self.correction_factor}') 72 | print(f'Adjusted power: {self.power}') 73 | 74 | def __repr__(self) -> str: 75 | return f"<'{self.name_id}' Move object>" 76 | 77 | 78 | class Pokemon(): 79 | """Representation of a Pokemon including its name, ability, types, stats, 80 | moves, level, IVs, EVs, and nature. 81 | """ 82 | 83 | def __init__( 84 | self, 85 | id_num: int, 86 | name_id: str, 87 | names: Dict[str, str], 88 | ability_name_id: str, 89 | abilities: Dict[str, str], 90 | type_ids: List[str], 91 | types: List[Dict[str, str]], 92 | base_stats: Tuple[int, int, int, int, int, int], 93 | moves: Tuple[Move], 94 | max_moves: Tuple[Move], 95 | level: int = 100, 96 | IVs: Tuple[int, int, int, int, int, int] = (15, 15, 15, 15, 15, 15), 97 | EVs: Tuple[int, int, int, int, int, int] = (0, 0, 0, 0, 0, 0), 98 | nature: Tuple[int, int, int, int, int, int] = (1, 1, 1, 1, 1, 1) 99 | ): 100 | self.id_num = id_num 101 | self.name_id = name_id 102 | self.names = names 103 | self.ability_name_id = ability_name_id 104 | self.abilities = abilities 105 | self.type_ids = type_ids 106 | self.types = types 107 | self.base_stats = base_stats 108 | self.moves = moves 109 | self.max_moves = max_moves 110 | self.level = level 111 | self.ivs = IVs 112 | self.evs = EVs 113 | self.nature = nature 114 | 115 | self.PP = [] 116 | for move in moves: 117 | self.PP.append(move.PP) 118 | 119 | self.restore() 120 | self.reset_stats() 121 | 122 | def __copy__(self): 123 | copied_pokemon = type(self)( 124 | self. id_num, self.name_id, self.names, self.ability_name_id, 125 | self.abilities, self.type_ids, self.types, self.base_stats, 126 | self.moves, self.max_moves, self.level, self.ivs, self.evs, 127 | self.nature 128 | ) 129 | copied_pokemon.PP = copy.deepcopy(self.PP) 130 | copied_pokemon.HP = copy.deepcopy(self.HP) 131 | copied_pokemon.status = self.status 132 | copied_pokemon.stat_modifiers = self.stat_modifiers 133 | copied_pokemon.dynamax = self.dynamax 134 | return copied_pokemon 135 | 136 | def __repr__(self) -> str: 137 | return f"<'{self.name_id}' Pokemon Object>" 138 | 139 | def __str__(self): 140 | return self.name_id 141 | 142 | def print_verbose(self): 143 | """Print a detailed summary of the Pokemon.""" 144 | print(f'ID number: {self.id_num}') 145 | print(f'Name identifier: {self.name_id}') 146 | print('Translations') 147 | for language, name in self.names.items(): 148 | print(f'\t{language}: {name}') 149 | print(f'Ability identifier: {self.ability_name_id}') 150 | print('Translations') 151 | for language, name in self.abilities.items(): 152 | print(f'\t{language}: {name}') 153 | for i in range(len(self.type_ids)): 154 | print(f'Type {i+1}: {self.type_ids[i]}') 155 | for language, name in self.types[i].items(): 156 | print(f'\t{language}: {name}') 157 | print('Base stats:') 158 | for stat in self.base_stats: 159 | print(f'\t{stat}') 160 | for move in self.moves: 161 | move.print_verbose() 162 | for max_move in self.max_moves: 163 | max_move.print_verbose() 164 | print(f'Level: {self.level}') 165 | print('PP:') 166 | for value in self.PP: 167 | print(f'\t{value}') 168 | 169 | def restore(self): 170 | """Restore HP, PP, and status effects.""" 171 | self.HP = 1 172 | for i in range(len(self.PP)): 173 | self.PP[i] = self.moves[i].PP 174 | self.status = None 175 | 176 | def reset_stats(self): 177 | """Reset stat changes.""" 178 | self.stat_modifiers = [None, 0, 0, 0, 0, 0] 179 | self.recalculate_stats() 180 | self.dynamax = False 181 | 182 | def adjust_stats(self, modification): 183 | """Apply stat modifications.""" 184 | for i in range(1, 6): 185 | self.stat_modifiers[i] += modification[i] 186 | self.recalculate_stats() 187 | 188 | def recalculate_stats(self): 189 | """(Re)calculate stats of the Pokemon. Called on instantiation and 190 | after any stat modifications are applied. 191 | """ 192 | 193 | self.stats = [self.HP * (math.floor(( 194 | 2 * self.base_stats[0] + self.ivs[0] + math.floor(self.evs[0] / 4) 195 | ) * self.level / 100) + self.level + 10)] 196 | for i in range(1, 6): 197 | self.stats.append(math.floor((math.floor(( 198 | 2 * self.base_stats[i] + self.ivs[i] 199 | + math.floor(self.evs[i] / 4) 200 | ) * self.level / 100) + 5) * self.nature[i])) 201 | if self.stat_modifiers[i] >= 0: 202 | if self.stat_modifiers[i] > 6: 203 | self.stat_modifiers[i] = 6 204 | self.stats[i] *= (2 + self.stat_modifiers[i]) / 2 205 | elif self.stat_modifiers[i] < 0: 206 | if self.stat_modifiers[i] < -6: 207 | self.stat_modifiers[i] = -6 208 | self.stats[i] *= 2 / (2 + self.stat_modifiers[i]) 209 | 210 | def get_name(self, language: str) -> str: 211 | """Return the name of the Pokemon in a given language. 212 | Returns the name ID if no value exists for the supplied language. 213 | """ 214 | 215 | return self.names.get(language, self.name_id) 216 | 217 | def get_ability(self, language: str) -> str: 218 | """Return the ability of the Pokemon in a given language. 219 | Returns the ability ID if no value exists for the supplied language. 220 | """ 221 | 222 | return self.abilities.get(language, self.ability_name_id) 223 | 224 | def get_types(self, language: str) -> List[str]: 225 | """Return the types of the Pokemon in a given language. 226 | Returns the type IDs if no value exists for the supplied language. 227 | """ 228 | 229 | types = [] 230 | for i in range(len(self.types)): 231 | types.append(self.types[i].get(language, self.type_ids[i])) 232 | return types 233 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | ## v0.2-beta 3 | * Initial stable public release. 4 | * Greatly improved Pokemon and move selection from development versions. 5 | ## v0.3 6 | * Implemented optional ball selection for both rental Pokemon and boss Pokemon. 7 | * Fixed a bug where Wide Guard users would Dynamax and use Max Guard instead. 8 | * Wide Guard is now weighted fairly well. 9 | * Refactoring. 10 | ## v0.4 11 | * Added alternate modes that reset the game to preserve rare balls and/or good seeds. 12 | * This mode was added with help of user fawress on the Pokemon Automation Discord. 13 | * The “ball saver” mode force quits and restarts the game if the legendary is caught but is not shiny. 14 | * The “strong boss” mode force quits and restarts the game if the legendary is caught and none of the Pokemon are shiny 15 | * Both modes can “lock on” to a good path, since the path is conserved if the game is reset before the end of the run. 16 | * Be mindful that repeated resets will incur a Dynite Ore cost, in contrast to the regular mode that accumulates Dynite Ore. 17 | * The “strong boss” mode is recommended for difficult bosses where even a good seed can sometimes lose. 18 | * This mode will increase the win rate against any boss, but repeated wins will quickly deplete all your Dynite Ore. 19 | * Fixed move scoring issues, including incorrect calculations that undervalued Max Steelspike derived from Steel Roller and overvalued Triple Axel. 20 | * Fixed an issue where Max Moves were overvalued by a factor of 100. 21 | * Added a counter for shiny Pokemon caught. 22 | * Adjusted detection rectangles to properly detect text when the Switch screen size is 100% (rectangles were previously optimized for a non-standard size of 96%). 23 | * Mitigated an issue where the bot would occasionally get stuck after dropping a button press in the battle routine. 24 | * Fixed an issue that caused Pokemon caught along the run to be scored incorrectly when determining whether to swap rental Pokemon or not. 25 | ## v0.5 26 | * Redesigned the way that the different sequences are processed to make the program run more smoothly and consistently. 27 | * The visual display and button presses are now handled by different threads, which reduces FPS drops when text is being read and allows the button sequences to be handled in a linear fashion (instead of relying on a timer and substages to dictate the next step). 28 | * Updated the “ball saver” and “strong boss” modes to gracefully handle running out of Dynite Ore. 29 | * “Ball saver” mode quits when there is insufficient ore to continue resetting. 30 | * “Strong boss” mode finishes the run without resetting if there is insufficient ore for another reset. 31 | * Miscellaneous bug fixes and improvements 32 | ## v0.6 33 | * Added support for multiple languages within the Pokemon data files. 34 | * All information about Pokemon is now fetched from PokeAPI. 35 | * Supported and verified languages include English, French, Spanish, Korean, and German. 36 | * Italian and Mandarin may also work but have not been tested. 37 | * Changed how a loss (losing all 4 lives) is detected, increasing consistency. 38 | * Detect move names, improving the accuracy of Pokemon identification. 39 | * Updated Ball Saver mode to skip catching the boss if it can't afford to reset the game, allowing it to be run indefinitely. 40 | * Added "Keep Path" and "Find Path" modes, which are useful against very strong bosses (e.g., Zygarde). 41 | * Add the ability to take a pokemon from the scientist if your current pokemon is worse than average. 42 | * Add the ability to hunt for specific stats legendary. 43 | * Add a way to send discord message with a picture showing your latest catch. 44 | ## v0.7 45 | * Paths through the den are now read, scored, and selected. 46 | * Refactored code to improve readability and facilitate extension to tasks other than Dynamax Adventures in the future. 47 | * Added support for variable hold time for button presses. The computer now sends two bytes per command, with the second byte specifying the hold time. 48 | * Added dependencies for the microcontroller code so it can be altered and recompiled without any external tools (besides WinAVR). 49 | * Precalculated data files are now stored in human-readable JSON format. 50 | * Tweaked how Pokemon are scored. 51 | * Stats checking now takes nature into account. 52 | * Changed how certain events are detected, improving efficiency and reducing reliance on Tesseract. 53 | * Improved path selection. 54 | * Improved config file. 55 | * Improved stats checking. 56 | * More improvements on the way! 57 | ## v0.8 58 | * Weather and terrain are now taken into consideration. 59 | * The teammates are now detected. 60 | * Improve the config file validation by asserting at the startup. 61 | * Add a UI to be able to easily edit the config file. 62 | * Detect ball type using sprites instead of text 63 | * Automatically detect ball numbers instead of relying on users to input them in the config file 64 | * Add compatibility for the PABotBase hex file in addition to the custom RemoteControl hex 65 | * Detect weather and terrain 66 | ## v1.0 67 | * Link PABotBase hex files 68 | * Use the PABotBase hex by default 69 | -------------------------------------------------------------------------------- /data/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /data/backpacker-items.txt: -------------------------------------------------------------------------------- 1 | blunder-policy 2 | bright-powder 3 | electric-seed 4 | eviolite 5 | expert-belt 6 | focus-band 7 | focus-sash 8 | grassy-seed 9 | leftovers 10 | leppa-berry 11 | life-orb 12 | lum-berry 13 | misty-seed 14 | muscle-band 15 | normal-gem 16 | psychic-seed 17 | quick-claw 18 | shell-bell 19 | sitrus-berry 20 | weakness-policy 21 | white-herb 22 | wide-lens 23 | wise-glasses 24 | -------------------------------------------------------------------------------- /data/ball_sprites.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PokemonAutomation/AutoMaxLair/39dfc88bde4a92a113af88fd10187a21227eb795/data/ball_sprites.pickle -------------------------------------------------------------------------------- /data/boss_colors.json: -------------------------------------------------------------------------------- 1 | { 2 | "articuno": [ 3 | 156, 4 | 186, 5 | 216 6 | ], 7 | "zapdos": [ 8 | 255, 9 | 214, 10 | 0 11 | ], 12 | "moltres": [ 13 | 230, 14 | 126, 15 | 145 16 | ], 17 | "mewtwo": [ 18 | 121, 19 | 193, 20 | 99 21 | ], 22 | "raikou": [ 23 | 203, 24 | 132, 25 | 46 26 | ], 27 | "entei": [ 28 | 159, 29 | 110, 30 | 73 31 | ], 32 | "suicune": [ 33 | 53, 34 | 93, 35 | 237 36 | ], 37 | "lugia": [ 38 | 203, 39 | 106, 40 | 147 41 | ], 42 | "ho-oh": [ 43 | 235, 44 | 177, 45 | 46 46 | ], 47 | "latias": [ 48 | 227, 49 | 163, 50 | 0 51 | ], 52 | "latios": [ 53 | 59, 54 | 160, 55 | 137 56 | ], 57 | "kyogre": [ 58 | 235, 59 | 96, 60 | 244 61 | ], 62 | "groudon": [ 63 | 237, 64 | 244, 65 | 59 66 | ], 67 | "rayquaza": [ 68 | 100, 69 | 109, 70 | 100 71 | ], 72 | "uxie": [ 73 | 255, 74 | 247, 75 | 166 76 | ], 77 | "mesprit": [ 78 | 245, 79 | 108, 80 | 128 81 | ], 82 | "azelf": [ 83 | 105, 84 | 148, 85 | 237 86 | ], 87 | "dialga": [ 88 | 177, 89 | 255, 90 | 177 91 | ], 92 | "palkia": [ 93 | 244, 94 | 216, 95 | 227 96 | ], 97 | "heatran": [ 98 | 157, 99 | 38, 100 | 0 101 | ], 102 | "giratina-altered": [ 103 | 219, 104 | 195, 105 | 159 106 | ], 107 | "cresselia": [ 108 | 182, 109 | 255, 110 | 255 111 | ], 112 | "tornadus-incarnate": [ 113 | 121, 114 | 159, 115 | 83 116 | ], 117 | "thundurus-incarnate": [ 118 | 159, 119 | 174, 120 | 216 121 | ], 122 | "reshiram": [ 123 | 199, 124 | 210, 125 | 220 126 | ], 127 | "zekrom": [ 128 | 28, 129 | 34, 130 | 28 131 | ], 132 | "landorus-incarnate": [ 133 | 101, 134 | 61, 135 | 42 136 | ], 137 | "kyurem": [ 138 | 68, 139 | 91, 140 | 102 141 | ], 142 | "xerneas": [ 143 | 34, 144 | 177, 145 | 227 146 | ], 147 | "yveltal": [ 148 | 170, 149 | 27, 150 | 61 151 | ], 152 | "zygarde-50": [ 153 | 41, 154 | 186, 155 | 149 156 | ], 157 | "tapu-koko": [ 158 | 244, 159 | 160, 160 | 107 161 | ], 162 | "tapu-lele": [ 163 | 255, 164 | 172, 165 | 176 166 | ], 167 | "tapu-bulu": [ 168 | 232, 169 | 222, 170 | 112 171 | ], 172 | "tapu-fini": [ 173 | 125, 174 | 153, 175 | 182 176 | ], 177 | "solgaleo": [ 178 | 227, 179 | 53, 180 | 61 181 | ], 182 | "lunala": [ 183 | 187, 184 | 12, 185 | 51 186 | ], 187 | "nihilego": [ 188 | 170, 189 | 147, 190 | 125 191 | ], 192 | "buzzwole": [ 193 | 72, 194 | 209, 195 | 80 196 | ], 197 | "pheromosa": [ 198 | 214, 199 | 211, 200 | 209 201 | ], 202 | "xurkitree": [ 203 | 129, 204 | 173, 205 | 200 206 | ], 207 | "celesteela": [ 208 | 235, 209 | 243, 210 | 241 211 | ], 212 | "kartana": [ 213 | 237, 214 | 237, 215 | 237 216 | ], 217 | "guzzlord": [ 218 | 229, 219 | 229, 220 | 223 221 | ], 222 | "necrozma": [ 223 | 0, 224 | 71, 225 | 159 226 | ], 227 | "stakataka": [ 228 | 125, 229 | 117, 230 | 28 231 | ], 232 | "blacephalon": [ 233 | 0, 234 | 22, 235 | 136 236 | ] 237 | } -------------------------------------------------------------------------------- /data/boss_pokemon.txt: -------------------------------------------------------------------------------- 1 | articuno,pressure,ice-beam,freeze-dry,hurricane,mist,blizzard 2 | zapdos,pressure,thunder,drill-peck,brave-bird,agility,discharge 3 | moltres,pressure,heat-wave,wing-attack,leer,fire-spin,heat-wave 4 | mewtwo,pressure,psychic,disable,recover,blizzard,psystrike 5 | raikou,pressure,thunderbolt,howl,extreme-speed,weather-ball,spark 6 | entei,pressure,flamethrower,scary-face,extreme-speed,crunch,flame-wheel 7 | suicune,pressure,liquidation,extrasensory,extreme-speed,calm-mind,water-pulse 8 | lugia,pressure,dragon-pulse,extrasensory,whirlpool,ancient-power,aeroblast 9 | ho-oh,pressure,flare-blitz,extrasensory,sunny-day,ancient-power,sacred-fire 10 | latias,levitate,reflect-type,dragon-breath,zen-headbutt,surf,mist-ball 11 | latios,levitate,dragon-dance,dragon-pulse,zen-headbutt,aura-sphere,luster-purge 12 | kyogre,drizzle,surf,body-slam,aqua-ring,thunder,origin-pulse 13 | groudon,drought,earthquake,scary-face,lava-plume,hammer-arm,precipice-blades 14 | rayquaza,air-lock,dragon-ascent,brutal-swing,extreme-speed,twister,dragon-pulse 15 | uxie,levitate,psychic,future-sight,magic-room,shadow-ball,swift 16 | mesprit,levitate,psychic,charm,draining-kiss,tri-attack,swift 17 | azelf,levitate,psychic,dazzling-gleam,nasty-plot,facade,swift 18 | dialga,pressure,slash,ancient-power,flash-cannon,dragon-claw,iron-tail 19 | palkia,pressure,slash,surf,ancient-power,dragon-claw,aqua-tail 20 | heatran,flash-fire,metal-sound,lava-plume,crunch,iron-head,heat-wave 21 | giratina-altered,pressure,dragon-claw,scary-face,shadow-ball,ancient-power,shadow-claw 22 | cresselia,levitate,icy-wind,moonblast,psycho-cut,psyshock,psychic 23 | tornadus-incarnate,prankster,hurricane,agility,icy-wind,heat-wave,air-cutter 24 | thundurus-incarnate,prankster,thunder,rain-dance,weather-ball,sludge-wave,discharge 25 | reshiram,turboblaze,noble-roar,extrasensory,fusion-flare,dragon-pulse,blue-flare 26 | zekrom,teravolt,noble-roar,slash,fusion-bolt,dragon-claw,bolt-strike 27 | landorus-incarnate,sand-force,sand-tomb,rock-slide,bulldoze,focus-blast,earthquake 28 | kyurem,pressure,ice-beam,hyper-voice,shadow-ball,scary-face,glaciate 29 | xerneas,fairy-aura,ingrain,dazzling-gleam,moonblast,horn-leech,moonblast 30 | yveltal,dark-aura,taunt,oblivion-wing,dragon-rush,sucker-punch,snarl 31 | zygarde-50,power-construct,thousand-arrows,lands-wrath,dragon-pulse,bind,lands-wrath 32 | tapu-koko,electric-surge,thunderbolt,quick-attack,brave-bird,taunt,discharge 33 | tapu-lele,psychic-surge,psychic,play-rough,magic-room,charm,psyshock 34 | tapu-bulu,grassy-surge,superpower,megahorn,wood-hammer,scary-face,horn-leech 35 | tapu-fini,misty-surge,whirlpool,water-pulse,brine,moonblast,surf 36 | solgaleo,full-metal-body,zen-headbutt,fire-spin,iron-tail,noble-roar,sunsteel-strike 37 | lunala,shadow-shield,shadow-ball,moonblast,magic-coat,swift,moongeist-beam 38 | nihilego,beast-boost,wonder-room,sludge-wave,brutal-swing,acid-spray,power-gem 39 | buzzwole,beast-boost,power-up-punch,taunt,leech-life,dynamic-punch,lunge 40 | pheromosa,beast-boost,high-jump-kick,swift,throat-chop,lunge,bug-buzz 41 | xurkitree,beast-boost,power-whip,discharge,eerie-impulse,brutal-swing,thunderbolt 42 | celesteela,beast-boost,leech-seed,smack-down,gyro-ball,earthquake,flash-cannon 43 | kartana,beast-boost,vacuum-wave,air-cutter,leaf-blade,swords-dance,leaf-blade 44 | guzzlord,beast-boost,dragon-rush,stomping-tantrum,brutal-swing,mega-punch,crunch 45 | necrozma,prism-armor,psycho-cut,charge-beam,power-gem,autotomize,photon-geyser 46 | stakataka,beast-boost,rock-slide,double-edge,brutal-swing,autotomize,iron-head 47 | blacephalon,beast-boost,shadow-claw,taunt,fire-blast,zen-headbutt,mystical-fire 48 | -------------------------------------------------------------------------------- /data/misc_icons/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /data/misc_icons/cheer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PokemonAutomation/AutoMaxLair/39dfc88bde4a92a113af88fd10187a21227eb795/data/misc_icons/cheer.png -------------------------------------------------------------------------------- /data/misc_icons/fight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PokemonAutomation/AutoMaxLair/39dfc88bde4a92a113af88fd10187a21227eb795/data/misc_icons/fight.png -------------------------------------------------------------------------------- /data/pokemon_sprites.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PokemonAutomation/AutoMaxLair/39dfc88bde4a92a113af88fd10187a21227eb795/data/pokemon_sprites.pickle -------------------------------------------------------------------------------- /data/rental_pokemon_scores.json: -------------------------------------------------------------------------------- 1 | { 2 | "ivysaur": 3.5786186100480686, 3 | "charmeleon": 3.322578319210127, 4 | "wartortle": 3.2599782629606766, 5 | "butterfree": 3.3230142005911913, 6 | "raichu": 3.457070396887134, 7 | "raichu-alola": 3.696516075818862, 8 | "sandslash": 3.907380543076768, 9 | "sandslash-alola": 3.806802358639943, 10 | "nidoqueen": 3.621024226133424, 11 | "nidoking": 4.2073616230481825, 12 | "clefairy": 2.9121409724962115, 13 | "clefable": 3.735686583541758, 14 | "jigglypuff": 2.539708446245346, 15 | "wigglytuff": 3.4671550901670196, 16 | "gloom": 3.131596451771026, 17 | "vileplume": 3.944531491390938, 18 | "dugtrio": 3.486753464389486, 19 | "dugtrio-alola": 3.9208060597641077, 20 | "persian": 2.9878809343066943, 21 | "persian-alola": 2.954942186645455, 22 | "golduck": 3.4427360413100425, 23 | "poliwrath": 3.8991751625107476, 24 | "kadabra": 3.1944542437634533, 25 | "machoke": 3.4103605614417645, 26 | "tentacruel": 3.705280588765657, 27 | "slowbro": 3.8692773118366404, 28 | "magneton": 4.222653605439104, 29 | "haunter": 3.6954817230321098, 30 | "kingler": 4.045766722401212, 31 | "exeggutor": 4.188635312382797, 32 | "marowak": 3.508036911143109, 33 | "marowak-alola": 4.1660758585156215, 34 | "hitmonlee": 4.051878883869966, 35 | "hitmonchan": 3.8054156591997708, 36 | "lickitung": 3.180244903629259, 37 | "weezing": 3.5283426697326057, 38 | "weezing-galar": 3.6817806711037253, 39 | "rhydon": 4.336089126657407, 40 | "chansey": 2.8525045969318263, 41 | "tangela": 3.049631014818207, 42 | "kangaskhan": 3.7382104974457504, 43 | "seadra": 3.2796581599576915, 44 | "seaking": 3.6562447137519096, 45 | "starmie": 3.4663535895516313, 46 | "mr-mime": 3.359553759080411, 47 | "mr-mime-galar": 3.3058989123009455, 48 | "scyther": 3.6181668014509216, 49 | "jynx": 3.535781337444526, 50 | "electabuzz": 3.4169908965789655, 51 | "magmar": 3.727174345936491, 52 | "tauros": 3.7613712659073197, 53 | "ditto": 3.446051918362511, 54 | "vaporeon": 3.586317634308359, 55 | "jolteon": 3.9478558531836763, 56 | "flareon": 3.7670036529822206, 57 | "porygon": 3.4590445366769846, 58 | "dragonair": 3.211461829078799, 59 | "noctowl": 3.1829861354851734, 60 | "lanturn": 3.8903000223933977, 61 | "togetic": 3.19284848304517, 62 | "xatu": 3.528598137360752, 63 | "bellossom": 3.6287907208136017, 64 | "azumarill": 3.459181125421998, 65 | "sudowoodo": 3.9561256570214622, 66 | "politoed": 3.5836382499019734, 67 | "quagsire": 3.24789615048619, 68 | "slowking": 3.7303117619833337, 69 | "dunsparce": 3.1858161005454626, 70 | "qwilfish": 3.695606321174776, 71 | "sneasel": 3.4464595075386337, 72 | "piloswine": 3.7134816685558403, 73 | "octillery": 3.7407187114502216, 74 | "mantine": 3.852630970998138, 75 | "skarmory": 3.779698761447987, 76 | "hitmontop": 3.655039424508941, 77 | "miltank": 3.463504303796099, 78 | "grovyle": 3.129208013624237, 79 | "sceptile": 3.9687040929021777, 80 | "combusken": 3.5932145959195796, 81 | "blaziken": 4.6194347971920795, 82 | "marshtomp": 3.616544344376196, 83 | "swampert": 3.9642865242756473, 84 | "linoone": 3.0766308572191585, 85 | "linoone-galar": 3.109104034551739, 86 | "pelipper": 3.140401821213393, 87 | "ninjask": 2.9308603157673905, 88 | "exploud": 3.5898321978807344, 89 | "lairon": 3.8204670092678237, 90 | "manectric": 3.9036069353761125, 91 | "roselia": 3.7244325509572227, 92 | "sharpedo": 3.2666464085990086, 93 | "wailmer": 3.1170446912680903, 94 | "torkoal": 3.5440877297667353, 95 | "flygon": 3.7250510066362432, 96 | "altaria": 3.388121185253864, 97 | "whiscash": 3.482156239147956, 98 | "crawdaunt": 3.8094859175786366, 99 | "claydol": 3.3190192745661853, 100 | "cradily": 3.9329236842498587, 101 | "armaldo": 4.371069358732509, 102 | "dusclops": 3.2897078706854033, 103 | "absol": 3.6918539350944957, 104 | "glalie": 3.417232381082936, 105 | "sealeo": 3.4710600236131515, 106 | "relicanth": 4.282446762800834, 107 | "metang": 3.7221458635598914, 108 | "luxray": 3.6697250430641173, 109 | "vespiquen": 3.5969675035909505, 110 | "cherrim": 3.346313205141046, 111 | "gastrodon": 3.6233211380366384, 112 | "drifblim": 3.4771958294408627, 113 | "lopunny": 3.3861909715669065, 114 | "skuntank": 3.6952026867773435, 115 | "bronzong": 3.8980858896584656, 116 | "munchlax": 3.7191526370042074, 117 | "drapion": 3.794879236992222, 118 | "abomasnow": 3.714228869042604, 119 | "froslass": 3.653249155105811, 120 | "rotom": 3.788675429772624, 121 | "stoutland": 3.647523220770024, 122 | "liepard": 3.0957087438147624, 123 | "musharna": 4.041426708477364, 124 | "unfezant": 3.478678714416895, 125 | "boldore": 3.8014238913226817, 126 | "swoobat": 3.189677403448876, 127 | "audino": 3.2736510866783965, 128 | "gurdurr": 3.76937936182945, 129 | "palpitoad": 3.329285761639143, 130 | "seismitoad": 2.9964526158040776, 131 | "scolipede": 3.802519324320781, 132 | "whimsicott": 3.586901627721715, 133 | "lilligant": 4.054213460064244, 134 | "basculin-red-striped": 3.448826446105757, 135 | "basculin-blue-striped": 3.3967205694577074, 136 | "krookodile": 4.035359009100929, 137 | "maractus": 4.010362744997375, 138 | "crustle": 4.09711873725032, 139 | "sigilyph": 3.7868078281269355, 140 | "cofagrigus": 3.7858156251264994, 141 | "garbodor": 3.6591627361637205, 142 | "cinccino": 3.136679288765303, 143 | "vanillish": 3.2081760304454434, 144 | "emolga": 3.3320960607116863, 145 | "escavalier": 4.057441438664975, 146 | "amoonguss": 3.7605134845232207, 147 | "jellicent": 3.5101820647432107, 148 | "galvantula": 3.8283732543062374, 149 | "klang": 3.52921278108987, 150 | "klinklang": 3.683636773179775, 151 | "beheeyem": 4.002611597766595, 152 | "lampent": 3.5592600852516085, 153 | "fraxure": 3.7438221726507956, 154 | "beartic": 3.576001933883359, 155 | "cryogonal": 3.825844863972948, 156 | "accelgor": 3.2596660049421033, 157 | "stunfisk": 4.069731508552442, 158 | "stunfisk-galar": 3.755825699864767, 159 | "mienshao": 3.6414283960597653, 160 | "druddigon": 3.8523695196430037, 161 | "golurk": 4.392800886849953, 162 | "bisharp": 4.05725626998198, 163 | "bouffalant": 4.190891529430881, 164 | "heatmor": 4.408967012381079, 165 | "durant": 3.984842766570923, 166 | "diggersby": 3.2789101662735174, 167 | "talonflame": 3.675933889714442, 168 | "pangoro": 4.498986329262075, 169 | "doublade": 4.023475765537569, 170 | "malamar": 3.6203793901961516, 171 | "barbaracle": 3.7971782996464274, 172 | "heliolisk": 3.7485112096145676, 173 | "tyrantrum": 4.0509826387340615, 174 | "aurorus": 3.9601779670813144, 175 | "hawlucha": 3.4450076717934497, 176 | "dedenne": 3.5942362588366814, 177 | "klefki": 3.79697599804055, 178 | "trevenant": 4.003065562838948, 179 | "gourgeist-average": 3.2378719334236643, 180 | "charjabug": 3.4479296165348323, 181 | "vikavolt": 4.293942468909208, 182 | "ribombee": 3.3745555112252736, 183 | "lycanroc-midday": 3.847059479647806, 184 | "lycanroc-midnight": 4.135622983308905, 185 | "mudsdale": 4.119929235420899, 186 | "araquanid": 3.6616360195983453, 187 | "lurantis": 3.811658729590916, 188 | "shiinotic": 3.4378895588099616, 189 | "salazzle": 3.775611679877203, 190 | "bewear": 4.15862284500402, 191 | "tsareena": 3.9805832338373466, 192 | "comfey": 3.300568114909494, 193 | "oranguru": 3.554480110200127, 194 | "passimian": 3.9313521062673087, 195 | "palossand": 4.065112549754215, 196 | "pyukumuku": 2.525322999523347, 197 | "togedemaru": 3.822420787600754, 198 | "mimikyu-disguised": 3.540294147693431, 199 | "greedent": 3.7178686157095138, 200 | "orbeetle": 3.474530063163537, 201 | "thievul": 3.3971856032127903, 202 | "eldegoss": 3.632606778469967, 203 | "dubwool": 3.528767843612659, 204 | "drednaw": 4.209169209451295, 205 | "boltund": 3.539750475386989, 206 | "carkol": 3.6207892151480308, 207 | "coalossal": 4.00350517337877, 208 | "sandaconda": 3.454603711005339, 209 | "cramorant": 3.3235714893645656, 210 | "barraskewda": 3.1845350607060188, 211 | "toxtricity-amped": 3.757973575021802, 212 | "centiskorch": 3.8980231425381358, 213 | "grapploct": 3.660074710808615, 214 | "polteageist": 3.8998152495121845, 215 | "hatterene": 4.07411942041316, 216 | "grimmsnarl": 3.613501300701067, 217 | "obstagoon": 3.673756687109596, 218 | "perrserker": 3.6815909422917366, 219 | "alcremie": 3.9965140931662972, 220 | "falinks": 3.5217078685769203, 221 | "pincurchin": 3.6681017274558556, 222 | "frosmoth": 3.4134088536949623, 223 | "indeedee-male": 3.641167294616705, 224 | "morpeko": 3.8462990911293153, 225 | "copperajah": 4.120911673805299, 226 | "duraludon": 4.092622564626306, 227 | "drakloak": 3.26364743796227 228 | } -------------------------------------------------------------------------------- /data/type_icons.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PokemonAutomation/AutoMaxLair/39dfc88bde4a92a113af88fd10187a21227eb795/data/type_icons.pickle -------------------------------------------------------------------------------- /doc/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /doc/Figure 1 - Initial Pokemon Selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PokemonAutomation/AutoMaxLair/39dfc88bde4a92a113af88fd10187a21227eb795/doc/Figure 1 - Initial Pokemon Selection.png -------------------------------------------------------------------------------- /doc/Figure 2 - Opponent Detection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PokemonAutomation/AutoMaxLair/39dfc88bde4a92a113af88fd10187a21227eb795/doc/Figure 2 - Opponent Detection.png -------------------------------------------------------------------------------- /doc/Figure 3 - Ball Selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PokemonAutomation/AutoMaxLair/39dfc88bde4a92a113af88fd10187a21227eb795/doc/Figure 3 - Ball Selection.png -------------------------------------------------------------------------------- /doc/Figure 4 - Post-Battle Pokemon Selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PokemonAutomation/AutoMaxLair/39dfc88bde4a92a113af88fd10187a21227eb795/doc/Figure 4 - Post-Battle Pokemon Selection.png -------------------------------------------------------------------------------- /doc/Figure 5 - Non-Shiny Pokemon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PokemonAutomation/AutoMaxLair/39dfc88bde4a92a113af88fd10187a21227eb795/doc/Figure 5 - Non-Shiny Pokemon.png -------------------------------------------------------------------------------- /doc/Figure 6 - Shiny Pokemon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PokemonAutomation/AutoMaxLair/39dfc88bde4a92a113af88fd10187a21227eb795/doc/Figure 6 - Shiny Pokemon.png -------------------------------------------------------------------------------- /doc/RemoteControl Documentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PokemonAutomation/AutoMaxLair/39dfc88bde4a92a113af88fd10187a21227eb795/doc/RemoteControl Documentation.pdf -------------------------------------------------------------------------------- /install-requirements.bat: -------------------------------------------------------------------------------- 1 | pip install -r requirements.txt 2 | pause 3 | -------------------------------------------------------------------------------- /logs/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | crccheck>=1.0,<2 2 | jsonpickle>=2.0.0,<3 3 | discord.py>=1.6.0,<2 4 | pytesseract>=0.3.6,<1 5 | pokebase>=1.3.0,<2 6 | opencv_python>=4.4.0.44,<5 7 | pyenchant>=3.2.0,<4 8 | pyserial>=3.5,<4 9 | toml>=0.10.2,<1 10 | -------------------------------------------------------------------------------- /scripts/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /scripts/OpenCV_demo_staticimage.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import cv2 as cv 3 | filename = input('Filename:') 4 | max_value = 255 5 | max_value_H = 360 // 2 6 | low_H = 0 7 | low_S = 0 8 | low_V = 0 9 | high_H = max_value_H 10 | high_S = max_value 11 | high_V = max_value 12 | window_capture_name = 'Video Capture' 13 | window_detection_name = 'Object Detection' 14 | low_H_name = 'Low H' 15 | low_S_name = 'Low S' 16 | low_V_name = 'Low V' 17 | high_H_name = 'High H' 18 | high_S_name = 'High S' 19 | high_V_name = 'High V' 20 | 21 | 22 | def on_low_H_thresh_trackbar(val): 23 | global low_H 24 | global high_H 25 | low_H = val 26 | low_H = min(high_H - 1, low_H) 27 | cv.setTrackbarPos(low_H_name, window_detection_name, low_H) 28 | 29 | 30 | def on_high_H_thresh_trackbar(val): 31 | global low_H 32 | global high_H 33 | high_H = val 34 | high_H = max(high_H, low_H + 1) 35 | cv.setTrackbarPos(high_H_name, window_detection_name, high_H) 36 | 37 | 38 | def on_low_S_thresh_trackbar(val): 39 | global low_S 40 | global high_S 41 | low_S = val 42 | low_S = min(high_S - 1, low_S) 43 | cv.setTrackbarPos(low_S_name, window_detection_name, low_S) 44 | 45 | 46 | def on_high_S_thresh_trackbar(val): 47 | global low_S 48 | global high_S 49 | high_S = val 50 | high_S = max(high_S, low_S + 1) 51 | cv.setTrackbarPos(high_S_name, window_detection_name, high_S) 52 | 53 | 54 | def on_low_V_thresh_trackbar(val): 55 | global low_V 56 | global high_V 57 | low_V = val 58 | low_V = min(high_V - 1, low_V) 59 | cv.setTrackbarPos(low_V_name, window_detection_name, low_V) 60 | 61 | 62 | def on_high_V_thresh_trackbar(val): 63 | global low_V 64 | global high_V 65 | high_V = val 66 | high_V = max(high_V, low_V + 1) 67 | cv.setTrackbarPos(high_V_name, window_detection_name, high_V) 68 | 69 | 70 | cap = cv.imread(filename) 71 | cv.namedWindow(window_capture_name) 72 | cv.namedWindow(window_detection_name) 73 | cv.createTrackbar( 74 | low_H_name, window_detection_name, low_H, max_value_H, 75 | on_low_H_thresh_trackbar 76 | ) 77 | cv.createTrackbar( 78 | high_H_name, window_detection_name, high_H, max_value_H, 79 | on_high_H_thresh_trackbar 80 | ) 81 | cv.createTrackbar( 82 | low_S_name, window_detection_name, low_S, max_value, 83 | on_low_S_thresh_trackbar 84 | ) 85 | cv.createTrackbar( 86 | high_S_name, window_detection_name, high_S, max_value, 87 | on_high_S_thresh_trackbar 88 | ) 89 | cv.createTrackbar( 90 | low_V_name, window_detection_name, low_V, max_value, 91 | on_low_V_thresh_trackbar 92 | ) 93 | cv.createTrackbar( 94 | high_V_name, window_detection_name, high_V, max_value, 95 | on_high_V_thresh_trackbar 96 | ) 97 | while True: 98 | 99 | frame = cap 100 | # frame = cv.imread('best_match11.png') 101 | if frame is None: 102 | print('No frame found!') 103 | break 104 | frame_HSV = cv.cvtColor(frame, cv.COLOR_BGR2HSV) 105 | frame_threshold = cv.inRange( 106 | frame_HSV, (low_H, low_S, low_V), (high_H, high_S, high_V)) 107 | 108 | cv.imshow(window_capture_name, frame) 109 | cv.imshow(window_detection_name, frame_threshold) 110 | 111 | key = cv.waitKey(30) 112 | if key == ord('q') or key == 27: 113 | break 114 | -------------------------------------------------------------------------------- /scripts/auto_gift_pokemon.py: -------------------------------------------------------------------------------- 1 | """Script for soft resetting to get 0 Atk IVs on gift Pokemon 2 | 3 | This script was developed for the Poipole available in the Max Lair and may not 4 | work for other gift Pokemon. 5 | 6 | Instructions: 7 | 1. Your Pokemon icon must be in its default position in the menu (top row, 8 | second column). 9 | 2. Navigate to a box in your PC that has an opening in the default cursor 10 | position (top left corner of the box). 11 | 3. Press the '+' button until the IVs of Pokemon in your boxes are shown. 12 | 4. Have your party full. 13 | 5. Save directly in front of the Poipole. 14 | 6. Connect the microcontroller, serial connection, and capture card, then start 15 | the script. 16 | """ 17 | 18 | import logging 19 | import os 20 | import sys 21 | from datetime import datetime 22 | from typing import Dict, Callable, Optional, TypeVar 23 | 24 | import pytesseract 25 | import toml 26 | 27 | # We need to import some class definitions from the parent directory. 28 | from os.path import dirname, abspath 29 | base_dir = dirname(dirname(abspath(__file__))) 30 | sys.path.insert(1, base_dir) 31 | sys.path.insert(1, os.path.join(base_dir, 'automaxlair')) 32 | 33 | import automaxlair 34 | 35 | Image = TypeVar('cv2 image') 36 | 37 | 38 | # load configuration from the config file 39 | try: 40 | config = toml.load("Config.toml") 41 | except FileNotFoundError: 42 | raise FileNotFoundError( 43 | "The Config.toml file was not found! Be sure to copy " 44 | "Config.sample.toml as Config.toml and edit it!") 45 | except: # noqa: E722 46 | raise SyntaxError( 47 | "Something went wrong parsing Config.toml\n" 48 | "Please make sure you entered the information right " 49 | "and did not modify \" or . symbols or have uppercase true or false " 50 | "in the settings.") 51 | 52 | COM_PORT = config['COM_PORT'] 53 | VIDEO_INDEX = config['VIDEO_INDEX'] 54 | VIDEO_EXTRA_DELAY = config['advanced']['VIDEO_EXTRA_DELAY'] 55 | pytesseract.pytesseract.tesseract_cmd = config['TESSERACT_PATH'] 56 | ENABLE_DEBUG_LOGS = config['advanced']['ENABLE_DEBUG_LOGS'] 57 | 58 | # Set the log name 59 | LOG_NAME = f"auto_gift_pokemon_{datetime.now().strftime('%Y-%m-%d %H-%M-%S')}" 60 | 61 | 62 | class AutoGiftPokemonController( 63 | automaxlair.switch_controller.SwitchController 64 | ): 65 | """Switch controller specific to resetting for 0 Atk gift Pokemon.""" 66 | def __init__( 67 | self, 68 | config_, 69 | log_name: str, 70 | actions: Dict[str, Callable] 71 | ) -> None: 72 | 73 | # Call base class constructor. 74 | super().__init__(config_, log_name, actions) 75 | 76 | self.resets = 0 77 | 78 | # Rectangles for OCR and display 79 | self.IV_atk_rect = ((0.78, 0.25), (0.9, 0.30)) 80 | self.IV_spa_rect = ((0.78, 0.35), (0.9, 0.4)) 81 | 82 | def get_frame( 83 | self, 84 | rectangle_set: Optional[str] = None, 85 | resize: bool = False 86 | ) -> Image: 87 | """Get an annotated image of the current Switch output.""" 88 | 89 | # Get the base image from the base class method. 90 | img = super().get_frame(resize=resize) 91 | if rectangle_set is not None: 92 | self.outline_region(img, self.IV_atk_rect, (0, 255, 0)) 93 | 94 | return img 95 | 96 | def display_results(self, log: bool = False, screenshot: bool = False): 97 | """Display video from the Switch alongside some annotations describing 98 | the run sequence. 99 | """ 100 | 101 | # Construct the dictionary that will be displayed by the base method. 102 | for key, value in { 103 | 'Resets': self.resets, 104 | }.items(): 105 | self.info[key] = value 106 | 107 | # Call the base display method. 108 | super().display_results( 109 | image=self.get_frame( 110 | rectangle_set=self.stage, resize=True), log=log, 111 | screenshot=screenshot) 112 | 113 | 114 | def initialize(ctrlr) -> None: 115 | """Executed once at the beginning of the sequence.""" 116 | # assume we're starting from the select controller menu, connect, then 117 | # press home twice to return to the game 118 | ctrlr.push_buttons( 119 | (b'a', 2), (b'h', 2.0), (b'h', 2.0), (b'b', 1.5), (b'b', 1.5) 120 | ) 121 | return 'loop' 122 | 123 | 124 | def loop(ctrlr) -> None: 125 | """Main sequence that is repeated once per cycle.""" 126 | # Take the gift Pokemon and navigate to its IV summary. 127 | ctrlr.push_buttons( 128 | (b'b', 1), (b'a', 1), (b'a', 1), (b'a', 1), (b'a', 1), (b'a', 1), 129 | (b'a', 1), (b'a', 1), (b'a', 1), (b'a', 1), (b'a', 1), (b'a', 1), 130 | (b'a', 1), (b'a', 1), (b'a', 1), (b'a', 1), (b'a', 1), (b'a', 1), 131 | (b'x', 1.5), (b'>', 0.5), (b'a', 2), 132 | (b'r', 3 + VIDEO_EXTRA_DELAY)) 133 | 134 | # Check the Atk IV and quit if it's 0 ("No good") 135 | IV_text = ctrlr.read_text(ctrlr.get_frame(), ctrlr.IV_atk_rect) 136 | if 'nogood' in IV_text.lower().replace(' ', ''): 137 | ctrlr.log('********** Found 0 Atk target! **********') 138 | return None 139 | ctrlr.resets += 1 140 | ctrlr.log(f'IV text detected as {IV_text}. Moving to reset {ctrlr.resets}') 141 | 142 | # Otherwise, reset the game and try again. 143 | ctrlr.push_buttons( 144 | (b'h', 3), (b'x', 1), (b'a', 3), (b'a', 1), (b'a', 20), (b'a', 4) 145 | ) 146 | return 'loop' 147 | 148 | 149 | def main(log_name: str) -> None: 150 | """Entry point for the sequence.""" 151 | 152 | actions = {'initialize': initialize, 'loop': loop} 153 | 154 | controller = AutoGiftPokemonController(config, log_name, actions) 155 | 156 | controller.event_loop() 157 | 158 | 159 | def exception_handler(exception_type, exception_value, exception_traceback): 160 | """Exception hook to ensure exceptions get logged.""" 161 | logger.error( 162 | 'Exception occurred:', 163 | exc_info=(exception_type, exception_value, exception_traceback) 164 | ) 165 | 166 | 167 | if __name__ == '__main__': 168 | # Set up the logger 169 | 170 | # Configure the logger. 171 | logger = logging.getLogger(LOG_NAME) 172 | logger.setLevel(logging.DEBUG if ENABLE_DEBUG_LOGS else logging.INFO) 173 | formatter = logging.Formatter( 174 | '%(asctime)s | %(levelname)s: %(message)s' 175 | ) 176 | 177 | # make the console formatter easier to read with fewer bits of info 178 | console_formatter = logging.Formatter( 179 | "%(asctime)s | %(levelname)s: %(message)s", "%H:%M:%S" 180 | ) 181 | 182 | # Configure the console, which will print logged information. 183 | console = logging.StreamHandler() 184 | console.setLevel(logging.DEBUG if ENABLE_DEBUG_LOGS else logging.INFO) 185 | console.setFormatter(console_formatter) 186 | 187 | # Configure the file handler, which will save logged information. 188 | fileHandler = logging.FileHandler( 189 | filename=os.path.join(base_dir, 'logs', LOG_NAME + '.log'), 190 | encoding="UTF-8" 191 | ) 192 | fileHandler.setFormatter(formatter) 193 | fileHandler.setLevel(logging.DEBUG if ENABLE_DEBUG_LOGS else logging.INFO) 194 | 195 | # Add the handlers to the logger so that it will both print messages to 196 | # the console as well as save them to a log file. 197 | logger.addHandler(console) 198 | logger.addHandler(fileHandler) 199 | logger.info('Starting new series: %s.', LOG_NAME) 200 | 201 | # Call main 202 | sys.excepthook = exception_handler 203 | main(LOG_NAME) 204 | -------------------------------------------------------------------------------- /scripts/build_all_data_files.py: -------------------------------------------------------------------------------- 1 | import time 2 | import package_ball 3 | import package_pokemon 4 | import package_pokemon_sprites 5 | import score_pokemon 6 | import get_good_color_discord 7 | 8 | 9 | if __name__ == '__main__': 10 | start = time.time() 11 | package_ball.main() 12 | end = time.time() 13 | print(f'package_ball took {end - start} s') 14 | 15 | start = time.time() 16 | package_pokemon.main() 17 | end = time.time() 18 | print(f'package_pokemon took {end - start} s') 19 | 20 | start = time.time() 21 | score_pokemon.main() 22 | end = time.time() 23 | print(f'score_pokemon took {end - start} s') 24 | 25 | start = time.time() 26 | package_pokemon_sprites.main() 27 | end = time.time() 28 | print(f'package_pokemon_sprites took {end - start} s') 29 | 30 | start = time.time() 31 | get_good_color_discord.main() 32 | end = time.time() 33 | print(f'get_good_color_discord took {end - start} s') 34 | -------------------------------------------------------------------------------- /scripts/build_path_tree.py: -------------------------------------------------------------------------------- 1 | """Tree training and building for basic path selection 2 | 3 | This is not some crazy smart algorithm, but is designed simply 4 | to update store all path possiblities that might arise while returning 5 | simple score values. 6 | """ 7 | 8 | import sys 9 | from os.path import abspath, dirname, join 10 | 11 | base_dir = dirname(dirname(abspath(__file__))) 12 | sys.path.insert(1, base_dir) 13 | 14 | import jsonpickle 15 | from automaxlair.path_tree import PathTree 16 | 17 | 18 | def argmax(pairs): 19 | """Function to return the argmax of a set of pairs""" 20 | return max(pairs, key=lambda x: x[1])[0] 21 | 22 | 23 | def argmax_index(values): 24 | """Function to take advantage of argmax so you can use one list""" 25 | return argmax(enumerate(values)) 26 | 27 | 28 | if __name__ == "__main__": 29 | 30 | with open(join(base_dir, 'data', 'rental_pokemon.json'), 'r', encoding='utf8') as file: 31 | rental_pokemon = jsonpickle.decode(file.read()) 32 | 33 | with open(join(base_dir, 'data', 'boss_pokemon.json'), 'r', encoding='utf8') as file: 34 | boss_pokemon = jsonpickle.decode(file.read()) 35 | 36 | tree = PathTree(rental_pokemon=rental_pokemon, boss_pokemon=boss_pokemon) 37 | 38 | # save the tree to pickle 39 | with open(join(base_dir, 'data', 'path_tree.json'), 'w', encoding='utf8') as file: 40 | file.write(jsonpickle.encode(tree, indent=4)) 41 | 42 | legendary = 'articuno' 43 | 44 | test_paths = [ 45 | ['rock', 'rock', 'rock'], 46 | ['rock', 'fire', 'fighting'], 47 | ['rock', 'normal', 'rock'], 48 | ['dark', 'fire', 'fighting'], 49 | ['ground', 'fire', 'water'], 50 | ['water', 'electric', 'grass'], 51 | ['fire', 'rock', 'fire'] 52 | ] 53 | 54 | path_scores = [] 55 | 56 | for test_path in test_paths: 57 | path_scores.append(tree.score_path(legendary, test_path)) 58 | 59 | print(path_scores) 60 | 61 | best_idx = argmax_index(path_scores) 62 | 63 | print( 64 | f"Best path found to be {test_paths[best_idx]} with score {path_scores[best_idx]} for {legendary}") 65 | -------------------------------------------------------------------------------- /scripts/calyrex.py: -------------------------------------------------------------------------------- 1 | """Script for soft resetting to get 0 Atk IVs calyrex 2 | 3 | Instructions: 4 | 1. Press the '+' button until the IVs of Pokemon in your boxes are shown. 5 | 2. Have your party missing 2 slots. 6 | 3. Save directly in front of calyrex. 7 | 4. Connect the microcontroller, serial connection, and capture card, then start 8 | the script. 9 | """ 10 | 11 | import logging 12 | import os 13 | import sys 14 | import time 15 | import cv2 16 | from datetime import datetime 17 | from typing import Dict, Callable, Optional, TypeVar 18 | 19 | import pytesseract 20 | import toml 21 | 22 | # We need to import some class definitions from the parent directory. 23 | from os.path import dirname, abspath 24 | base_dir = dirname(dirname(abspath(__file__))) 25 | sys.path.insert(1, base_dir) 26 | sys.path.insert(1, os.path.join(base_dir, 'automaxlair')) 27 | 28 | import automaxlair 29 | 30 | Image = TypeVar('cv2 image') 31 | 32 | 33 | # load configuration from the config file 34 | try: 35 | config = toml.load("Config.toml") 36 | except FileNotFoundError: 37 | raise FileNotFoundError( 38 | "The Config.toml file was not found! Be sure to copy " 39 | "Config.sample.toml as Config.toml and edit it!") 40 | except: # noqa: E722 41 | raise SyntaxError( 42 | "Something went wrong parsing Config.toml\n" 43 | "Please make sure you entered the information right " 44 | "and did not modify \" or . symbols or have uppercase true or false " 45 | "in the settings.") 46 | 47 | COM_PORT = config['COM_PORT'] 48 | VIDEO_INDEX = config['VIDEO_INDEX'] 49 | VIDEO_EXTRA_DELAY = config['advanced']['VIDEO_EXTRA_DELAY'] 50 | pytesseract.pytesseract.tesseract_cmd = config['TESSERACT_PATH'] 51 | ENABLE_DEBUG_LOGS = config['advanced']['ENABLE_DEBUG_LOGS'] 52 | 53 | # Set the log name 54 | LOG_NAME = f"calyrex_{datetime.now().strftime('%Y-%m-%d %H-%M-%S')}" 55 | 56 | 57 | class CalyrexController(automaxlair.switch_controller.SwitchController): 58 | """Switch controller specific to resetting for 0 Atk calyrex.""" 59 | 60 | def __init__( 61 | self, 62 | config_, 63 | log_name: str, 64 | actions: Dict[str, Callable] 65 | ) -> None: 66 | 67 | # Call base class constructor. 68 | super().__init__(config_, log_name, actions) 69 | 70 | self.resets = 0 71 | self.checks_for_IV = 0 72 | self.current_balls_thrown = 0 73 | self.total_balls_thrown = 0 74 | 75 | # Rectangles for OCR and display 76 | self.IV_atk_rect = ((0.78, 0.25), (0.9, 0.30)) 77 | self.battle_symbol_rect = ((0.9, 0.55), (0.99, 0.69)) 78 | self.text_rect = ((0, 0.70), (1, 1)) 79 | 80 | self.misc_icons = {} 81 | directory = self.config['pokemon_data_paths']['misc_icon_dir'] 82 | for filename in os.listdir(directory): 83 | if '.png' in filename: 84 | self.misc_icons[filename.split('.png')[0]] = cv2.imread( 85 | os.path.join(directory, filename) 86 | ) 87 | 88 | def get_frame( 89 | self, 90 | rectangle_set: Optional[str] = None, 91 | resize: bool = False 92 | ) -> Image: 93 | """Get an annotated image of the current Switch output.""" 94 | 95 | # Get the base image from the base class method. 96 | img = super().get_frame(resize=resize) 97 | if rectangle_set is not None: 98 | self.outline_region(img, self.IV_atk_rect, (255, 0, 0)) 99 | self.outline_region(img, self.battle_symbol_rect, (0, 255, 0)) 100 | self.outline_region(img, self.text_rect, (0, 0, 255)) 101 | 102 | return img 103 | 104 | def display_results(self, log: bool = False, screenshot: bool = False): 105 | """Display video from the Switch alongside some annotations describing 106 | the run sequence. 107 | """ 108 | 109 | # Construct the dictionary that will be displayed by the base method. 110 | for key, value in { 111 | 'Resets': self.resets, 112 | 'Checks for IV': self.checks_for_IV, 113 | 'Current balls thrown': self.current_balls_thrown, 114 | 'Total balls thrown': self.total_balls_thrown, 115 | }.items(): 116 | self.info[key] = value 117 | 118 | # Call the base display method. 119 | super().display_results( 120 | image=self.get_frame( 121 | rectangle_set=self.stage, resize=True), log=log, 122 | screenshot=screenshot) 123 | 124 | 125 | def initialize(ctrlr) -> None: 126 | """Executed once at the beginning of the sequence.""" 127 | # assume we're starting from the select controller menu, connect, then 128 | # press home twice to return to the game 129 | ctrlr.push_buttons( 130 | (b'a', 2), (b'h', 2.0), (b'h', 2.0), (b'b', 1.5), (b'b', 1.5) 131 | ) 132 | return 'loop' 133 | 134 | 135 | def loop(ctrlr) -> None: 136 | """Main sequence that is repeated once per cycle.""" 137 | while(True): 138 | img = ctrlr.get_frame() 139 | fight_menu_image = ctrlr.get_image_slice(img, ctrlr.battle_symbol_rect) 140 | fight_match = ctrlr.match_template( 141 | fight_menu_image, ctrlr.misc_icons['fight'])[0] 142 | if fight_match >= 0.85: 143 | ctrlr.log( 144 | 'Detected "Fight" symbol with match value of ' 145 | f'{fight_match:.3f}.', 'DEBUG') 146 | break 147 | else: 148 | ctrlr.push_button(b'a', 2) 149 | 150 | start_time = time.time() 151 | while(True): 152 | img = ctrlr.get_frame() 153 | fight_menu_image = ctrlr.get_image_slice(img, ctrlr.battle_symbol_rect) 154 | fight_match = ctrlr.match_template( 155 | fight_menu_image, ctrlr.misc_icons['fight'])[0] 156 | if fight_match >= 0.85: 157 | ctrlr.log( 158 | 'Detected "Fight" symbol with match value of ' 159 | f'{fight_match:.3f}.', 'DEBUG') 160 | 161 | # Throw a ball and then reset the timer 162 | ctrlr.log('Throw a ball.') 163 | ctrlr.current_balls_thrown += 1 164 | ctrlr.total_balls_thrown += 1 165 | 166 | ctrlr.push_buttons( 167 | (b'x', 1), (b'a', 1) 168 | ) 169 | start_time = time.time() 170 | elif 'avez attrapé' in ctrlr.read_text(img, ctrlr.text_rect): 171 | # pokemon was caught, now check it's IV 172 | break 173 | elif ('est K.O.' in ctrlr.read_text(img, ctrlr.text_rect)) or (time.time() - start_time > 30): 174 | # if more than 30s passed, consider you need to reset (program locked itself, your pokemon died, etc) 175 | ctrlr.resets += 1 176 | 177 | ctrlr.log(f'Program stall for 30s. Moving to reset {ctrlr.resets}.') 178 | ctrlr.current_balls_thrown = 0 179 | ctrlr.push_buttons( 180 | (b'h', 3), (b'x', 1), (b'a', 3), (b'a', 1), (b'a', 20), (b'a', 4) 181 | ) 182 | return 'loop' 183 | 184 | for __ in range(45): 185 | ctrlr.push_button(b'a', 1) 186 | 187 | ctrlr.log('Splitting calyrex and spectrier.') 188 | ctrlr.push_buttons( 189 | (b'x', 1.5), (b'>', 0.5), (b'>', 0.5), 190 | (b'a', 2), (b'<', 0.5), (b'^', 0.5), 191 | (b'a', 2), (b'a', 2), (b'^', 2), 192 | (b'a', 2), (b'a', 2)) 193 | 194 | for __ in range(10): 195 | ctrlr.push_button(b'b', 1) 196 | 197 | ctrlr.log('Checkin stats.') 198 | ctrlr.push_buttons( 199 | (b'x', 2), (b'<', 0.5), (b'a', 2), 200 | (b'r', 3), (b'<', 1), (b'^', 1), 201 | (b'^', 1 + VIDEO_EXTRA_DELAY)) 202 | # Check the Atk IV and quit if it's 0 ("No good") 203 | IV_text = ctrlr.read_text(ctrlr.get_frame(), ctrlr.IV_atk_rect) 204 | if 'pastop' in IV_text.lower().replace(' ', ''): 205 | ctrlr.log('********** Found 0 Atk target! **********') 206 | return None 207 | ctrlr.checks_for_IV += 1 208 | ctrlr.current_balls_thrown = 0 209 | ctrlr.log(f'IV text detected as {IV_text}. It was checked {ctrlr.checks_for_IV} times.') 210 | 211 | ctrlr.push_button(b'^', 1 + VIDEO_EXTRA_DELAY) 212 | # Check the Atk IV and quit if it's 0 ("No good") 213 | IV_text = ctrlr.read_text(ctrlr.get_frame(), ctrlr.IV_atk_rect) 214 | if 'pastop' in IV_text.lower().replace(' ', ''): 215 | ctrlr.log('********** Found 0 Atk target! **********') 216 | return None 217 | ctrlr.checks_for_IV += 1 218 | ctrlr.current_balls_thrown = 0 219 | ctrlr.log(f'IV text detected as {IV_text}. It was checked {ctrlr.checks_for_IV} times.') 220 | 221 | # Otherwise, reset the game and try again. 222 | ctrlr.push_buttons( 223 | (b'h', 3), (b'x', 1), (b'a', 3), (b'a', 1), (b'a', 20), (b'a', 4) 224 | ) 225 | return 'loop' 226 | 227 | 228 | def main(log_name: str) -> None: 229 | """Entry point for the sequence.""" 230 | 231 | actions = {'initialize': initialize, 'loop': loop} 232 | 233 | controller = CalyrexController(config, log_name, actions) 234 | 235 | controller.event_loop() 236 | 237 | 238 | def exception_handler(exception_type, exception_value, exception_traceback): 239 | """Exception hook to ensure exceptions get logged.""" 240 | logger.error( 241 | 'Exception occurred:', 242 | exc_info=(exception_type, exception_value, exception_traceback) 243 | ) 244 | 245 | 246 | if __name__ == '__main__': 247 | # Set up the logger 248 | 249 | # Configure the logger. 250 | logger = logging.getLogger(LOG_NAME) 251 | logger.setLevel(logging.DEBUG if ENABLE_DEBUG_LOGS else logging.INFO) 252 | formatter = logging.Formatter( 253 | '%(asctime)s | %(levelname)s: %(message)s' 254 | ) 255 | 256 | # make the console formatter easier to read with fewer bits of info 257 | console_formatter = logging.Formatter( 258 | "%(asctime)s | %(levelname)s: %(message)s", "%H:%M:%S" 259 | ) 260 | 261 | # Configure the console, which will print logged information. 262 | console = logging.StreamHandler() 263 | console.setLevel(logging.DEBUG if ENABLE_DEBUG_LOGS else logging.INFO) 264 | console.setFormatter(console_formatter) 265 | 266 | # Configure the file handler, which will save logged information. 267 | fileHandler = logging.FileHandler( 268 | filename=os.path.join(base_dir, 'logs', LOG_NAME + '.log'), 269 | encoding="UTF-8" 270 | ) 271 | fileHandler.setFormatter(formatter) 272 | fileHandler.setLevel(logging.DEBUG if ENABLE_DEBUG_LOGS else logging.INFO) 273 | 274 | # Add the handlers to the logger so that it will both print messages to 275 | # the console as well as save them to a log file. 276 | logger.addHandler(console) 277 | logger.addHandler(fileHandler) 278 | logger.info('Starting new series: %s.', LOG_NAME) 279 | 280 | # Call main 281 | sys.excepthook = exception_handler 282 | main(LOG_NAME) 283 | -------------------------------------------------------------------------------- /scripts/custom_script_template.py: -------------------------------------------------------------------------------- 1 | """Template for creating custom scripts. 2 | 3 | To use, edit the areas enclosed in the horizontal lines. The simplest place to 4 | edit is in the `loop` function, which runs continuously until the program ends. 5 | 6 | You can run this script directly after making these modifications. 7 | """ 8 | 9 | import logging 10 | import os 11 | import sys 12 | from datetime import datetime 13 | from typing import Callable, Dict, TypeVar 14 | 15 | import pytesseract 16 | import toml 17 | 18 | # We need to import some class definitions from the parent directory. 19 | from os.path import dirname, abspath 20 | base_dir = dirname(dirname(abspath(__file__))) 21 | sys.path.insert(1, base_dir) 22 | sys.path.insert(1, os.path.join(base_dir, 'automaxlair')) 23 | 24 | import automaxlair # noqa: E402 25 | Image = TypeVar('cv2 image') 26 | 27 | 28 | # load configuration from the config file 29 | try: 30 | config = toml.load("Config.toml") 31 | except FileNotFoundError: 32 | raise FileNotFoundError( 33 | "The Config.toml file was not found! Be sure to copy " 34 | "Config.sample.toml as Config.toml and edit it!") 35 | except: # noqa: E722 36 | raise SyntaxError( 37 | "Something went wrong parsing Config.toml\n" 38 | "Please make sure you entered the information right " 39 | "and did not modify \" or . symbols or have uppercase true or false " 40 | "in the settings.") 41 | 42 | COM_PORT = config['COM_PORT'] 43 | VIDEO_INDEX = config['VIDEO_INDEX'] 44 | VIDEO_EXTRA_DELAY = config['advanced']['VIDEO_EXTRA_DELAY'] 45 | pytesseract.pytesseract.tesseract_cmd = config['TESSERACT_PATH'] 46 | ENABLE_DEBUG_LOGS = config['advanced']['ENABLE_DEBUG_LOGS'] 47 | 48 | # Set the log name 49 | LOG_NAME = f"CUSTOM_SCRIPT_{datetime.now().strftime('%Y-%m-%d %H-%M-%S')}" 50 | 51 | 52 | class CustomController(automaxlair.switch_controller.SwitchController): 53 | """Custom Switch controller that can include any functionality you want to 54 | add that doesn't fit in the functions below, for example extra variables 55 | that you want to track or new methods that you plan to use often. 56 | """ 57 | 58 | def __init__( 59 | self, 60 | config_, 61 | log_name: str, 62 | actions: Dict[str, Callable] 63 | ) -> None: 64 | # Call base class constructor. 65 | super().__init__(config_, log_name, actions) 66 | 67 | # -------------------- 68 | # Add custom initialization here. 69 | 70 | # -------------------- 71 | 72 | def get_frame( 73 | self, 74 | resize: bool = False 75 | ) -> Image: 76 | """Get an annotated image of the current Switch output.""" 77 | 78 | # Get the base image from the base class method. 79 | img = super().get_frame(resize=resize) 80 | 81 | # -------------------- 82 | # Add any extra image processing you want here. 83 | 84 | # -------------------- 85 | 86 | return img 87 | 88 | def display_results(self, log: bool = False, screenshot: bool = False): 89 | """Display video from the Switch alongside some annotations describing 90 | the run sequence. 91 | """ 92 | 93 | # Construct the dictionary that will be displayed by the base method. 94 | for key, value in { 95 | # -------------------- 96 | # You can add key: value pairs here to be displayed. 97 | 98 | # -------------------- 99 | }.items(): 100 | self.info[key] = value 101 | 102 | # Call the base display method. 103 | super().display_results( 104 | image=self.get_frame(resize=True), log=log, 105 | screenshot=screenshot) 106 | 107 | 108 | def initialize(ctrlr) -> None: 109 | """Executed once at the beginning of the sequence.""" 110 | # assume we're starting from the select controller menu, connect, then 111 | # press home twice to return to the game 112 | ctrlr.push_buttons( 113 | (b'a', 2), (b'h', 2.0), (b'h', 2.0), (b'b', 1.5), (b'b', 1.5) 114 | ) 115 | return 'loop' 116 | 117 | 118 | def loop(ctrlr) -> None: 119 | """Main sequence that is repeated once per cycle. To quit, simply return 120 | `None` at any point in this function. 121 | """ 122 | 123 | # -------------------- 124 | # Add your commands here! 125 | 126 | # -------------------- 127 | 128 | return 'loop' 129 | 130 | 131 | def main(log_name: str) -> None: 132 | """Entry point for the sequence.""" 133 | 134 | actions = {'initialize': initialize, 'loop': loop} 135 | 136 | # Replace the controller name if you renamed the custom class. 137 | controller = CustomController(config, log_name, actions) 138 | 139 | controller.event_loop() 140 | 141 | 142 | def exception_handler(exception_type, exception_value, exception_traceback): 143 | """Exception hook to ensure exceptions get logged.""" 144 | logger.error( 145 | 'Exception occurred:', 146 | exc_info=(exception_type, exception_value, exception_traceback) 147 | ) 148 | 149 | 150 | if __name__ == '__main__': 151 | # Set up the logger 152 | 153 | # Configure the logger. 154 | logger = logging.getLogger(LOG_NAME) 155 | logger.setLevel(logging.DEBUG if ENABLE_DEBUG_LOGS else logging.INFO) 156 | formatter = logging.Formatter( 157 | '%(asctime)s | %(levelname)s: %(message)s' 158 | ) 159 | 160 | # make the console formatter easier to read with fewer bits of info 161 | console_formatter = logging.Formatter( 162 | "%(asctime)s | %(levelname)s: %(message)s", "%H:%M:%S" 163 | ) 164 | 165 | # Configure the console, which will print logged information. 166 | console = logging.StreamHandler() 167 | console.setLevel(logging.DEBUG if ENABLE_DEBUG_LOGS else logging.INFO) 168 | console.setFormatter(console_formatter) 169 | 170 | # Configure the file handler, which will save logged information. 171 | fileHandler = logging.FileHandler( 172 | filename=os.path.join(base_dir, 'logs', LOG_NAME + '.log'), 173 | encoding="UTF-8" 174 | ) 175 | fileHandler.setFormatter(formatter) 176 | fileHandler.setLevel(logging.DEBUG if ENABLE_DEBUG_LOGS else logging.INFO) 177 | 178 | # Add the handlers to the logger so that it will both print messages to 179 | # the console as well as save them to a log file. 180 | logger.addHandler(console) 181 | logger.addHandler(fileHandler) 182 | logger.info('Starting new series: %s.', LOG_NAME) 183 | 184 | # Call main 185 | sys.excepthook = exception_handler 186 | main(LOG_NAME) 187 | -------------------------------------------------------------------------------- /scripts/detailed_log.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | # We need to import log files from the parent directory. 4 | from os.path import dirname, abspath 5 | base_dir = dirname(dirname(abspath(__file__))) 6 | log_path = os.path.join(base_dir, 'logs') 7 | file_list = os.listdir(log_path) 8 | 9 | bosses = ['articuno', 'zapdos', 'moltres', 'mewtwo', 'raikou', 'entei', 10 | 'suicune', 'lugia', 'ho-oh', 'latias', 'latios', 'kyogre', 'groudon', 11 | 'rayquaza', 'uxie', 'mesprit', 'azelf', 'dialga', 'palkia', 'heatran', 12 | 'giratina-altered', 'cresselia', 'tornadus-incarnate', 'thundurus-incarnate', 13 | 'reshiram', 'zekrom', 'landorus-incarnate', 'kyurem', 'xerneas', 'yveltal', 14 | 'zygarde-50', 'tapu-koko', 'tapu-lele', 'tapu-bulu', 'tapu-fini', 'solgaleo', 15 | 'lunala', 'necrozma', 'nihilego', 'xurkitree', 'buzzwole', 'pheromosa', 16 | 'celesteela', 'kartana', 'guzzlord', 'stakataka', 'blacephalon' 17 | ] 18 | global_losses = 0 19 | global_wins = 0 20 | global_shinies = 0 21 | global_legends = 0 22 | boss_files = {} 23 | 24 | for boss in bosses: 25 | found_files = [] 26 | for x in file_list: 27 | if boss.lower() in x.lower() and ('.log' in x or '.txt' in x): 28 | # add to the files found 29 | found_files.append(x) 30 | 31 | if len(found_files) > 0: 32 | boss_files[boss] = found_files 33 | 34 | boss_files = {key: val for key, val in boss_files.items() if len(val) > 0} 35 | boss_order = sorted(boss_files, key=lambda x: os.path.getmtime(os.path.join(log_path, boss_files[x][0]))) 36 | 37 | for boss in boss_order: 38 | logs = boss_files[boss] 39 | total_losses = 0 40 | total_wins = 0 41 | total_shinies = 0 42 | shiny_names = [] 43 | total_legends = 0 44 | 45 | for log in logs: 46 | with open(os.path.join(log_path, log), newline='\n', encoding='utf-8') as log_file: 47 | num_losses = 0 48 | num_wins = 0 49 | num_shinies = 0 50 | legend_caught = 0 51 | for row in log_file.readlines(): 52 | # The following lines are logged once at the end of a run. 53 | if 'You lose' in row: 54 | num_losses += 1 55 | if 'Congratulations' in row: 56 | num_wins += 1 57 | if 'Shiny found' in row: 58 | num_shinies += 1 59 | if 'will be kept' in row: 60 | shiny_names.append(row.split(' will be kept')[0].split('Shiny ')[1]) 61 | if (f'{boss} will be kept') in row: 62 | legend_caught += 1 63 | 64 | total_losses += num_losses 65 | total_wins += num_wins 66 | total_shinies += num_shinies 67 | total_legends += legend_caught 68 | 69 | total_runs = total_losses + total_wins 70 | win_percentage = 0 if total_runs == 0 else total_wins / total_runs * 100 71 | 72 | print(f'\nSummary for {boss}') 73 | print(f'Total loses: {total_losses}') 74 | print(f'Total wins: {total_wins}') 75 | print(f'Total runs: {total_runs}') 76 | print(f'Win percentage: {win_percentage:.0f} %') 77 | print(f'Legend odds: {total_legends} in {total_wins}') 78 | print(f'Total shinies found: {total_shinies}') 79 | for shiny_name in shiny_names: 80 | print(f'{shiny_name}') 81 | 82 | global_wins += total_wins 83 | global_losses += total_losses 84 | global_shinies += total_shinies 85 | global_legends += total_legends 86 | 87 | global_runs = global_losses + global_wins 88 | global_win_percentage = 0 if global_runs == 0 else global_wins / global_runs * 100 89 | global_legend_odds = 0 if global_legends == 0 else global_wins / global_legends 90 | 91 | print('\nGlobal Summary') 92 | print(f'Global loses: {global_losses}') 93 | print(f'Global wins: {global_wins}') 94 | print(f'Global runs: {global_runs}') 95 | print(f'Global Win percentage: {global_win_percentage:.0f} %') 96 | print(f'Global legends: {global_legends}') 97 | print(f'Global legend odds: 1 in {global_legend_odds:.0f}') 98 | print(f'Global shinies found: {global_shinies} \n') 99 | input('Press ENTER to exit log...') 100 | -------------------------------------------------------------------------------- /scripts/get_good_color_discord.py: -------------------------------------------------------------------------------- 1 | import jsonpickle 2 | import requests 3 | from PIL import Image 4 | import numpy as np 5 | from os.path import abspath, dirname, join 6 | 7 | base_dir = dirname(dirname(abspath(__file__))) 8 | 9 | 10 | def palette(img): 11 | """ 12 | Return palette in descending order of frequency 13 | 14 | Taken directly from stack overflow: https://stackoverflow.com/a/18801274 15 | """ 16 | arr = np.asarray(img) 17 | palette, index = np.unique(asvoid(arr).ravel(), return_inverse=True) 18 | palette = palette.view(arr.dtype).reshape(-1, arr.shape[-1]) 19 | count = np.bincount(index) 20 | order = np.argsort(count) 21 | return palette[order[::-1]] 22 | 23 | 24 | def asvoid(arr): 25 | """View the array as dtype np.void (bytes) 26 | This collapses ND-arrays to 1D-arrays, 27 | so you can perform 1D operations on them. 28 | http://stackoverflow.com/a/16216866/190597 (Jaime) 29 | http://stackoverflow.com/a/16840350/190597 (Jaime) 30 | Warning: 31 | >>> asvoid([-0.]) == asvoid([0.]) 32 | array([False], dtype=bool) 33 | """ 34 | arr = np.ascontiguousarray(arr) 35 | return arr.view(np.dtype((np.void, arr.dtype.itemsize * arr.shape[-1]))) 36 | 37 | 38 | def main(): 39 | 40 | with open( 41 | join(base_dir, 'data', 'boss_pokemon.json'), 'r', encoding='utf8' 42 | ) as file: 43 | boss_pokemon = jsonpickle.decode(file.read()) 44 | 45 | boss_colors = {} 46 | 47 | for boss_name in boss_pokemon.keys(): 48 | # get the image from pokemondb 49 | 50 | url = f"https://img.pokemondb.net/sprites/home/shiny/{boss_name}.png" 51 | img = Image.open(requests.get(url, stream=True).raw) 52 | 53 | the_pal = palette(img)[:10, :-1] 54 | 55 | # find the best color that isn't all 0's or 255's 56 | for pal_test in the_pal: 57 | if np.mean(pal_test) > 245 or np.mean(pal_test) < 10: 58 | # if np.all(pal_test == np.array([0, 0, 0])) or \ 59 | # np.all(pal_test == np.array([255, 255, 255])) or \ 60 | # np.all(pal_test == np.array([255, 255, 255])) or \ 61 | # np.all(pal_test == np.array([254, 254, 254])): 62 | continue 63 | else: 64 | col_use = pal_test 65 | break 66 | 67 | boss_colors[boss_name] = col_use.tolist() 68 | 69 | # NOTE: some of them were bad so i hand selected these ones 70 | boss_colors['buzzwole'] = [72, 209, 80] 71 | boss_colors['dialga'] = [177, 255, 177] 72 | boss_colors['giratina-altered'] = [219, 195, 159] 73 | boss_colors['lugia'] = [203, 106, 147] 74 | boss_colors['lunala'] = [187, 12, 51] 75 | boss_colors['mesprit'] = [245, 108, 128] 76 | boss_colors['mewtwo'] = [121, 193, 99] 77 | boss_colors['moltres'] = [230, 126, 145] 78 | boss_colors['tapu-fini'] = [125, 153, 182] 79 | boss_colors['tapu-bulu'] = [232, 222, 112] 80 | boss_colors['tapu-lele'] = [255, 172, 176] 81 | boss_colors['tapu-koko'] = [244, 160, 107] 82 | boss_colors['thundurus-incarnate'] = [159, 174, 216] 83 | boss_colors['tornadus-incarnate'] = [121, 159, 83] 84 | boss_colors['xerneas'] = [34, 177, 227] 85 | boss_colors['xurkitree'] = [129, 173, 200] 86 | boss_colors['zygarde-50'] = [41, 186, 149] 87 | 88 | # then save it as a json 89 | with open( 90 | join(base_dir, "data", 'boss_colors.json'), 'w', encoding='utf8' 91 | ) as f: 92 | f.write(jsonpickle.encode(boss_colors, indent=4)) 93 | 94 | 95 | if __name__ == "__main__": 96 | main() 97 | -------------------------------------------------------------------------------- /scripts/import_testing.py: -------------------------------------------------------------------------------- 1 | """Script for testing and viewing stored Pokemon and matchups.""" 2 | 3 | import os 4 | import pickle 5 | import sys 6 | import jsonpickle 7 | 8 | # We need to import some class definitions from the parent directory. 9 | from os.path import dirname, abspath 10 | base_dir = dirname(dirname(abspath(__file__))) 11 | sys.path.insert(1, base_dir) 12 | sys.path.insert(1, os.path.join(base_dir, 'automaxlair')) 13 | 14 | from automaxlair import matchup_scoring # noqa: E402 15 | from automaxlair.field import Field # noqa: E402 16 | 17 | 18 | def test_terrain(rental_pokemon, boss_pokemon): 19 | print('Terrain tests') 20 | field_clear = Field() 21 | field_clear.set_terrain_clear() 22 | 23 | field_electric = Field() 24 | field_electric.set_terrain_electric() 25 | 26 | field_psychic = Field() 27 | field_psychic.set_terrain_psychic() 28 | 29 | field_grassy = Field() 30 | field_grassy.set_terrain_grassy() 31 | 32 | field_misty = Field() 33 | field_misty.set_terrain_misty() 34 | print('electric move (clear / electric / psychic / grassy / misty)') 35 | for field in [field_clear, field_electric, field_psychic, field_grassy, field_misty]: 36 | dmg = matchup_scoring.calculate_damage(rental_pokemon['electabuzz'], 1, boss_pokemon['guzzlord'], field) 37 | print(f'Damage is {dmg}') 38 | 39 | print('rising voltage (clear / electric / psychic / grassy / misty)') 40 | for field in [field_clear, field_electric, field_psychic, field_grassy, field_misty]: 41 | dmg = matchup_scoring.calculate_damage(rental_pokemon['electabuzz'], 0, boss_pokemon['guzzlord'], field) 42 | print(f'Damage is {dmg}') 43 | 44 | print('psychic move (clear / electric / psychic / grassy / misty)') 45 | for field in [field_clear, field_electric, field_psychic, field_grassy, field_misty]: 46 | dmg = matchup_scoring.calculate_damage(rental_pokemon['slowbro'], 3, rental_pokemon['electabuzz'], field) 47 | print(f'Damage is {dmg}') 48 | 49 | print('expanding force (clear / electric / psychic / grassy / misty)') 50 | for field in [field_clear, field_electric, field_psychic, field_grassy, field_misty]: 51 | dmg = matchup_scoring.calculate_damage(rental_pokemon['slowbro'], 0, rental_pokemon['electabuzz'], field) 52 | print(f'Damage is {dmg}') 53 | 54 | print('dragon move (clear / electric / psychic / grassy / misty)') 55 | for field in [field_clear, field_electric, field_psychic, field_grassy, field_misty]: 56 | dmg = matchup_scoring.calculate_damage(rental_pokemon['charmeleon'], 1, rental_pokemon['electabuzz'], field) 57 | print(f'Damage is {dmg}') 58 | 59 | print('grass move (clear / electric / psychic / grassy / misty)') 60 | for field in [field_clear, field_electric, field_psychic, field_grassy, field_misty]: 61 | dmg = matchup_scoring.calculate_damage(rental_pokemon['tangela'], 2, rental_pokemon['electabuzz'], field) 62 | print(f'Damage is {dmg}') 63 | 64 | 65 | def test_weather(rental_pokemon, boss_pokemon): 66 | print('Weather tests') 67 | field_clear = Field() 68 | field_clear.set_weather_clear() 69 | 70 | field_rain = Field() 71 | field_rain.set_weather_rain() 72 | 73 | field_sandstorm = Field() 74 | field_sandstorm.set_weather_sandstorm() 75 | 76 | field_sunlingt = Field() 77 | field_sunlingt.set_weather_sunlight() 78 | 79 | field_hail = Field() 80 | field_hail.set_weather_hail() 81 | print('Solar beam (clear / rain / sandstorm / sun / hail)') 82 | for field in [field_clear, field_rain, field_sandstorm, field_sunlingt, field_hail]: 83 | dmg = matchup_scoring.calculate_damage(rental_pokemon['exeggutor'], 1, boss_pokemon['guzzlord'], field) 84 | print(f'Damage is {dmg}') 85 | 86 | print('Water attack (clear / rain / sandstorm / sun / hail)') 87 | for field in [field_clear, field_rain, field_sandstorm, field_sunlingt, field_hail]: 88 | dmg = matchup_scoring.calculate_damage(rental_pokemon['vaporeon'], 0, boss_pokemon['guzzlord'], field) 89 | print(f'Damage is {dmg}') 90 | 91 | print('Fire attack (clear / rain / sandstorm / sun / hail)') 92 | for field in [field_clear, field_rain, field_sandstorm, field_sunlingt, field_hail]: 93 | dmg = matchup_scoring.calculate_damage(rental_pokemon['flareon'], 0, boss_pokemon['guzzlord'], field) 94 | print(f'Damage is {dmg}') 95 | 96 | print('Weather ball attack vs ground type (clear / rain / sandstorm / sun / hail)') 97 | for field in [field_clear, field_rain, field_sandstorm, field_sunlingt, field_hail]: 98 | dmg = matchup_scoring.calculate_damage(rental_pokemon['roselia'], 2, rental_pokemon['marowak'], field) 99 | print(f'Damage is {dmg}') 100 | 101 | print('Weather ball attack vs flying type (clear / rain / sandstorm / sun / hail)') 102 | for field in [field_clear, field_rain, field_sandstorm, field_sunlingt, field_hail]: 103 | dmg = matchup_scoring.calculate_damage(rental_pokemon['roselia'], 2, rental_pokemon['unfezant'], field) 104 | print(f'Damage is {dmg}') 105 | 106 | print('Weather ball attack vs grass type (clear / rain / sandstorm / sun / hail)') 107 | for field in [field_clear, field_rain, field_sandstorm, field_sunlingt, field_hail]: 108 | dmg = matchup_scoring.calculate_damage(rental_pokemon['roselia'], 2, rental_pokemon['ivysaur'], field) 109 | print(f'Damage is {dmg}') 110 | 111 | print('Special attack vs rock type (clear / rain / sandstorm / sun / hail)') 112 | for field in [field_clear, field_rain, field_sandstorm, field_sunlingt, field_hail]: 113 | dmg = matchup_scoring.calculate_damage(rental_pokemon['roselia'], 0, rental_pokemon['sudowoodo'], field) 114 | print(f'Damage is {dmg}') 115 | 116 | 117 | def test_field(rental_pokemon, boss_pokemon): 118 | print('Fields tests') 119 | test_weather(rental_pokemon, boss_pokemon) 120 | test_terrain(rental_pokemon, boss_pokemon) 121 | 122 | 123 | def main(): 124 | with open( 125 | os.path.join(base_dir, 'data', 'boss_pokemon.json'), 'r', 126 | encoding='utf8' 127 | ) as file: 128 | boss_pokemon = jsonpickle.decode(file.read()) 129 | with open( 130 | os.path.join(base_dir, 'data', 'rental_pokemon.json'), 'r', 131 | encoding='utf8' 132 | ) as file: 133 | rental_pokemon = jsonpickle.decode(file.read()) 134 | with open( 135 | os.path.join(base_dir, 'data', 'boss_matchup_LUT.json'), 'r', 136 | encoding='utf8' 137 | ) as file: 138 | boss_matchups = jsonpickle.decode(file.read()) 139 | with open( 140 | os.path.join(base_dir, 'data', 'rental_matchup_LUT.json'), 'r', 141 | encoding='utf8' 142 | ) as file: 143 | rental_matchups = jsonpickle.decode(file.read()) 144 | with open( 145 | os.path.join(base_dir, 'data', 'rental_pokemon_scores.json'), 'r', 146 | encoding='utf8' 147 | ) as file: 148 | rental_scores = jsonpickle.decode(file.read()) 149 | 150 | # Test retrieval of a rental Pokemon 151 | rental_pokemon['stunfisk-galar'].print_verbose() 152 | print('________________________________________') 153 | 154 | # Test retrieval of a boss Pokemon 155 | boss_pokemon['mewtwo'].print_verbose() 156 | print('________________________________________') 157 | 158 | # Test retrieval of rental Pokemon matchups 159 | print( 160 | 'Matchup for Chansey against Golurk (poor): ' 161 | f'{rental_matchups["chansey"]["golurk"]}') 162 | print( 163 | 'Matchup for Carkol against Butterfree (good): ' 164 | f'{rental_matchups["carkol"]["butterfree"]}') 165 | print('________________________________________') 166 | 167 | # Test retrieval of boss Pokemon matchups 168 | print( 169 | 'Matchup for Jynx against Heatran (poor): ' 170 | f'{boss_matchups["jynx"]["heatran"]}') 171 | print( 172 | 'Matchup for Golurk against Raikou (good): ' 173 | f'{boss_matchups["golurk"]["raikou"]}') 174 | print('________________________________________') 175 | 176 | # Test retrieval of rental Pokemon scores 177 | print(f'Score for Jigglypuff (poor): {rental_scores["jigglypuff"]}') 178 | print(f'Score for Doublade (good): {rental_scores["doublade"]}') 179 | print('________________________________________') 180 | 181 | # Test move selection 182 | print('Wide Guard utility:') 183 | matchup_scoring.print_matchup_summary( 184 | rental_pokemon['pelipper'], boss_pokemon['groudon'], Field(), 185 | rental_pokemon.values() 186 | ) 187 | salazzle = rental_pokemon['salazzle'] 188 | print('Regular matchup:') 189 | matchup_scoring.print_matchup_summary( 190 | salazzle, boss_pokemon['kartana'], Field(), rental_pokemon.values() 191 | ) 192 | print('Max move scores:') 193 | salazzle.dynamax = True 194 | matchup_scoring.print_matchup_summary( 195 | salazzle, boss_pokemon['kartana'], Field(), rental_pokemon.values() 196 | ) 197 | print('Sap Sipper:') 198 | matchup_scoring.print_matchup_summary( 199 | rental_pokemon['tsareena'], rental_pokemon['azumarill'], Field(), 200 | rental_pokemon.values() 201 | ) 202 | print('________________________________________') 203 | 204 | # Ensure all rental Pokemon have sprites 205 | with open( 206 | os.path.join(base_dir, 'data', 'pokemon_sprites.pickle'), 'rb' 207 | ) as file: 208 | pokemon_sprites = pickle.load(file) 209 | pokemon_sprites_dict = dict(pokemon_sprites) 210 | for name_id in rental_pokemon: 211 | if pokemon_sprites_dict.get(name_id) is None: 212 | raise KeyError(f'ERROR: no image found for: {name_id}') 213 | print('Successfully tested Pokemon sprite importing without errors.') 214 | 215 | print('________________________________________') 216 | test_field(rental_pokemon, boss_pokemon) 217 | 218 | 219 | if __name__ == '__main__': 220 | main() 221 | -------------------------------------------------------------------------------- /scripts/package_ball.py: -------------------------------------------------------------------------------- 1 | """Collect ball images from pokesprite (linked submodule) and save them in a 2 | ready-to-use pickle file. 3 | """ 4 | 5 | import os 6 | import pickle 7 | import sys 8 | 9 | from os.path import dirname, abspath 10 | 11 | import cv2 12 | 13 | base_dir = dirname(dirname(abspath(__file__))) 14 | sys.path.insert(1, base_dir) 15 | 16 | 17 | def main(): 18 | """Build Ball dictionaries and pickle the results. 19 | """ 20 | 21 | image_dir = os.path.join(base_dir, 'data', 'pokesprite', 'items', 'ball') 22 | 23 | ball_ids = [ 24 | 'beast-ball', 'dive-ball', 'dream-ball', 'dusk-ball', 'fast-ball', 25 | 'friend-ball', 'great-ball', 'heal-ball', 'heavy-ball', 'level-ball', 26 | 'love-ball', 'lure-ball', 'luxury-ball', 'master-ball', 'moon-ball', 27 | 'nest-ball', 'net-ball', 'poke-ball', 'premier-ball', 'quick-ball', 28 | 'repeat-ball', 'safari-ball', 'sport-ball', 'timer-ball', 'ultra-ball'] 29 | ball_images = {} 30 | 31 | for ball_id in ball_ids: 32 | # Load the sprite for the ball 33 | image_fn = ball_id.split('-')[0] + '.png' 34 | ball_image = cv2.imread( 35 | os.path.join(image_dir, image_fn), cv2.IMREAD_UNCHANGED) 36 | # Double the sprite size to match what is shown in game. 37 | ball_image = cv2.resize( 38 | ball_image, (0, 0), fx=2, fy=2, interpolation=cv2.INTER_NEAREST) 39 | # Convert transparent pixels to black as they appear in game. 40 | mask = ball_image[:, :, 3] == 0 41 | ball_image[mask] = [0, 0, 0, 0] 42 | ball_images[ball_id] = cv2.cvtColor(ball_image, cv2.COLOR_BGRA2BGR) 43 | 44 | print(f'Finished loading {ball_id}') 45 | 46 | # Pickle the ball sprites for later use. 47 | with open( 48 | os.path.join(base_dir, 'data', 'ball_sprites.pickle'), 'wb' 49 | ) as file: 50 | pickle.dump(ball_images, file) 51 | print('Finished packaging balls!') 52 | 53 | 54 | if __name__ == '__main__': 55 | main() 56 | -------------------------------------------------------------------------------- /scripts/package_pokemon_sprites.py: -------------------------------------------------------------------------------- 1 | """Collect images from pokesprite (linked submodule) and save them in a 2 | ready-to-use pickle file. 3 | """ 4 | 5 | import pickle 6 | import os 7 | 8 | import cv2 9 | import jsonpickle 10 | 11 | # We need to import some class definitions from the parent directory. 12 | from os.path import dirname, abspath 13 | 14 | 15 | def main(): 16 | # We're working across a few directories, so store them for later use. 17 | base_dir = dirname(dirname(abspath(__file__))) 18 | data_dir = os.path.join(base_dir, 'data') 19 | image_dir = os.path.join( 20 | data_dir, 'pokesprite', 'pokemon-gen8', 'regular') 21 | 22 | # Load the rental Pokemon we need to build sprites for 23 | with open( 24 | os.path.join(data_dir, 'rental_pokemon.json'), 'r', 25 | encoding='utf8' 26 | ) as file: 27 | rental_pokemon = jsonpickle.decode(file.read()) 28 | 29 | # Generate sprites of each rental Pokemon. 30 | all_images = [] 31 | for name_id in rental_pokemon: 32 | # The name format isn't exactly between PokeAPI and pokesprite. 33 | # Therefore, manually convert where there are descrepencies. 34 | if name_id == 'basculin-red-striped': 35 | fn = 'basculin.png' 36 | elif name_id == 'gourgeist-average': 37 | fn = 'gourgeist.png' 38 | elif name_id == 'lycanroc-midday': 39 | fn = 'lycanroc.png' 40 | elif name_id == 'mimikyu-disguised': 41 | fn = 'mimikyu.png' 42 | elif name_id == 'toxtricity-amped': 43 | fn = 'toxtricity.png' 44 | elif name_id == 'indeedee-male': 45 | fn = 'indeedee.png' 46 | elif name_id == 'gastrodon': 47 | fn = 'gastrodon-east.png' 48 | else: 49 | fn = name_id + '.png' 50 | # Start by loading the Pokemon's sprite and female sprite if it exists. 51 | species_images = [] 52 | species_images.append(cv2.imread( 53 | os.path.join(image_dir, fn), cv2.IMREAD_UNCHANGED)) 54 | if fn in os.listdir(os.path.join(image_dir, 'female')): 55 | species_images.append(cv2.imread( 56 | os.path.join(image_dir, 'female', fn), cv2.IMREAD_UNCHANGED)) 57 | # Then, remove the alpha channel and add each image to the export list. 58 | for image in species_images: 59 | image = cv2.resize( 60 | image, (0, 0), fx=2, fy=2, interpolation=cv2.INTER_NEAREST) 61 | mask = image[:, :, 3] == 0 62 | image[mask] = [255, 255, 255, 255] 63 | all_images.append( 64 | (name_id, cv2.cvtColor(image, cv2.COLOR_BGRA2BGR))) 65 | 66 | print(f'Finished processing {name_id}') 67 | 68 | # Save the sprites. 69 | with open( 70 | os.path.join(data_dir, 'pokemon_sprites.pickle'), 'wb' 71 | ) as file: 72 | pickle.dump(all_images, file) 73 | 74 | 75 | if __name__ == '__main__': 76 | main() 77 | -------------------------------------------------------------------------------- /scripts/regis.py: -------------------------------------------------------------------------------- 1 | """Script for soft resetting to get 0 Atk IVs regis 2 | 3 | Instructions: 4 | 1. Your Pokemon icon must be in its default position in the menu (top row, 5 | second column). 6 | 2. Navigate to a box in your PC that has an opening in the default cursor 7 | position (top left corner of the box). 8 | 3. Press the '+' button until the IVs of Pokemon in your boxes are shown. 9 | 4. Have your party full. 10 | 5. Save directly in front of the regi with all lights on. 11 | 6. Connect the microcontroller, serial connection, and capture card, then start 12 | the script. 13 | """ 14 | 15 | import logging 16 | import os 17 | import sys 18 | import time 19 | import cv2 20 | from datetime import datetime 21 | from typing import Dict, Callable, Optional, TypeVar 22 | 23 | import pytesseract 24 | import toml 25 | 26 | # We need to import some class definitions from the parent directory. 27 | from os.path import dirname, abspath 28 | base_dir = dirname(dirname(abspath(__file__))) 29 | sys.path.insert(1, base_dir) 30 | sys.path.insert(1, os.path.join(base_dir, 'automaxlair')) 31 | 32 | import automaxlair 33 | 34 | Image = TypeVar('cv2 image') 35 | 36 | 37 | # load configuration from the config file 38 | try: 39 | config = toml.load("Config.toml") 40 | except FileNotFoundError: 41 | raise FileNotFoundError( 42 | "The Config.toml file was not found! Be sure to copy " 43 | "Config.sample.toml as Config.toml and edit it!") 44 | except: # noqa: E722 45 | raise SyntaxError( 46 | "Something went wrong parsing Config.toml\n" 47 | "Please make sure you entered the information right " 48 | "and did not modify \" or . symbols or have uppercase true or false " 49 | "in the settings.") 50 | 51 | COM_PORT = config['COM_PORT'] 52 | VIDEO_INDEX = config['VIDEO_INDEX'] 53 | VIDEO_EXTRA_DELAY = config['advanced']['VIDEO_EXTRA_DELAY'] 54 | pytesseract.pytesseract.tesseract_cmd = config['TESSERACT_PATH'] 55 | ENABLE_DEBUG_LOGS = config['advanced']['ENABLE_DEBUG_LOGS'] 56 | 57 | # Set the log name 58 | LOG_NAME = f"regis_{datetime.now().strftime('%Y-%m-%d %H-%M-%S')}" 59 | 60 | 61 | class RegisController(automaxlair.switch_controller.SwitchController): 62 | """Switch controller specific to resetting for 0 Atk regis.""" 63 | 64 | def __init__( 65 | self, 66 | config_, 67 | log_name: str, 68 | actions: Dict[str, Callable] 69 | ) -> None: 70 | 71 | # Call base class constructor. 72 | super().__init__(config_, log_name, actions) 73 | 74 | self.resets = 0 75 | self.checks_for_IV = 0 76 | self.current_balls_thrown = 0 77 | self.total_balls_thrown = 0 78 | 79 | # Rectangles for OCR and display 80 | self.IV_atk_rect = ((0.78, 0.25), (0.9, 0.30)) 81 | self.battle_symbol_rect = ((0.9, 0.55), (0.99, 0.69)) 82 | self.text_rect = ((0, 0.70), (1, 1)) 83 | 84 | self.misc_icons = {} 85 | directory = self.config['pokemon_data_paths']['misc_icon_dir'] 86 | for filename in os.listdir(directory): 87 | if '.png' in filename: 88 | self.misc_icons[filename.split('.png')[0]] = cv2.imread( 89 | os.path.join(directory, filename) 90 | ) 91 | 92 | def get_frame( 93 | self, 94 | rectangle_set: Optional[str] = None, 95 | resize: bool = False 96 | ) -> Image: 97 | """Get an annotated image of the current Switch output.""" 98 | 99 | # Get the base image from the base class method. 100 | img = super().get_frame(resize=resize) 101 | if rectangle_set is not None: 102 | self.outline_region(img, self.IV_atk_rect, (255, 0, 0)) 103 | self.outline_region(img, self.battle_symbol_rect, (0, 255, 0)) 104 | self.outline_region(img, self.text_rect, (0, 0, 255)) 105 | 106 | return img 107 | 108 | def display_results(self, log: bool = False, screenshot: bool = False): 109 | """Display video from the Switch alongside some annotations describing 110 | the run sequence. 111 | """ 112 | 113 | # Construct the dictionary that will be displayed by the base method. 114 | for key, value in { 115 | 'Resets': self.resets, 116 | 'Checks for IV': self.checks_for_IV, 117 | 'Current balls thrown': self.current_balls_thrown, 118 | 'Total balls thrown': self.total_balls_thrown, 119 | }.items(): 120 | self.info[key] = value 121 | 122 | # Call the base display method. 123 | super().display_results( 124 | image=self.get_frame( 125 | rectangle_set=self.stage, resize=True), log=log, 126 | screenshot=screenshot) 127 | 128 | 129 | def initialize(ctrlr) -> None: 130 | """Executed once at the beginning of the sequence.""" 131 | # assume we're starting from the select controller menu, connect, then 132 | # press home twice to return to the game 133 | ctrlr.push_buttons( 134 | (b'a', 2), (b'h', 2.0), (b'h', 2.0), (b'b', 1.5), (b'b', 1.5) 135 | ) 136 | return 'loop' 137 | 138 | 139 | def loop(ctrlr) -> None: 140 | """Main sequence that is repeated once per cycle.""" 141 | ctrlr.push_buttons( 142 | (b'a', 1), (b'a', 1), (b'a', 1) 143 | ) 144 | 145 | start_time = time.time() 146 | while(True): 147 | img = ctrlr.get_frame() 148 | fight_menu_image = ctrlr.get_image_slice(img, ctrlr.battle_symbol_rect) 149 | fight_match = ctrlr.match_template( 150 | fight_menu_image, ctrlr.misc_icons['fight'])[0] 151 | if fight_match >= 0.85: 152 | ctrlr.log( 153 | 'Detected "Fight" symbol with match value of ' 154 | f'{fight_match:.3f}.', 'DEBUG') 155 | 156 | # Throw a ball and then reset the timer 157 | ctrlr.log('Throw a ball.') 158 | ctrlr.current_balls_thrown += 1 159 | ctrlr.total_balls_thrown += 1 160 | ctrlr.push_buttons( 161 | (b'x', 1), (b'a', 1) 162 | ) 163 | start_time = time.time() 164 | elif 'avez attrapé' in ctrlr.read_text(img, ctrlr.text_rect): 165 | # pokemon was caught, now check it's IV 166 | break 167 | elif ('est K.O.' in ctrlr.read_text(img, ctrlr.text_rect)) or (time.time() - start_time > 30): 168 | # if more than 30s passed, consider you need to reset (program locked itself, your pokemon died, etc) 169 | ctrlr.resets += 1 170 | 171 | ctrlr.log(f'Program stall for 30s. Moving to reset {ctrlr.resets}.') 172 | ctrlr.current_balls_thrown = 0 173 | ctrlr.push_buttons( 174 | (b'h', 3), (b'x', 1), (b'a', 3), (b'a', 1), (b'a', 20), (b'a', 4) 175 | ) 176 | return 'loop' 177 | 178 | for __ in range(35): 179 | ctrlr.push_button(b'b', 1) 180 | 181 | # Navigate to the IV summary 182 | ctrlr.push_buttons( 183 | (b'x', 1.5), (b'>', 0.5), (b'a', 2), 184 | (b'r', 3 + VIDEO_EXTRA_DELAY)) 185 | 186 | # Check the Atk IV and quit if it's 0 ("No good") 187 | IV_text = ctrlr.read_text(ctrlr.get_frame(), ctrlr.IV_atk_rect) 188 | if 'pastop' in IV_text.lower().replace(' ', ''): 189 | ctrlr.log('********** Found 0 Atk target! **********') 190 | return None 191 | ctrlr.checks_for_IV += 1 192 | ctrlr.current_balls_thrown = 0 193 | ctrlr.log(f'IV text detected as {IV_text}. It was checked {ctrlr.checks_for_IV} times.') 194 | 195 | # Otherwise, reset the game and try again. 196 | ctrlr.push_buttons( 197 | (b'h', 3), (b'x', 1), (b'a', 3), (b'a', 1), (b'a', 20), (b'a', 4) 198 | ) 199 | return 'loop' 200 | 201 | 202 | def main(log_name: str) -> None: 203 | """Entry point for the sequence.""" 204 | 205 | actions = {'initialize': initialize, 'loop': loop} 206 | 207 | controller = RegisController(config, log_name, actions) 208 | 209 | controller.event_loop() 210 | 211 | 212 | def exception_handler(exception_type, exception_value, exception_traceback): 213 | """Exception hook to ensure exceptions get logged.""" 214 | logger.error( 215 | 'Exception occurred:', 216 | exc_info=(exception_type, exception_value, exception_traceback) 217 | ) 218 | 219 | 220 | if __name__ == '__main__': 221 | # Set up the logger 222 | 223 | # Configure the logger. 224 | logger = logging.getLogger(LOG_NAME) 225 | logger.setLevel(logging.DEBUG if ENABLE_DEBUG_LOGS else logging.INFO) 226 | formatter = logging.Formatter( 227 | '%(asctime)s | %(levelname)s: %(message)s' 228 | ) 229 | 230 | # make the console formatter easier to read with fewer bits of info 231 | console_formatter = logging.Formatter( 232 | "%(asctime)s | %(levelname)s: %(message)s", "%H:%M:%S" 233 | ) 234 | 235 | # Configure the console, which will print logged information. 236 | console = logging.StreamHandler() 237 | console.setLevel(logging.DEBUG if ENABLE_DEBUG_LOGS else logging.INFO) 238 | console.setFormatter(console_formatter) 239 | 240 | # Configure the file handler, which will save logged information. 241 | fileHandler = logging.FileHandler( 242 | filename=os.path.join(base_dir, 'logs', LOG_NAME + '.log'), 243 | encoding="UTF-8" 244 | ) 245 | fileHandler.setFormatter(formatter) 246 | fileHandler.setLevel(logging.DEBUG if ENABLE_DEBUG_LOGS else logging.INFO) 247 | 248 | # Add the handlers to the logger so that it will both print messages to 249 | # the console as well as save them to a log file. 250 | logger.addHandler(console) 251 | logger.addHandler(fileHandler) 252 | logger.info('Starting new series: %s.', LOG_NAME) 253 | 254 | # Call main 255 | sys.excepthook = exception_handler 256 | main(LOG_NAME) 257 | -------------------------------------------------------------------------------- /scripts/score_pokemon.py: -------------------------------------------------------------------------------- 1 | """Score Pokemon script. Takes stored rental and boss Pokemon and generates 2 | scores for the rental Pokemon. 3 | """ 4 | 5 | # Score Pokemon 6 | # Eric Donders 7 | # Multiprocessing and logging implemented with help from denvoros. 8 | # 2021-01-15 9 | 10 | import logging 11 | import logging.handlers 12 | import multiprocessing as mp 13 | import os 14 | import sys 15 | import time 16 | 17 | from os.path import dirname, abspath 18 | 19 | import jsonpickle 20 | 21 | # We need to import some things from the parent directory. 22 | base_dir = dirname(dirname(abspath(__file__))) 23 | sys.path.insert(1, base_dir) 24 | sys.path.insert(1, base_dir + '\\automaxlair') 25 | 26 | # Needs to be lower than path insert. 27 | from automaxlair import matchup_scoring # noqa: E402 28 | 29 | 30 | # Config values for the log and multiprocessing. 31 | LOG_NAME = 'packagePokemon' 32 | ENABLE_DEBUG_LOGS = True 33 | MAX_NUM_THREADS = mp.cpu_count() 34 | 35 | 36 | def compute_scores(attacker): 37 | """Computes the scores of a single rental Pokemon against all other rental 38 | Pokemon and all boss Pokemon. Called by the multiprocessing pool. 39 | Parameters: 40 | attacker (Pokemon): the rental Pokemon to be scored. 41 | """ 42 | 43 | logger = logging.getLogger(LOG_NAME) 44 | 45 | attacker_id = attacker.name_id 46 | 47 | # Load a separate dictionary for each process because elements get popped 48 | # and re-added during the matchup scoring process. 49 | with open( 50 | base_dir + '/data/rental_pokemon.json', 'r', encoding='utf8' 51 | ) as file: 52 | rental_pokemon = jsonpickle.decode(file.read()) 53 | with open( 54 | base_dir + '/data/boss_pokemon.json', 'r', encoding='utf8' 55 | ) as file: 56 | boss_pokemon = jsonpickle.decode(file.read()) 57 | 58 | # First iterate through all boss Pokemon and score the interactions. 59 | boss_matchups = {} 60 | for defender_id in tuple(boss_pokemon): 61 | defender = boss_pokemon[defender_id] 62 | score = matchup_scoring.evaluate_matchup( 63 | attacker, defender, rental_pokemon.values() 64 | ) 65 | boss_matchups[defender_id] = score 66 | logger.debug( 67 | f'Matchup between {attacker_id} and {defender_id}: {score:.2f}' 68 | ) 69 | 70 | # Then, iterate through all rental Pokemon and score the interactions. 71 | rental_matchups = {} 72 | rental_score = 0 73 | for defender_id in tuple(rental_pokemon): 74 | defender = rental_pokemon[defender_id] 75 | score = matchup_scoring.evaluate_matchup( 76 | attacker, defender, rental_pokemon.values() 77 | ) 78 | rental_matchups[defender_id] = score 79 | # We sum the attacker's score which will later be normalized. 80 | rental_score += score 81 | logger.debug( 82 | f'Matchup between {attacker_id} and {defender_id}: {score:.2f}' 83 | ) 84 | rental_score /= len(rental_pokemon) 85 | logger.debug(f'Score for {attacker_id}: {rental_score:.3f}') 86 | 87 | # Return the results as a tuple which will be unpacked and repacked in 88 | # dicts later, with the first element (the Pokemon's name identifier) as 89 | # the key and the other elements as values in their respective dicts. 90 | logger.info('Finished computing matchups for %s', attacker) 91 | return (attacker_id, boss_matchups, rental_matchups, rental_score) 92 | 93 | 94 | def worker_init(q): 95 | """Basic function that initializes the thread workers to know where to send 96 | logs to. 97 | Parameters: 98 | q (multiprocessing.Queue): The queue object used for multiprocessing. 99 | """ 100 | 101 | # All records from worker processes go to qh and then into q. 102 | qh = logging.handlers.QueueHandler(q) 103 | logger = logging.getLogger() 104 | logger.setLevel(logging.DEBUG if ENABLE_DEBUG_LOGS else logging.INFO) 105 | logger.addHandler(qh) 106 | 107 | 108 | def main(): 109 | """Main function that loads the Pokemon data files, uses a multiprocessing 110 | pool to compute their matchups, and saves the results. 111 | Parameters: 112 | q (multiprocessing.Queue): The queue object used for multiprocessing. 113 | 114 | """ 115 | 116 | # Configure the logger. 117 | logger = logging.getLogger(LOG_NAME) 118 | logger.setLevel(logging.DEBUG if ENABLE_DEBUG_LOGS else logging.INFO) 119 | formatter = logging.Formatter( 120 | '%(asctime)s | %(name)s | %(levelname)s: %(message)s' 121 | ) 122 | 123 | # Configure the console, which will print logged information. 124 | console = logging.StreamHandler() 125 | console.setLevel(logging.INFO) 126 | console.setFormatter(formatter) 127 | 128 | # Configure the file handler, which will save logged information. 129 | fileHandler = logging.handlers.TimedRotatingFileHandler( 130 | filename=os.path.join(base_dir, 'logs', 'packagePokemonScript.log'), 131 | when='midnight', 132 | backupCount=30 133 | ) 134 | fileHandler.setFormatter(formatter) 135 | fileHandler.setLevel(logging.DEBUG if ENABLE_DEBUG_LOGS else logging.INFO) 136 | 137 | # Add the handlers to the logger so that it will both print messages to the 138 | # console as well as save them to a log file. 139 | logger.addHandler(console) 140 | logger.addHandler(fileHandler) 141 | 142 | # Configure the queue and listener that will receive information to be 143 | # logged from all of the processes. 144 | q = mp.Queue() 145 | ql = logging.handlers.QueueListener(q, console, fileHandler) 146 | ql.start() 147 | 148 | logger.info('Started scoring Pokemon.') 149 | 150 | with open( 151 | base_dir + '/data/rental_pokemon.json', 'r', encoding='utf8' 152 | ) as file: 153 | rental_pokemon = jsonpickle.decode(file.read()) 154 | 155 | # Iterate through all rental Pokemon and calculate scores against all the 156 | # other rental Pokemon and all boss Pokemon. Also calculate an average 157 | # score against other rental Pokemon which is helpful for picking 158 | # generalists at the start of a new run. 159 | pool = mp.Pool(MAX_NUM_THREADS, worker_init, [q]) 160 | results_dump = pool.imap(compute_scores, rental_pokemon.values()) 161 | pool.close() 162 | pool.join() 163 | 164 | # Unpack the computed results and repack in dicts which will be pickled. 165 | rental_matchup_LUT = {} 166 | boss_matchup_LUT = {} 167 | rental_pokemon_scores = {} 168 | total_score = 0 169 | for name_id, boss_matchups, rental_matchups, rental_score in results_dump: 170 | boss_matchup_LUT[name_id] = boss_matchups 171 | rental_matchup_LUT[name_id] = rental_matchups 172 | rental_pokemon_scores[name_id] = rental_score 173 | total_score += rental_score 174 | 175 | ql.stop() 176 | 177 | # Pickle the score lookup tables for later use. 178 | with open( 179 | base_dir + '/data/boss_matchup_LUT.json', 'w', encoding='utf8' 180 | ) as file: 181 | file.write(jsonpickle.encode(boss_matchup_LUT, indent=4)) 182 | with open( 183 | base_dir + '/data/rental_matchup_LUT.json', 'w', encoding='utf8' 184 | ) as file: 185 | file.write(jsonpickle.encode(rental_matchup_LUT, indent=4)) 186 | with open( 187 | base_dir + '/data/rental_pokemon_scores.json', 'w', encoding='utf8' 188 | ) as file: 189 | file.write(jsonpickle.encode(rental_pokemon_scores, indent=4)) 190 | 191 | 192 | if __name__ == '__main__': 193 | # Call main, then clean up. 194 | start_time = time.time() 195 | main() 196 | end_time = time.time() 197 | logger = logging.getLogger(LOG_NAME) 198 | logger.info(f'Finished scoring Pokemon, taking {end_time - start_time} s.') 199 | --------------------------------------------------------------------------------