Skip to content

Commit cd5cece

Browse files
author
Gin
committed
Add Wild Zone Cafe
1 parent 0aecbf0 commit cd5cece

File tree

5 files changed

+398
-1
lines changed

5 files changed

+398
-1
lines changed

SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "Programs/ShinyHunting/PokemonLZA_ShinyHunt_OverworldReset.h"
3232
#include "Programs/ShinyHunting/PokemonLZA_BeldumHunter.h"
3333
#include "Programs/ShinyHunting/PokemonLZA_WildZoneEntrance.h"
34+
#include "Programs/ShinyHunting/PokemonLZA_WildZoneCafe.h"
3435
#include "Programs/ShinyHunting/PokemonLZA_ShinyHunt_FlySpotReset.h"
3536
#include "Programs/ShinyHunting/PokemonLZA_ShuttleRun.h"
3637

@@ -82,6 +83,7 @@ std::vector<PanelEntry> PanelListFactory::make_panels() const{
8283
ret.emplace_back(make_single_switch_program<ShinyHunt_OverworldReset_Descriptor, ShinyHunt_OverworldReset>());
8384
ret.emplace_back(make_single_switch_program<ShinyHunt_WildZoneEntrance_Descriptor, ShinyHunt_WildZoneEntrance>());
8485
if (IS_BETA_VERSION){
86+
ret.emplace_back(make_single_switch_program<ShinyHunt_WildZoneCafe_Descriptor, ShinyHunt_WildZoneCafe>());
8587
ret.emplace_back(make_single_switch_program<ShinyHunt_FlySpotReset_Descriptor, ShinyHunt_FlySpotReset>());
8688
ret.emplace_back(make_single_switch_program<ShinyHunt_ShuttleRun_Descriptor, ShinyHunt_ShuttleRun>());
8789
}

SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_Locations.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ enum class WildZone{
3434
WILD_ZONE_20,
3535
};
3636

37+
enum class WildZoneCafe{
38+
CAFE_BATAILLE,
39+
CAFE_ULTIMO,
40+
};
41+
42+
3743

3844
}
3945
}
Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
/* Shiny Hunt - Wild Zone Cafe
2+
*
3+
* From: https://github.com/PokemonAutomation/
4+
*
5+
*/
6+
7+
#include "Common/Cpp/PrettyPrint.h"
8+
#include "Common/Cpp/Time.h"
9+
#include "CommonFramework/Exceptions/OperationFailedException.h"
10+
#include "CommonFramework/GlobalSettingsPanel.h"
11+
#include "CommonFramework/ProgramStats/StatsTracking.h"
12+
#include "CommonFramework/Notifications/ProgramNotifications.h"
13+
#include "CommonFramework/Globals.h"
14+
#include "CommonFramework/VideoPipeline/VideoFeed.h"
15+
#include "CommonTools/Async/InferenceRoutines.h"
16+
#include "CommonTools/StartupChecks/VideoResolutionCheck.h"
17+
#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h"
18+
#include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h"
19+
#include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h"
20+
#include "Pokemon/Pokemon_Strings.h"
21+
#include "PokemonLA/Inference/Sounds/PokemonLA_ShinySoundDetector.h"
22+
#include "PokemonLZA/Inference/PokemonLZA_AlertEyeDetector.h"
23+
#include "PokemonLZA/Inference/PokemonLZA_ButtonDetector.h"
24+
#include "PokemonLZA/Inference/PokemonLZA_OverworldPartySelectionDetector.h"
25+
#include "PokemonLZA/Programs/PokemonLZA_BasicNavigation.h"
26+
#include "PokemonLZA/Programs/PokemonLZA_GameEntry.h"
27+
#include "PokemonLZA_WildZoneCafe.h"
28+
29+
namespace PokemonAutomation::NintendoSwitch::PokemonLZA {
30+
31+
using namespace Pokemon;
32+
33+
34+
ShinyHunt_WildZoneCafe_Descriptor::ShinyHunt_WildZoneCafe_Descriptor()
35+
: SingleSwitchProgramDescriptor(
36+
"PokemonLZA:ShinyHunt-WildZoneCafe", STRING_POKEMON + " LZA",
37+
"Wild Zone Cafe",
38+
"Programs/PokemonLZA/ShinyHunt-WildZoneCafe.html",
39+
"Shiny hunt by repeatedly entering Wild Zone cafe.",
40+
ProgramControllerClass::StandardController_NoRestrictions, FeedbackType::REQUIRED,
41+
AllowCommandsWhenRunning::DISABLE_COMMANDS, {}
42+
)
43+
{}
44+
45+
class ShinyHunt_WildZoneCafe_Descriptor::Stats : public StatsTracker{
46+
public:
47+
Stats()
48+
: visits(m_stats["Visits"])
49+
, chased(m_stats["Chased"])
50+
, shinies(m_stats["Shiny Sounds"])
51+
, game_resets(m_stats["Game Resets"])
52+
, errors(m_stats["Errors"])
53+
{
54+
m_display_order.emplace_back("Visits");
55+
m_display_order.emplace_back("Chased");
56+
m_display_order.emplace_back("Shiny Sounds");
57+
m_display_order.emplace_back("Game Resets");
58+
m_display_order.emplace_back("Errors", HIDDEN_IF_ZERO);
59+
}
60+
61+
std::atomic<uint64_t>& visits;
62+
std::atomic<uint64_t>& chased;
63+
std::atomic<uint64_t>& shinies;
64+
std::atomic<uint64_t>& game_resets;
65+
std::atomic<uint64_t>& errors;
66+
};
67+
68+
std::unique_ptr<StatsTracker> ShinyHunt_WildZoneCafe_Descriptor::make_stats() const{
69+
return std::unique_ptr<StatsTracker>(new Stats());
70+
}
71+
72+
73+
ShinyHunt_WildZoneCafe::ShinyHunt_WildZoneCafe()
74+
: CAFE(
75+
"<b>Caf\u00e9:</b>",
76+
{
77+
{WildZoneCafe::CAFE_BATAILLE, "cafe-bataille", "Wild Zone 6 - Caf\u00e9 Bataille"},
78+
{WildZoneCafe::CAFE_ULTIMO, "cafe-ultimo", "Wild Zone 15 - Caf\u00e9 Ultimo"},
79+
},
80+
LockMode::LOCK_WHILE_RUNNING,
81+
WildZoneCafe::CAFE_BATAILLE
82+
)
83+
, SHINY_DETECTED("Shiny Detected", "", "2000 ms", ShinySoundDetectedAction::NOTIFY_ON_FIRST_ONLY)
84+
, NOTIFICATION_STATUS("Status Update", true, false, std::chrono::seconds(3600))
85+
, NOTIFICATIONS({
86+
&NOTIFICATION_STATUS,
87+
&SHINY_DETECTED.NOTIFICATIONS,
88+
&NOTIFICATION_PROGRAM_FINISH,
89+
&NOTIFICATION_ERROR_RECOVERABLE,
90+
&NOTIFICATION_ERROR_FATAL,
91+
})
92+
{
93+
PA_ADD_STATIC(SHINY_REQUIRES_AUDIO);
94+
PA_ADD_OPTION(CAFE);
95+
PA_ADD_OPTION(SHINY_DETECTED);
96+
PA_ADD_OPTION(NOTIFICATIONS);
97+
}
98+
99+
100+
void do_one_cafe_trip(
101+
SingleSwitchProgramEnvironment& env,
102+
ProControllerContext& context,
103+
WildZoneCafe cafe,
104+
ShinySoundHandler& shiny_sound_handler,
105+
bool to_zoom_to_max
106+
){
107+
env.log("Starting one cafe trip");
108+
ShinyHunt_WildZoneCafe_Descriptor::Stats& stats = env.current_stats<ShinyHunt_WildZoneCafe_Descriptor::Stats>();
109+
context.wait_for_all_requests();
110+
shiny_sound_handler.process_pending(context);
111+
112+
AlertEyeDetector alert_eye_detector(COLOR_WHITE, &env.console.overlay());
113+
auto latest_frame = env.console.video().snapshot_latest_blocking();
114+
// if there is the alert symbol of a white eye telling player they are being chased by wild pokmeon
115+
const bool has_alert_eye = latest_frame ? alert_eye_detector.detect(latest_frame): false;
116+
117+
if (!has_alert_eye){
118+
// we are not being chased by wild pokemon. Try to fast travel back to entrance.
119+
120+
// Open map is robust against day/night change. So after open_map()
121+
// we are sure we are in map view
122+
bool can_fast_travel = open_map(env.console, context, to_zoom_to_max);
123+
to_zoom_to_max = false;
124+
125+
if (can_fast_travel){
126+
pbf_wait(context, 300ms);
127+
switch(cafe){
128+
case WildZoneCafe::CAFE_BATAILLE:
129+
env.log("Move to Cafe Bataille icon");
130+
pbf_move_left_joystick(context, 148, 20, 100ms, 0ms);
131+
break;
132+
case WildZoneCafe::CAFE_ULTIMO:
133+
env.log("Move to Cafe Ultimo icon");
134+
pbf_move_left_joystick(context, 50, 100, 100ms, 0ms);
135+
break;
136+
}
137+
pbf_wait(context, 300ms);
138+
FastTravelState travel_status = fly_from_map(env.console, context);
139+
if (travel_status == FastTravelState::SUCCESS){
140+
env.log("Fast travel back to cafe");
141+
return;
142+
} else if (travel_status == FastTravelState::NOT_AT_FLY_SPOT){
143+
stats.errors++;
144+
env.update_stats();
145+
OperationFailedException::fire(
146+
ErrorReport::SEND_ERROR_REPORT,
147+
"do_one_cafe_trip: Cannot fast travel after moving map cursor.",
148+
env.console
149+
);
150+
}
151+
// else: travel_status == FastTravelState::PURSUED
152+
}
153+
}
154+
// we are being chased by wild pokemon
155+
156+
stats.chased++;
157+
env.update_stats();
158+
env.log("Escaping");
159+
env.console.overlay().add_log("Escaping to Entrance");
160+
const double starting_direction = get_facing_direction(env.console, context);
161+
162+
uint8_t move_x = 0, move_y = 0;
163+
switch(cafe){
164+
case WildZoneCafe::CAFE_BATAILLE:
165+
env.log("Move to zone gate from Cafe Bataille");
166+
move_x = 255; move_y = 160;
167+
break;
168+
case WildZoneCafe::CAFE_ULTIMO:
169+
env.log("Move to zone gate from Cafe Ultimo");
170+
move_x = 128; move_y = 255;
171+
break;
172+
}
173+
174+
const ImageFloatBox button_A_box{0.3, 0.2, 0.4, 0.7};
175+
int ret = run_towards_wild_zone_gate(env.console, context, button_A_box, move_x, move_y, Seconds(10));
176+
switch (ret){
177+
case 0: // Found button A. Reached the gate.
178+
break;
179+
case 1: // day night change happend
180+
{
181+
const double cur_direction = get_facing_direction(env.console, context);
182+
const double direction_change = get_angle_between_facing_directions(starting_direction, cur_direction);
183+
env.log("Facing direction difference after day/night change: " + tostr_fixed(direction_change, 0) + " deg, from "
184+
+ tostr_fixed(starting_direction, 0) + " to " + tostr_fixed(cur_direction, 0) + " deg");
185+
186+
if (direction_change > 30){
187+
// we are already facing the gate
188+
move_x = 128; move_y = 0;
189+
env.log("Running forward");
190+
env.console.overlay().add_log("Running Forward");
191+
}
192+
// Running forward or backward depends on character facing to go back to zone entrance
193+
ret = run_towards_wild_zone_gate(env.console, context, button_A_box, move_x, move_y, Seconds(10));
194+
if (ret != 0){
195+
stats.errors++;
196+
env.update_stats();
197+
OperationFailedException::fire(
198+
ErrorReport::SEND_ERROR_REPORT,
199+
"do_one_cafe_trip: Cannot reach wild zone gate after day/night change.",
200+
env.console
201+
);
202+
}
203+
}
204+
break;
205+
default:
206+
stats.errors++;
207+
env.update_stats();
208+
OperationFailedException::fire(
209+
ErrorReport::SEND_ERROR_REPORT,
210+
"do_one_cafe_trip: Cannot reach wild zone gate after being chased by wild pokemon.",
211+
env.console
212+
);
213+
}
214+
shiny_sound_handler.process_pending(context);
215+
216+
// Found button A, so we are at the entrance.
217+
// Mash A to leave Zone.
218+
leave_zone_gate(env.console, context);
219+
shiny_sound_handler.process_pending(context);
220+
221+
// do a fast travel back to cafe
222+
bool can_fast_travel = open_map(env.console, context, to_zoom_to_max);
223+
if (!can_fast_travel){
224+
// cannot fast travel outside zone. Chased by wild pokemon that used Dig?
225+
stats.errors++;
226+
env.update_stats();
227+
OperationFailedException::fire(
228+
ErrorReport::SEND_ERROR_REPORT,
229+
"do_one_cafe_trip: Cannot fast travel outside gate.",
230+
env.console
231+
);
232+
}
233+
234+
// move map cursor to cafe:
235+
pbf_wait(context, 300ms);
236+
switch(cafe){
237+
case WildZoneCafe::CAFE_BATAILLE:
238+
env.log("Move to Cafe Bataille icon from zone gate");
239+
pbf_move_left_joystick(context, 96, 160, 100ms, 0ms);
240+
break;
241+
case WildZoneCafe::CAFE_ULTIMO:
242+
env.log("Move to Cafe Ultimo icon from zone gate");
243+
pbf_move_left_joystick(context, 190, 144, 100ms, 0ms);
244+
break;
245+
}
246+
pbf_wait(context, 300ms);
247+
248+
FastTravelState travel_status = fly_from_map(env.console, context);
249+
if (travel_status != FastTravelState::SUCCESS){
250+
stats.errors++;
251+
env.update_stats();
252+
OperationFailedException::fire(
253+
ErrorReport::SEND_ERROR_REPORT,
254+
"do_one_cafe_trip: Cannot fast travel to cafe.",
255+
env.console
256+
);
257+
}
258+
}
259+
260+
261+
void ShinyHunt_WildZoneCafe::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){
262+
assert_16_9_720p_min(env.logger(), env.console);
263+
264+
// Record when we should zoom out the map for flyable fast travel icon
265+
// detection on map during fast traveling.
266+
bool to_max_zoom_level_on_map = true;
267+
268+
// Mash button B to let Switch register the controller
269+
pbf_mash_button(context, BUTTON_B, 500ms);
270+
271+
ShinyHunt_WildZoneCafe_Descriptor::Stats& stats = env.current_stats<ShinyHunt_WildZoneCafe_Descriptor::Stats>();
272+
273+
ShinySoundHandler shiny_sound_handler(SHINY_DETECTED);
274+
275+
PokemonLA::ShinySoundDetector shiny_detector(env.console, [&](float error_coefficient) -> bool {
276+
// Warning: This callback will be run from a different thread than this function.
277+
stats.shinies++;
278+
env.update_stats();
279+
env.console.overlay().add_log("Shiny Sound Detected!", COLOR_YELLOW);
280+
281+
return shiny_sound_handler.on_shiny_sound(
282+
env, env.console,
283+
stats.shinies,
284+
error_coefficient
285+
);
286+
});
287+
288+
int consecutive_failures = 0;
289+
run_until<ProControllerContext>(
290+
env.console, context,
291+
[&](ProControllerContext& context){
292+
while (true){
293+
try{
294+
do_one_cafe_trip(
295+
env, context,
296+
CAFE, shiny_sound_handler, to_max_zoom_level_on_map
297+
);
298+
// Fast travel auto saves the game. So now the map is fixed at max zoom level.
299+
// We no longer needs to zoom in future.
300+
to_max_zoom_level_on_map = false;
301+
stats.visits++;
302+
env.update_stats();
303+
// No failure. Reset consecutive failure counter.
304+
consecutive_failures = 0;
305+
}catch (OperationFailedException&){
306+
consecutive_failures++;
307+
env.log("Consecutive failures: " + std::to_string(consecutive_failures), COLOR_RED);
308+
if (consecutive_failures >= 3){
309+
if (PreloadSettings::instance().DEVELOPER_MODE && GlobalSettings::instance().SAVE_DEBUG_VIDEOS_ON_SWITCH){
310+
env.log("Saving debug video on Switch...");
311+
env.console.overlay().add_log("Save Debug Video on Switch");
312+
pbf_press_button(context, BUTTON_CAPTURE, 2 * TICKS_PER_SECOND, 0);
313+
context.wait_for_all_requests();
314+
}
315+
go_home(env.console, context); // go Home to preserve game state for debugging
316+
throw;
317+
}
318+
env.log("Error encountered. Resetting...", COLOR_RED);
319+
stats.game_resets++;
320+
stats.errors++;
321+
env.update_stats();
322+
env.console.overlay().add_log("Error Found. Reset Game", COLOR_RED);
323+
go_home(env.console, context);
324+
reset_game_from_home(env, env.console, context);
325+
}
326+
send_program_status_notification(env, NOTIFICATION_STATUS);
327+
}
328+
},
329+
{{shiny_detector}}
330+
);
331+
332+
// Shiny sound detected and user requested stopping the program when
333+
// detected shiny sound.
334+
shiny_sound_handler.process_pending(context);
335+
336+
go_home(env.console, context);
337+
send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH);
338+
}
339+
340+
341+
} // namespace PokemonAutomation::NintendoSwitch::PokemonLZA

0 commit comments

Comments
 (0)