Skip to content
Draft
Show file tree
Hide file tree
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 @@ -6,6 +6,9 @@

#include <stdint.h>
#include <array>
#include <iostream>
using std::cout;
using std::endl;

#include "Common/Cpp/Color.h"
#include "CommonFramework/ImageTools/ImageBoxes.h"
Expand Down Expand Up @@ -65,6 +68,47 @@ bool BlackDialogBoxDetector::process_frame(const ImageViewRGB32& frame, WallCloc



namespace{
std::array<ImageFloatBox, 2> EGG_HATCH_DIALOG_BOXES{{
{0.200, 0.900, 0.520, 0.050},
{0.270, 0.820, 0.480, 0.050},
}};
}

EggHatchBlackDialogBoxDetector::EggHatchBlackDialogBoxDetector(bool stop_on_detected)
: VisualInferenceCallback("EggHatchBlackDialogBoxDetector")
, m_stop_on_detected(stop_on_detected)
, m_detected(false)
{}

void EggHatchBlackDialogBoxDetector::make_overlays(VideoOverlaySet& items) const{
for (const auto& box : EGG_HATCH_DIALOG_BOXES){
items.add(COLOR_YELLOW, box);
}
}

bool EggHatchBlackDialogBoxDetector::process_frame(const ImageViewRGB32& frame, WallClock timestamp){
static size_t s_frame_count = 0;
++s_frame_count;

bool detected = true;
for (size_t i = 0; i < EGG_HATCH_DIALOG_BOXES.size(); ++i){
ImageStats stats = image_stats(extract_box_reference(frame, EGG_HATCH_DIALOG_BOXES[i]));
// Print once per ~60 frames (~1s). Only runs while open_menu_to_fly is active.
if (s_frame_count % 60 == 0){
cout << "EggHatchBlackDialogBox box[" << i << "]: avg_sum=" << stats.average.sum()
<< " stddev_sum=" << stats.stddev.sum() << endl;
}
if (!is_black(stats, 210, 30)){
detected = false;
break;
}
}
m_detected.store(detected, std::memory_order_release);
return detected && m_stop_on_detected;
}


WhiteDialogBoxDetector::WhiteDialogBoxDetector(Color color)
: m_color(color)
, m_right(0.782, 0.850, 0.030, 0.050)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,25 @@ class BlackDialogBoxDetector : public VisualInferenceCallback{
};


// Detects the semi-transparent dark dialog that appears when an egg begins hatching.
// Checks two horizontal bands inside the dialog that are uniformly near-black (~RGB 20-30).
class EggHatchBlackDialogBoxDetector : public VisualInferenceCallback{
public:
EggHatchBlackDialogBoxDetector(bool stop_on_detected);

bool detected() const{
return m_detected.load(std::memory_order_acquire);
}

virtual void make_overlays(VideoOverlaySet& items) const override;
virtual bool process_frame(const ImageViewRGB32& frame, WallClock timestamp) override;

private:
bool m_stop_on_detected;
std::atomic<bool> m_detected;
};


class WhiteDialogBoxDetector : public StaticScreenDetector{
public:
WhiteDialogBoxDetector(Color color = COLOR_RED);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -380,8 +380,14 @@ bool EggAutonomous::run_batch(
// we still need to fetch more eggs

// Use fly to reset the location because now we don't know where the player character is.
const bool fly_from_overworld = true;
call_flying_taxi(env, context, fly_from_overworld);
// open_menu_to_fly handles the race where an egg starts hatching during the X press.
open_menu_to_fly(env, context, stats, num_eggs_hatched);
// open_menu_to_fly may have hatched the 5th egg during the X press race condition.
// After it returns the player is at loop start (flew back), so check if we're done.
if (num_eggs_hatched == 5 && m_num_eggs_retrieved == 5){
m_player_at_loop_start = true;
break;
}
restart_bike_loop = true;
// We don't update i_bike_loop here because we haven't finished one full bike loop due to egg hatching
} // end one bike loop
Expand Down Expand Up @@ -464,6 +470,55 @@ void EggAutonomous::save_game(SingleSwitchProgramEnvironment& env, ProController
mash_B_until_y_comm_icon(env, context, "Cannot detect end of saving game.");
}

void EggAutonomous::open_menu_to_fly(
SingleSwitchProgramEnvironment& env,
ProControllerContext& context,
EggAutonomous_Descriptor::Stats& stats,
size_t& num_eggs_hatched
){
const size_t MAX_RETRIES = 5;
for (size_t retry = 0; retry < MAX_RETRIES; ++retry){
RotomPhoneMenuArrowWatcher menu_arrow(env.console.overlay());
EggHatchBlackDialogBoxDetector black_dialog(true);

int ret = run_until<ProControllerContext>(
env.console, context,
[](ProControllerContext& context){
// Press X then wait for the menu animation to complete.
// Do NOT press anything after X — the menu must stay open for the detector.
pbf_press_button(context, BUTTON_X, 160ms,
GameSettings::instance().OVERWORLD_TO_MENU_DELAY0);
ssf_do_nothing(context, 3000ms);
},
{{menu_arrow, black_dialog}}
);

switch (ret){
case 0: // Menu is open — navigate and fly (already in menu, skip X press)
call_flying_taxi(env, context, false);
return;
case 1: // Egg started hatching in the transition window — handle it then retry
++num_eggs_hatched;
stats.m_hatched++;
env.update_stats();
wait_for_egg_hatched(env, context, stats, num_eggs_hatched);
if (num_eggs_hatched < 5){
pbf_move_left_joystick(context, {-1.0, -1.0}, 800ms, 80ms);
context.wait_for_all_requests();
}
break;
default: // lambda timed out, X was ignored — retry
env.log("Menu didn't open, retrying X press.");
break;
}
}
OperationFailedException::fire(
ErrorReport::SEND_ERROR_REPORT,
"Cannot open Rotom phone menu after multiple retries.",
env.console
);
}

void EggAutonomous::call_flying_taxi(
SingleSwitchProgramEnvironment& env,
ProControllerContext& context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ class EggAutonomous : public SingleSwitchProgramInstance{
bool need_taxi
);

// Press X to open the Rotom phone menu and fly home. Uses a state machine to handle
// the race condition where an egg starts hatching just as the menu is being opened.
// num_eggs_hatched is updated in-place if additional hatches are detected.
void open_menu_to_fly(
SingleSwitchProgramEnvironment& env,
ProControllerContext& context,
EggAutonomous_Descriptor::Stats& stats,
size_t& num_eggs_hatched
);

// Used to wait until Y-Comm icon shows up.
// Throw error if it does not find it after 10 sec.
void mash_B_until_y_comm_icon(
Expand Down
Loading