Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@
*
*/

#include <array>
#include <cstddef>
#include <string>
#include "Common/Cpp/Options/ConfigOption.h"
#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h"
#include "CommonFramework/ProgramStats/StatsTracking.h"
#include "CommonFramework/Notifications/ProgramNotifications.h"
#include "CommonFramework/VideoPipeline/VideoFeed.h"
#include "CommonFramework/ImageTools/ImageStats.h"
#include "CommonTools/Async/InferenceRoutines.h"
#include "CommonTools/StartupChecks/VideoResolutionCheck.h"
#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h"
Expand All @@ -16,11 +21,11 @@
#include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h"
#include "Pokemon/Pokemon_Strings.h"
#include "VideoGames/PokemonLZA/Inference/PokemonLZA_ButtonDetector.h"
#include "VideoGames/PokemonLZA/Inference/PokemonLZA_DayNightStateDetector.h"
#include "VideoGames/PokemonLZA/Inference/PokemonLZA_WeatherDetector.h"
#include "PokemonLZA_ShinyHunt_HelioptileHunter.h"
#include "VideoGames/PokemonLZA/Programs/PokemonLZA_BasicNavigation.h"
#include <cstddef>
#include <string>
#include "VideoGames/PokemonLZA/Programs/PokemonLZA_FastTravelNavigation.h"
#include "PokemonLZA_ShinyHunt_HelioptileHunter.h"

namespace PokemonAutomation{
namespace NintendoSwitch{
Expand Down Expand Up @@ -66,11 +71,14 @@ std::unique_ptr<StatsTracker> ShinyHunt_HelioptileHunter_Descriptor::make_stats(
}

ShinyHunt_HelioptileHunter::ShinyHunt_HelioptileHunter()
: END_AFTER_CYCLE("<b>How Many cycles before stopping. 0 for never stop.</b>",
: END_AFTER_CYCLE(
"<b>How many day/night cycles before stopping. 0 for never stop.</b><br>"
"<br>"
"Each day/night cycle is roughly 65 tries.",
LockMode::LOCK_WHILE_RUNNING,
0, 0, 32*30
0, 0
)
, NOTIFICATION_STATUS("Status Update", true, false, ImageAttachmentMode::JPG, {"Notifs"}, std::chrono::seconds(3600))
, NOTIFICATION_STATUS("Status Update", true, false, std::chrono::seconds(3600))
, NOTIFICATIONS({
&NOTIFICATION_STATUS,
&NOTIFICATION_PROGRAM_FINISH,
Expand All @@ -82,51 +90,144 @@ ShinyHunt_HelioptileHunter::ShinyHunt_HelioptileHunter()
PA_ADD_OPTION(NOTIFICATIONS);
}

bool proper_weather(SingleSwitchProgramEnvironment& env, ProControllerContext& context){

open_map(env.console, context, true, true);

WeatherIconDetector sunnyDetector(
WeatherIconType::Sunny,
&env.console.overlay()
);

WeatherIconDetector clearDetector(
WeatherIconType::Clear,
&env.console.overlay()
);
VideoSnapshot screen = env.console.video().snapshot();

return (sunnyDetector.detect(screen) || clearDetector.detect(screen));
}

void bench_loop(SingleSwitchProgramEnvironment& env, ProControllerContext& context, size_t quantity){
for (size_t i = 0; i < quantity; i++){
sit_on_bench(env.console, context);
pbf_move_left_joystick(context, {0, -1}, 500ms, 200ms);
}
}

void find_weather(SingleSwitchProgramEnvironment& env, ProControllerContext& context, bool is_night_time){
struct WeatherTimeState{
bool correct_weather;
bool daytime;
};

WeatherTimeState get_weather_time_state(
SingleSwitchProgramEnvironment& env,
ProControllerContext& context,
bool close_map
){
open_map(env.console, context, false, true);
context.wait_for_all_requests();

// zoom fully in
pbf_move_right_joystick(context, {0, 1}, 900ms, 120ms);
context.wait_for_all_requests();
// hide icons
pbf_press_button(context, BUTTON_MINUS, 80ms, 120ms);
context.wait_for_all_requests();
env.log("Starting weather loop");
bench_loop(env, context, (is_night_time) ? 1 : 2);

while (!proper_weather(env, context)){
env.log("Weather not found");
VideoSnapshot screen = env.console.video().snapshot();
// Log every weather type so uploaded user logs distinguish unsuitable
// weather from missed or ambiguous detections. These small-region checks
// run between controller sequences; retain them for support troubleshooting.
// Keep the Sunny-or-Clear acceptance rule, including multiple matches.
const std::array<WeatherIconType, 6> types = {
WeatherIconType::Sunny, WeatherIconType::Clear, WeatherIconType::Rain,
WeatherIconType::Cloudy, WeatherIconType::Foggy, WeatherIconType::Rainbow,
};
const std::array<const char*, 6> names = {
"Sunny", "Clear", "Rain", "Cloudy", "Foggy", "Rainbow",
};
std::array<bool, 6> matches{};
std::string detected;
std::string detection_map;
size_t match_count = 0;
for (size_t i = 0; i < types.size(); i++){
WeatherIconDetector detector(types[i], &env.console.overlay());
matches[i] = detector.detect(screen);
if (i > 0){
detection_map += ",";
}
detection_map += std::string(names[i]) + "=" + (matches[i] ? "1" : "0");
if (matches[i]){
if (match_count++ > 0){
detected += ",";
}
detected += names[i];
}
}
if (match_count == 0){
detected = "Unknown";
}

DayNightStateDetector dayNightDetector(&env.console.overlay());
dayNightDetector.detect(screen);
bool daytime = dayNightDetector.state() != DayNightState::NIGHT;
bool correct_weather = matches[0] || matches[1];

// Mirror the detector's terrain sample so the day/night threshold can be
// checked against capture brightness without changing the detector itself.
const ImageStats terrain = image_stats(
extract_box_reference(screen, ImageFloatBox(0.30, 0.55, 0.15, 0.18))
);
const double blue_ratio = terrain.average.b /
(terrain.average.r + terrain.average.g + terrain.average.b);
auto& stats = env.current_stats<ShinyHunt_HelioptileHunter_Descriptor::Stats>();
env.log("[Helioptile][Weather] cycle=" + std::to_string(stats.cycles.load() + 1)
+ " completed_loops=" + std::to_string(stats.loops.load())
+ " phase=" + (close_map ? "bench" : "hunt")
+ " detected=" + detected
+ " ambiguous=" + (match_count > 1 ? "1" : "0")
+ " matches={" + detection_map + "}"
+ " time=" + (daytime ? "Day" : "Night")
+ " blue_ratio=" + std::to_string(blue_ratio)
+ " night_threshold=0.36"
+ " eligible=" + (correct_weather && daytime ? "1" : "0")
+ " frame=" + std::to_string(screen->width()) + "x" + std::to_string(screen->height())
);

if (close_map){
context.wait_for_all_requests();
pbf_press_button(context, BUTTON_PLUS, 500ms, 500ms);
context.wait_for_all_requests();
}

return {correct_weather, daytime};
}

void find_weather(SingleSwitchProgramEnvironment& env, ProControllerContext& context){
WeatherTimeState state = get_weather_time_state(env, context, true);
context.wait_for_all_requests();
env.log("[Helioptile][Decision] action=initial_bench_reroll sits="
+ std::to_string(state.daytime ? 2 : 1));
bench_loop(env, context, (state.daytime) ? 2 : 1);

size_t weather_attempt = 0;
while (true){
env.log("[Helioptile][Bench] attempt=" + std::to_string(++weather_attempt));
state = get_weather_time_state(env, context, true);
if (state.correct_weather && state.daytime){
break;
}
env.log("[Helioptile][Decision] action=bench_reroll sits=2 reason="
+ std::string(!state.correct_weather ? "weather" : "night"));
bench_loop(env, context, 2);
}
env.log("Weather found");
env.log("[Helioptile][Decision] action=weather_found destination=WildZone14");
context.wait_for_all_requests();
}

void reach_bench(SingleSwitchProgramEnvironment& env, ProControllerContext& context){
//Go to poke center
pbf_move_left_joystick(context, {-0.141, +1}, 100ms, 200ms);
pbf_press_button(context, BUTTON_A, 500ms, 500ms);
pbf_press_button(context, BUTTON_A, 500ms, 500ms);
pbf_wait(context, 3000ms);
FastTravelState result = open_map_and_fly_to(
env.console, context, Language::English,
Location::MAGENTA_POKEMON_CENTER, false, true
);

if (result != FastTravelState::SUCCESS) {
env.log(
"Fast travel to Magenta failed. State = "
+ std::to_string((int)result)
);

OperationFailedExceptionWithScreenshot::fire(
ErrorReportMode::SEND_ERROR_REPORT,
"Failed to fast travel to Magenta Pokemon Center.",
env.console
);
}
context.wait_for_all_requests();
//Go to bench
pbf_move_left_joystick(context, {-1, 0}, 700ms, 200ms);
pbf_move_left_joystick(context, {0, +1}, 500ms, 200ms);
Expand All @@ -151,11 +252,20 @@ void reach_gate(
env.console.overlay().add_log("Detect Entrance");
}

void reach_wild_zone(SingleSwitchProgramEnvironment& env, ProControllerContext& context){
pbf_move_left_joystick(context, {+0.094, -1}, 200ms, 200ms);
pbf_press_button(context, BUTTON_A, 500ms, 500ms);
pbf_press_button(context, BUTTON_A, 500ms, 500ms);
pbf_wait(context, 2000ms);
void warp_wild_zone_14(SingleSwitchProgramEnvironment& env, ProControllerContext& context
) {
FastTravelState result = open_map_and_fly_to(
env.console, context, Language::English,
Location::WILD_ZONE_14, false, true
);

if (result != FastTravelState::SUCCESS) {
OperationFailedExceptionWithScreenshot::fire(
ErrorReportMode::SEND_ERROR_REPORT,
"Failed to fast travel to Wild Zone 14.",
env.console
);
}
}

void execute_fixed_routine(SingleSwitchProgramEnvironment& env, ConsoleHandle& console, ProControllerContext& context, EventNotificationOption& settings){
Expand Down Expand Up @@ -190,36 +300,80 @@ void ShinyHunt_HelioptileHunter::program(SingleSwitchProgramEnvironment& env, Pr
require_player(env.console, context, BUTTON_L);

ShinyHunt_HelioptileHunter_Descriptor::Stats& stats = env.current_stats<ShinyHunt_HelioptileHunter_Descriptor::Stats>();

// This routine do not care for day/night change as is supposed to stop before time change.

while(true){

if (END_AFTER_CYCLE.current_value() > 0 && END_AFTER_CYCLE.current_value() == stats.cycles.load()){

if (END_AFTER_CYCLE.current_value() > 0 &&
END_AFTER_CYCLE.current_value() == stats.cycles.load(std::memory_order_relaxed)
){
go_home(env.console, context);
send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH);
break;
}

int hunt_loops = 0;
std::string cycle_end_reason = "loop_limit";
while (hunt_loops < 65){
if (!proper_weather(env,context) || (hunt_loops == 0)){
env.log("Not correct weather");
// Begin each cycle by re-rolling weather at the bench. A cycle ends
// after 65 completed hunt loops or when the weather/time check fails.
if (hunt_loops == 0){
env.log("[Helioptile][Decision] action=reach_bench reason=cycle_start");
reach_bench(env, context);
find_weather(env, context, false);
reach_wild_zone(env, context);
find_weather(env, context);
warp_wild_zone_14(env, context);
hunt_loops = 0;
}else{
env.log("Correct weather. Continuing");
move_map_cursor_from_entrance_to_zone(env.console, context, Location::WILD_ZONE_14);
fly_from_map(env.console, context);
}
WeatherTimeState state = get_weather_time_state(env, context, false);
if (state.correct_weather && state.daytime){
env.log("[Helioptile][Decision] action=continue_hunt");
// Zoom fully out before moving map cursor for fast travel.
pbf_move_right_joystick(context, {0, -1}, 900ms, 120ms);
// Re-show icons before moving the map cursor to the destination.
pbf_press_button(context, BUTTON_MINUS, 80ms, 120ms);
//these extra waits help prevent early execution of the map move which sometimes fired early during testing
context.wait_for_all_requests();
pbf_wait(context, 100ms);
move_map_cursor_from_entrance_to_zone(env.console, context, Location::WILD_ZONE_14);
FastTravelState result = fly_from_map(env.console, context);

switch (result) {
case FastTravelState::SUCCESS:
wait_until_overworld(env.console, context);
break;

default:
OperationFailedExceptionWithScreenshot::fire(
ErrorReportMode::SEND_ERROR_REPORT,
"Failed to fast travel back to Wild Zone 14 after weather check.",
env.console
);
}
}else{
cycle_end_reason = !state.correct_weather ? "weather" : "night";
env.log("[Helioptile][Decision] action=end_cycle reason=" + cycle_end_reason);
pbf_press_button(context, BUTTON_PLUS, 500ms, 500ms);
context.wait_for_all_requests();
break;
}
}

env.log(
"[Helioptile][Loop] cycle=" + std::to_string(stats.cycles.load(std::memory_order_relaxed) + 1)
+ " cycle_loop=" + std::to_string(hunt_loops + 1)
+ " total_loop=" + std::to_string(stats.loops.load() + 1)
);
execute_fixed_routine(env,env.console, context, NOTIFICATION_STATUS);

stats.loops++;
hunt_loops++;
env.update_stats();
}
env.log(
"[Helioptile][Decision] action=cycle_complete cycle="
+ std::to_string(stats.cycles.load(std::memory_order_relaxed) + 1)
+ " completed_loops=" + std::to_string(hunt_loops)
+ " reason=" + cycle_end_reason
);
stats.cycles++;
env.update_stats();
}
Expand Down
Loading