├── .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 |
--------------------------------------------------------------------------------