Skip to content

Commit 6c3f017

Browse files
committed
2 parents d61a360 + b5da1e7 commit 6c3f017

File tree

8 files changed

+452
-3
lines changed

8 files changed

+452
-3
lines changed

SerialPrograms/Scripts/check_detector_regions.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@
3333
raw_image = image.copy()
3434

3535

36+
# ==================================================================
37+
# LZA selection arrow
38+
# add_infer_box_to_image(raw_image, 0.654308, 0.481553, 0.295529, 0.312621, image) # restaurant farmer
39+
add_infer_box_to_image(raw_image, 0.543, 0.508, 0.365, 0.253, image) # Jacinthe farmer
40+
3641
# ==================================================================
3742
# LZA main menu detector
3843
add_infer_box_to_image(raw_image, 0.87, 0.940, 0.077, 0.044, image) # detect button B

SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
// Farming
1818
#include "Programs/Farming/PokemonLZA_RestaurantFarmer.h"
19+
#include "Programs/Farming/PokemonLZA_JacintheInfiniteFarmer.h"
1920
#include "Programs/Farming/PokemonLZA_MegaShardFarmer.h"
2021

2122
// Shiny Hunting
@@ -54,6 +55,7 @@ std::vector<PanelEntry> PanelListFactory::make_panels() const{
5455
ret.emplace_back(make_single_switch_program<RestaurantFarmer_Descriptor, RestaurantFarmer>());
5556
ret.emplace_back(make_single_switch_program<MegaShardFarmer_Descriptor, MegaShardFarmer>());
5657
if (PreloadSettings::instance().DEVELOPER_MODE){
58+
ret.emplace_back(make_single_switch_program<JacintheInfiniteFarmer_Descriptor, JacintheInfiniteFarmer>());
5759
}
5860

5961
ret.emplace_back("---- Shiny Hunting ----");
Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
/* Jacinthe Infinite Farmer
2+
*
3+
* From: https://github.com/PokemonAutomation/
4+
*
5+
*/
6+
7+
#include "CommonFramework/Logging/Logger.h"
8+
#include "CommonFramework/Exceptions/OperationFailedException.h"
9+
#include "CommonFramework/ProgramStats/StatsTracking.h"
10+
#include "CommonFramework/Tools/ErrorDumper.h"
11+
#include "CommonTools/Async/InterruptableCommands.h"
12+
#include "CommonTools/Async/InferenceRoutines.h"
13+
#include "CommonTools/VisualDetectors/BlackScreenDetector.h"
14+
#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h"
15+
#include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h"
16+
#include "Pokemon/Pokemon_Strings.h"
17+
#include "PokemonLZA/Inference/PokemonLZA_SelectionArrowDetector.h"
18+
#include "PokemonLZA/Inference/PokemonLZA_DialogDetector.h"
19+
#include "PokemonLZA/Inference/PokemonLZA_ButtonDetector.h"
20+
#include "PokemonLZA/Inference/PokemonLZA_MoveEffectivenessSymbol.h"
21+
#include "PokemonLZA/Programs/PokemonLZA_TrainerBattle.h"
22+
#include "PokemonLZA_JacintheInfiniteFarmer.h"
23+
#include "CommonFramework/Notifications/ProgramNotifications.h"
24+
25+
namespace PokemonAutomation{
26+
namespace NintendoSwitch{
27+
namespace PokemonLZA{
28+
29+
using namespace Pokemon;
30+
31+
32+
// Jacinthe infinite battle flow:
33+
// - Button A overworld
34+
// - Flat white dialog x 2
35+
// - Selection arrow for three menuitem: "give a try", "can you give me spiel again", "I'll pass"
36+
// - Flat white excited dialog
37+
// - Flat white dialog
38+
// - Black screen
39+
// - Enter pre-battle animation
40+
// - Pre-battle dialog (no background, white arrow)
41+
// - Battle
42+
// - Aurorus uses protect first turn
43+
// - Mega evolve Clefable
44+
// - Clefable uses protect first turn
45+
// - Given +8100 poke dollar
46+
// - Flat white dialog
47+
// - Black screen
48+
// - Flat white dialog
49+
// - Selection arrow for two item: "let's start", "no thanks"
50+
// - If choose let's start:
51+
// - Flat white dialog
52+
// - Pre-battle dialog
53+
// - <loop> ...
54+
// - If choose no thanks:
55+
// - Flat white dialg
56+
// - Selection arrow for two item: "let's keep going", "I'm done"
57+
// - choose 2nd
58+
// - Flat white dialog x3
59+
// - Button A overworld <finish battle>
60+
61+
62+
JacintheInfiniteFarmer_Descriptor::JacintheInfiniteFarmer_Descriptor()
63+
: SingleSwitchProgramDescriptor(
64+
"PokemonLZA:JacintheInfiniteFarmer",
65+
STRING_POKEMON + " LZA", "Jacinthe Infinite Farmer",
66+
"Programs/PokemonLZA/JacintheInfiniteFarmer.html",
67+
"Farm Jacinthe Infinite for exp and money.",
68+
ProgramControllerClass::StandardController_NoRestrictions,
69+
FeedbackType::REQUIRED,
70+
AllowCommandsWhenRunning::DISABLE_COMMANDS,
71+
{}
72+
)
73+
{}
74+
class JacintheInfiniteFarmer_Descriptor::Stats : public StatsTracker{
75+
public:
76+
Stats()
77+
: rounds(m_stats["Rounds"])
78+
, errors(m_stats["Errors"])
79+
{
80+
m_display_order.emplace_back("Rounds");
81+
m_display_order.emplace_back("Errors", HIDDEN_IF_ZERO);
82+
83+
m_aliases["Battles"] = "Rounds";
84+
}
85+
86+
std::atomic<uint64_t>& rounds;
87+
std::atomic<uint64_t>& errors;
88+
};
89+
std::unique_ptr<StatsTracker> JacintheInfiniteFarmer_Descriptor::make_stats() const{
90+
return std::unique_ptr<StatsTracker>(new Stats());
91+
}
92+
93+
94+
JacintheInfiniteFarmer::~JacintheInfiniteFarmer(){
95+
STOP_AFTER_CURRENT.remove_listener(*this);
96+
}
97+
98+
JacintheInfiniteFarmer::JacintheInfiniteFarmer()
99+
: m_stop_after_current(false)
100+
, NUM_ROUNDS(
101+
"<b>Number of Rounds to Run:</b><br>"
102+
"Zero will run until 'Stop after Current Round' is pressed or the program is manually stopped.</b>",
103+
LockMode::UNLOCK_WHILE_RUNNING,
104+
100,
105+
0
106+
)
107+
, GO_HOME_WHEN_DONE(false)
108+
, MOVE_AI(
109+
"<b>Move Selection AI:</b><br>"
110+
"If enabled, it will be smarter with move selection.<br>"
111+
"However, this adds a split-second delay which may cause opponent attacks to land first.",
112+
LockMode::UNLOCK_WHILE_RUNNING,
113+
true
114+
)
115+
, USE_PLUS_MOVES(
116+
"<b>Use Plus Moves:</b><br>"
117+
"If enabled, it will attempt to use plus moves.<br>"
118+
"However, this adds a 320ms delay which may cause opponent attacks to land first.",
119+
LockMode::UNLOCK_WHILE_RUNNING,
120+
false
121+
)
122+
, NOTIFICATION_STATUS_UPDATE("Status Update", true, false, std::chrono::seconds(3600))
123+
, NOTIFICATIONS({
124+
&NOTIFICATION_STATUS_UPDATE,
125+
&NOTIFICATION_PROGRAM_FINISH,
126+
&NOTIFICATION_ERROR_FATAL,
127+
})
128+
{
129+
PA_ADD_OPTION(STOP_AFTER_CURRENT);
130+
PA_ADD_OPTION(MOVE_AI);
131+
PA_ADD_OPTION(USE_PLUS_MOVES);
132+
133+
PA_ADD_OPTION(NUM_ROUNDS);
134+
PA_ADD_OPTION(GO_HOME_WHEN_DONE);
135+
PA_ADD_OPTION(NOTIFICATIONS);
136+
137+
STOP_AFTER_CURRENT.set_idle();
138+
STOP_AFTER_CURRENT.add_listener(*this);
139+
}
140+
141+
142+
143+
JacintheInfiniteFarmer::StopButton::StopButton()
144+
: ButtonOption(
145+
"<b>Stop after current round:",
146+
"Stop after current round",
147+
0, 16
148+
)
149+
{}
150+
void JacintheInfiniteFarmer::StopButton::set_idle(){
151+
this->set_enabled(false);
152+
this->set_text("Stop after Current Round");
153+
}
154+
void JacintheInfiniteFarmer::StopButton::set_ready(){
155+
this->set_enabled(true);
156+
this->set_text("Stop after Current Round");
157+
}
158+
void JacintheInfiniteFarmer::StopButton::set_pressed(){
159+
this->set_enabled(false);
160+
this->set_text("Program will stop after current round...");
161+
}
162+
163+
164+
165+
bool JacintheInfiniteFarmer::talk_to_jacinthe(SingleSwitchProgramEnvironment& env, ProControllerContext& context){
166+
JacintheInfiniteFarmer_Descriptor::Stats& stats = env.current_stats<JacintheInfiniteFarmer_Descriptor::Stats>();
167+
168+
bool seen_selection_arrow = false;
169+
bool confirm_entering_battle = false;
170+
while (true){
171+
context.wait_for_all_requests();
172+
173+
ButtonWatcher buttonA(
174+
COLOR_RED,
175+
ButtonType::ButtonA,
176+
{0.1, 0.1, 0.8, 0.8},
177+
&env.console.overlay()
178+
);
179+
SelectionArrowWatcher arrow(
180+
COLOR_YELLOW, &env.console.overlay(),
181+
SelectionArrowType::RIGHT,
182+
{0.543, 0.508, 0.365, 0.253}
183+
);
184+
FlatWhiteDialogWatcher dialog(COLOR_RED, &env.console.overlay());
185+
BlackScreenWatcher black_screen(COLOR_BLACK);
186+
187+
std::vector<PeriodicInferenceCallback> callbacks = {
188+
buttonA,
189+
arrow,
190+
dialog
191+
};
192+
if (seen_selection_arrow && confirm_entering_battle){
193+
// seen selection arrow means we may now enter battle, which transitions with a black screen
194+
callbacks.push_back(black_screen);
195+
}
196+
197+
int ret = wait_until(
198+
env.console, context,
199+
10000ms,
200+
callbacks
201+
);
202+
context.wait_for(100ms);
203+
204+
switch (ret){
205+
case 0:
206+
env.log("Detected A button.");
207+
env.console.overlay().add_log("Button A Detected");
208+
if (m_stop_after_current.load(std::memory_order_relaxed)){
209+
return true;
210+
}
211+
212+
pbf_press_button(context, BUTTON_A, 160ms, 80ms);
213+
continue;
214+
215+
case 1:
216+
env.log("Detected selection arrow.");
217+
seen_selection_arrow = true;
218+
// This is when Jacinthe is asking whether you want
219+
// to start the battle or continue the battle
220+
if (m_stop_after_current.load(std::memory_order_relaxed)){
221+
env.console.overlay().add_log("Selection Arrow: Cancel");
222+
pbf_press_button(context, BUTTON_B, 160ms, 80ms);
223+
continue;
224+
} else{
225+
confirm_entering_battle = true;
226+
// confirm entering battle
227+
env.console.overlay().add_log("Selection Arrow: Confirm");
228+
pbf_press_button(context, BUTTON_A, 160ms, 80ms);
229+
}
230+
231+
case 2:
232+
env.log("Detected white dialog.");
233+
env.console.overlay().add_log("Advance Dialog");
234+
pbf_press_button(context, BUTTON_B, 160ms, 80ms);
235+
continue;
236+
237+
case 3:
238+
env.log("Detected black screen.");
239+
env.console.overlay().add_log("Transition to Battle");
240+
// battle starts
241+
return false;
242+
243+
default:
244+
stats.errors++;
245+
env.update_stats();
246+
OperationFailedException::fire(
247+
ErrorReport::SEND_ERROR_REPORT,
248+
"talk_to_jacinthe(): No recognized state after 60 seconds.",
249+
env.console
250+
);
251+
}
252+
}
253+
}
254+
void JacintheInfiniteFarmer::run_round(SingleSwitchProgramEnvironment& env, ProControllerContext& context){
255+
JacintheInfiniteFarmer_Descriptor::Stats& stats = env.current_stats<JacintheInfiniteFarmer_Descriptor::Stats>();
256+
257+
WallClock start = current_time();
258+
259+
while (true){
260+
context.wait_for_all_requests();
261+
262+
FlatWhiteDialogWatcher dialog(COLOR_RED, &env.console.overlay(), 250ms);
263+
264+
int ret = run_until<ProControllerContext>(
265+
env.console, context,
266+
[&](ProControllerContext& context){
267+
while (current_time() - start < 30min){
268+
attempt_one_attack(env, context, MOVE_AI, USE_PLUS_MOVES);
269+
}
270+
},
271+
{
272+
dialog,
273+
}
274+
);
275+
276+
switch (ret){
277+
case 0:
278+
env.log("Detected white dialog.");
279+
env.console.overlay().add_log("Post-Battle Dialog");
280+
pbf_press_button(context, BUTTON_B, 160ms, 80ms);
281+
stats.rounds++;
282+
env.update_stats();
283+
return;
284+
285+
default:
286+
stats.errors++;
287+
env.update_stats();
288+
OperationFailedException::fire(
289+
ErrorReport::SEND_ERROR_REPORT,
290+
"Round took longer than 30 minutes.",
291+
env.console
292+
);
293+
}
294+
}
295+
}
296+
297+
298+
class JacintheInfiniteFarmer::ResetOnExit{
299+
public:
300+
ResetOnExit(StopButton& button)
301+
: m_button(button)
302+
{}
303+
~ResetOnExit(){
304+
m_button.set_idle();
305+
}
306+
307+
private:
308+
StopButton& m_button;
309+
};
310+
311+
void JacintheInfiniteFarmer::on_press(){
312+
global_logger_tagged().log("Stop after current requested...");
313+
m_stop_after_current.store(true, std::memory_order_relaxed);
314+
STOP_AFTER_CURRENT.set_pressed();
315+
}
316+
317+
void JacintheInfiniteFarmer::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){
318+
JacintheInfiniteFarmer_Descriptor::Stats& stats = env.current_stats<JacintheInfiniteFarmer_Descriptor::Stats>();
319+
m_stop_after_current.store(false, std::memory_order_relaxed);
320+
STOP_AFTER_CURRENT.set_ready();
321+
ResetOnExit reset_button_on_exit(STOP_AFTER_CURRENT);
322+
pbf_mash_button(context, BUTTON_B, 1000ms);
323+
324+
// auto lobby = env.console.video().snapshot();
325+
326+
while (true){
327+
send_program_status_notification(env, NOTIFICATION_STATUS_UPDATE);
328+
if (NUM_ROUNDS != 0 && stats.rounds >= NUM_ROUNDS) {
329+
m_stop_after_current.store(true, std::memory_order_relaxed);
330+
STOP_AFTER_CURRENT.set_pressed();
331+
}
332+
if (talk_to_jacinthe(env, context)){
333+
break;
334+
}
335+
run_round(env, context);
336+
}
337+
338+
send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH);
339+
GO_HOME_WHEN_DONE.run_end_of_program(context);
340+
}
341+
342+
343+
344+
}
345+
}
346+
}

0 commit comments

Comments
 (0)