Skip to content
Open
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 @@ -10,7 +10,6 @@
#include "CommonTools/Async/InferenceRoutines.h"
#include "CommonTools/StartupChecks/StartProgramChecks.h"
#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h"
#include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h"
#include "Pokemon/Pokemon_Strings.h"
#include "PokemonBDSP/PokemonBDSP_Settings.h"
#include "PokemonBDSP/Programs/PokemonBDSP_GlobalRoomHeal.h"
Expand All @@ -20,6 +19,10 @@
#include "PokemonBDSP/Inference/Battles/PokemonBDSP_EndBattleDetector.h"
#include "PokemonBDSP_MoneyFarmerRoute210.h"

#include "CommonFramework/ImageTools/ImageBoxes.h"
#include <vector>
#include <limits>

namespace PokemonAutomation{
namespace NintendoSwitch{
namespace PokemonBDSP{
Expand All @@ -44,16 +47,19 @@ struct MoneyFarmerRoute210_Descriptor::Stats : public StatsTracker{
, m_errors(m_stats["Errors"])
, m_noreact(m_stats["No React"])
, m_react(m_stats["React"])
, m_pickup(m_stats["Pickup"])
{
m_display_order.emplace_back("Searches");
m_display_order.emplace_back("Errors", HIDDEN_IF_ZERO);
m_display_order.emplace_back("No React");
m_display_order.emplace_back("React");
m_display_order.emplace_back("Pickup", HIDDEN_IF_ZERO);
}
std::atomic<uint64_t>& m_searches;
std::atomic<uint64_t>& m_errors;
std::atomic<uint64_t>& m_noreact;
std::atomic<uint64_t>& m_react;
std::atomic<uint64_t>& m_pickup;
};
std::unique_ptr<StatsTracker> MoneyFarmerRoute210_Descriptor::make_stats() const{
return std::unique_ptr<StatsTracker>(new Stats());
Expand Down Expand Up @@ -134,7 +140,9 @@ MoneyFarmerRoute210::MoneyFarmerRoute210()



bool MoneyFarmerRoute210::battle(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint8_t pp0[4], uint8_t pp1[4]){
MoneyFarmerRoute210::BattleOutcome MoneyFarmerRoute210::battle(SingleSwitchProgramEnvironment& env, ProControllerContext& context,
uint8_t pp0[4], uint8_t pp1[4],
const std::vector<ImagePixelBox>& bubbles, const QSize& img_dims){
MoneyFarmerRoute210_Descriptor::Stats& stats = env.current_stats<MoneyFarmerRoute210_Descriptor::Stats>();

env.log("Starting battle!");
Expand All @@ -143,7 +151,8 @@ bool MoneyFarmerRoute210::battle(SingleSwitchProgramEnvironment& env, ProControl
StartBattleDetector detector(env.console);
int ret = run_until<ProControllerContext>(
env.console, context,
[](ProControllerContext& context){
[this, &env, &bubbles, &img_dims](ProControllerContext& context){
move_to_trainer(env, context, bubbles, img_dims);
pbf_press_button(context, BUTTON_ZL, 80ms, 80ms);
for (size_t c = 0; c < 17; c++){
pbf_press_dpad(context, DPAD_UP, 40ms, 80ms);
Expand All @@ -158,7 +167,7 @@ bool MoneyFarmerRoute210::battle(SingleSwitchProgramEnvironment& env, ProControl
stats.m_errors++;
env.log("Failed to detect start of battle after 20 seconds.", COLOR_RED);
pbf_mash_button(context, BUTTON_B, 1000ms);
return false;
return BattleOutcome::FAILED_START;
}
}
pbf_wait(context, 5000ms);
Expand Down Expand Up @@ -244,15 +253,15 @@ bool MoneyFarmerRoute210::battle(SingleSwitchProgramEnvironment& env, ProControl
case 1:
env.log("Battle finished!", COLOR_BLUE);
pbf_mash_button(context, BUTTON_B, 2000ms);
return false;
return BattleOutcome::FINISHED;
case 2:
env.log("Detected move learn!", COLOR_BLUE);
if (ON_LEARN_MOVE == OnLearnMove::DONT_LEARN){
pbf_move_right_joystick(context, {0, -1}, 160ms, 840ms);
pbf_press_button(context, BUTTON_ZL, 160ms, 840ms);
break;
}
return true;
return BattleOutcome::MOVE_LEARN;
default:
stats.m_errors++;
OperationFailedException::fire(
Expand All @@ -270,14 +279,98 @@ bool MoneyFarmerRoute210::battle(SingleSwitchProgramEnvironment& env, ProControl
);
}

void MoneyFarmerRoute210::move_to_trainer(SingleSwitchProgramEnvironment& env, ProControllerContext& context,
const std::vector<ImagePixelBox>& bubbles, const QSize& img_dims){

const double inv_w = 1.0 / img_dims.width();
const double inv_h = 1.0 / img_dims.height();
const double player_rel_x = 540.0 * inv_w;
const double player_rel_y = 163.0 * inv_h;

// Offsets from original pixel calibration: 350-540=-190, 450-540=-90, 560-540=+20, 110-163=-53
const double FAR_LEFT_DX = -190.0 * inv_w;
const double LEFT_DX = -90.0 * inv_w;
const double RIGHT_DX = 20.0 * inv_w;
const double ABOVE_DY = -53.0 * inv_h;

const ImagePixelBox* closest = nullptr;
double best_dist_sq = std::numeric_limits<double>::max();

for (const auto& box : bubbles) {
double dx = box.min_x * inv_w - player_rel_x;
double dy = box.min_y * inv_h - player_rel_y;
double dist_sq = dx*dx + dy*dy;

if (dist_sq < best_dist_sq) {
best_dist_sq = dist_sq;
closest = &box;
}
}

if (!closest) return;

const double dx = closest->min_x * inv_w - player_rel_x;
const double dy = closest->min_y * inv_h - player_rel_y;

if (dx < FAR_LEFT_DX) {
// Trainer is far-left, behind the other trainer
pbf_press_dpad(context, DPAD_UP, 400ms, 0ms);
pbf_press_dpad(context, DPAD_LEFT, 500ms, 0ms);
pbf_mash_button(context, BUTTON_A, 1000ms);
context.wait_for_all_requests();
pbf_press_dpad(context, DPAD_DOWN, 400ms, 0ms);
pbf_mash_button(context, BUTTON_A, 1000ms);
context.wait_for_all_requests();
} else if (dx < LEFT_DX) {
// Trainer is to the left
if (dy < ABOVE_DY) {
// Also one step above
pbf_press_dpad(context, DPAD_UP, 400ms, 0ms);
pbf_press_dpad(context, DPAD_LEFT, 400ms, 0ms);
context.wait_for_all_requests();
} else {
pbf_press_dpad(context, DPAD_LEFT, 400ms, 0ms);
}
} else if (dx < RIGHT_DX) {
// Trainer is directly above
pbf_press_dpad(context, DPAD_UP, 400ms, 0ms);
} else {
// Trainer is above and one step to the right
pbf_press_dpad(context, DPAD_RIGHT, 400ms, 0ms);
pbf_press_dpad(context, DPAD_UP, 400ms, 0ms);
context.wait_for_all_requests();
}

pbf_mash_button(context, BUTTON_A, 1000ms);
context.wait_for_all_requests();
}

void MoneyFarmerRoute210::recover_from_failed_battle_start(
SingleSwitchProgramEnvironment& env,
ProControllerContext& context
){
env.log("Recovering from failed battle start – moving to safe position.");

pbf_press_dpad(context, DPAD_LEFT, 600ms, 0ms);
pbf_press_dpad(context, DPAD_UP, 1000ms, 0ms);
pbf_press_dpad(context, DPAD_LEFT, 1000ms, 0ms);
pbf_press_dpad(context, DPAD_DOWN, 5000ms, 0ms);
context.wait_for_all_requests();
}

void MoneyFarmerRoute210::check_pickup_items(
ProControllerContext& context, const bool pickup_slots[6]
){

// Open menu
// Open the menu and the map no matter where the cursor is and leaving the menu again to make sure of its position
pbf_press_button(context, BUTTON_X, 80ms, 1000ms);
pbf_press_button(context, BUTTON_PLUS, 80ms, 1920ms);
pbf_mash_button(context, BUTTON_B, 2000ms);

// Open Pokemon menu
// Open the menu and move to the Pokemon menu
pbf_press_button(context, BUTTON_X, 40ms, 600ms);
pbf_press_dpad(context, DPAD_UP, 80ms, 600ms);
pbf_press_dpad(context, DPAD_RIGHT, 80ms, 600ms);
pbf_press_button(context, BUTTON_A, 80ms, 1000ms);

// Loop over each pokemon that has Pickup according to the settings
Expand Down Expand Up @@ -313,6 +406,7 @@ void MoneyFarmerRoute210::check_pickup_items(

// Exit the screen
pbf_mash_button(context, BUTTON_B, 2000ms);

}

void MoneyFarmerRoute210::heal_at_center_and_return(
Expand Down Expand Up @@ -372,11 +466,19 @@ void MoneyFarmerRoute210::fly_to_center_heal_and_return(
bool MoneyFarmerRoute210::heal_after_battle_and_return(
SingleSwitchProgramEnvironment& env,
VideoStream& stream, ProControllerContext& context,
uint8_t pp0[4], uint8_t pp1[4])
uint8_t pp0[4], uint8_t pp1[4], bool has_pickup_mons)
{
if (HEALING_METHOD == HealMethod::CelesticTown){
// Go to Celestic Town Pokecenter to heal the party.
fly_to_center_heal_and_return(stream.logger(), context, pp0, pp1);
if (has_pickup_mons){
// Move the menu cursor back to the Pokemon icon
pbf_press_button(context, BUTTON_X, 40ms, 600ms);
pbf_press_dpad(context, DPAD_UP, 80ms, 600ms);
pbf_press_dpad(context, DPAD_RIGHT, 80ms, 600ms);
pbf_wait(context, 400ms);
pbf_mash_button(context, BUTTON_B, 1000ms);
}
return false;
}else{
// Use Global Room to heal the party.
Expand Down Expand Up @@ -435,13 +537,14 @@ void MoneyFarmerRoute210::program(SingleSwitchProgramEnvironment& env, ProContro
PICKUP_SLOT6,
};



bool has_pickup_mons = PICKUP_SLOT1 || PICKUP_SLOT2 || PICKUP_SLOT3 || PICKUP_SLOT4 || PICKUP_SLOT5 || PICKUP_SLOT6;

uint32_t pickup_counter = 0;
uint32_t total_pickup_checks = 0;

// Connect the controller.
require_player(env.console, context, BUTTON_B);
pbf_press_button(context, BUTTON_B, 40ms, 40ms);

bool need_to_charge = true;
if (START_LOCATION == StartLocation::CelesticTown){
Expand All @@ -453,16 +556,13 @@ void MoneyFarmerRoute210::program(SingleSwitchProgramEnvironment& env, ProContro
}
pbf_move_left_joystick(context, {+1, 0}, 1120ms, 0ms);
}

uint8_t consecutive_failures = 0;

while (true){
env.update_stats();

send_program_status_notification(env, NOTIFICATION_STATUS_UPDATE);

if (has_pickup_mons && pickup_counter % CHECK_PICKUP_FREQ.current_value() == 0 && pickup_counter != total_pickup_checks){
check_pickup_items(context, pickup_slots_selected);
total_pickup_checks = pickup_counter;
}

if (need_to_charge){
pbf_move_left_joystick(context, {+1, 0}, 1120ms, 0ms);
Expand All @@ -478,6 +578,7 @@ void MoneyFarmerRoute210::program(SingleSwitchProgramEnvironment& env, ProContro
context.wait_for_all_requests();
stats.m_searches++;

QSize img_dims;
std::vector<ImagePixelBox> bubbles;
{
VSSeekerReactionTracker tracker(env.console, {0.20, 0.20, 0.60, 0.60});
Expand All @@ -491,6 +592,7 @@ void MoneyFarmerRoute210::program(SingleSwitchProgramEnvironment& env, ProContro
need_to_charge = true;
pbf_mash_button(context, BUTTON_B, 2000ms);

img_dims = tracker.dimensions();
bubbles = tracker.reactions();
if (bubbles.empty()){
env.log("No reactions.", COLOR_ORANGE);
Expand All @@ -500,16 +602,32 @@ void MoneyFarmerRoute210::program(SingleSwitchProgramEnvironment& env, ProContro
stats.m_react++;
}
for (const ImagePixelBox& box : bubbles){
env.log("Reaction at: " + std::to_string(box.min_x), COLOR_BLUE);
env.log("Reaction at X: " + std::to_string(box.min_x) + "Y: " + std::to_string(box.min_y), COLOR_BLUE);
}

if (this->battle(env, context, pp0, pp1)){
return;
// Attempt the battle
BattleOutcome outcome = battle(env, context, pp0, pp1, bubbles, img_dims);
if (outcome == BattleOutcome::FAILED_START) {
consecutive_failures++;
if (consecutive_failures > 3) {
stats.m_errors++;
env.log("Battle did not 4 times in a row. Flying back.");
heal_after_battle_and_return(env, env.console, context, pp0, pp1, has_pickup_mons);
consecutive_failures = 0;
}
env.log("Battle did not start. Recovering and retrying.");
recover_from_failed_battle_start(env, context);
continue;
}

pickup_counter++;
if (has_pickup_mons && pickup_counter % CHECK_PICKUP_FREQ.current_value() == 0) {
check_pickup_items(context, pickup_slots_selected);
stats.m_pickup++;
}

if (!has_pp(pp0, pp1)){
need_to_charge = heal_after_battle_and_return(env, env.console, context, pp0, pp1);
need_to_charge = heal_after_battle_and_return(env, env.console, context, pp0, pp1, has_pickup_mons);
continue;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
#include "PokemonBDSP/Options/PokemonBDSP_ShortcutDirection.h"
#include "PokemonBDSP/Options/PokemonBDSP_LearnMove.h"

#include "CommonFramework/ImageTools/ImageBoxes.h"
#include <vector>
#include <QSize>

namespace PokemonAutomation{
namespace NintendoSwitch{
namespace PokemonBDSP{
Expand All @@ -35,23 +39,44 @@ class MoneyFarmerRoute210 : public SingleSwitchProgramInstance{
virtual void program(SingleSwitchProgramEnvironment& env, ProControllerContext& context) override;

private:
// Run the battle loop. Return true if the program should stop.
bool battle(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint8_t pp0[4], uint8_t pp1[4]);
enum class BattleOutcome {
FINISHED,
FAILED_START,
MOVE_LEARN
};
// Run the battle loop. Returns the outcome of the battle attempt.
BattleOutcome battle(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint8_t pp0[4], uint8_t pp1[4],
const std::vector<ImagePixelBox>& bubbles, const QSize& img_dims
);
// Check for items generated through the Pickup ability
void check_pickup_items(
ProControllerContext& context,
const bool pickup_slots_selected[6]
);
);
// From the bottom row of the Ace Trainer pair, heal Pokemon and return.
// Return true if VS Seeker needs charging.
void move_to_trainer(
SingleSwitchProgramEnvironment& env,
ProControllerContext& context,
const std::vector<ImagePixelBox>& bubbles,
const QSize& img_dims
);
void recover_from_failed_battle_start(
SingleSwitchProgramEnvironment& env,
ProControllerContext& context
);
bool heal_after_battle_and_return(
SingleSwitchProgramEnvironment& env,
VideoStream& stream, ProControllerContext& context,
uint8_t pp0[4], uint8_t pp1[4]
uint8_t pp0[4], uint8_t pp1[4], bool has_pickup_mons
);
// Starting in front of the Celestic Town Pokecenter, heal and return
// to the Ace Trainer pair.
void heal_at_center_and_return(Logger& logger, ProControllerContext& context, uint8_t pp0[4], uint8_t pp1[4]);
void heal_at_center_and_return(
Logger& logger,
ProControllerContext& context,
uint8_t pp0[4], uint8_t pp1[4]
);
// Fly from the Ace Trainer pair to Hearthome Pokecenter, heal and return.
void fly_to_center_heal_and_return(
Logger& logger, ProControllerContext& context,
Expand Down
Loading