diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Framework/PokemonSwSh_MaxLair_StateMachine.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/Framework/PokemonSwSh_MaxLair_StateMachine.cpp index fed71fe8a..9d09a0d7f 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Framework/PokemonSwSh_MaxLair_StateMachine.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Framework/PokemonSwSh_MaxLair_StateMachine.cpp @@ -160,7 +160,8 @@ StateMachineAction run_state_iteration( global_state, runtime.console_settings[console_index], battle_menu.dmaxed(), - battle_menu.cheer() + battle_menu.cheer(), + runtime.actions ); case 5: console.log("Current State: Catch Select"); diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options.h b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options.h index 6b4e8335c..e0949cb5a 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options.h +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options.h @@ -59,6 +59,8 @@ class EndBattleDecider{ const PathStats& path_stats, bool any_shiny, bool boss_is_shiny ) const = 0; + + virtual bool stop_for_non_boss(const std::string& slug) const = 0; }; diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_NonBossAction.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_NonBossAction.cpp new file mode 100644 index 000000000..6265d52eb --- /dev/null +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_NonBossAction.cpp @@ -0,0 +1,142 @@ +/* Max Lair Non-Boss Action + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "Pokemon/Pokemon_Strings.h" +#include "Pokemon/Resources/Pokemon_PokemonNames.h" +#include "PokemonSwSh/Resources/PokemonSwSh_PokemonSprites.h" +#include "PokemonSwSh/Resources/PokemonSwSh_MaxLairDatabase.h" +#include "PokemonSwSh_MaxLair_Options_NonBossAction.h" + +//#include +//using std::cout; +//using std::endl; + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonSwSh{ +namespace MaxLairInternal{ + + + + +const EnumDropdownDatabase& NonBossAction_Database(){ + static const EnumDropdownDatabase database({ + {NonBossAction::IGNORE, "ignore", "Ignore"}, + {NonBossAction::STOP_PROGRAM, "stop", "Stop program"}, + }); + return database; +} + +static std::string format_display_name(const std::string& slug) { + static const std::vector> region_map = { + + {"-alola", "Alolan "}, + {"-galar", "Galarian "} + }; + + std::string base_slug = slug; + std::string prefix; + + for (const auto& pair : region_map) { + size_t pos = slug.find(pair.first); + if (pos != std::string::npos) { + base_slug = slug.substr(0, pos); + + prefix = pair.second; + break; + } + } + + // Get base Pokemon name + std::string base_name = get_pokemon_name(base_slug).display_name(); + return prefix + base_name; +} + +NonBossActionRow::NonBossActionRow(std::string slug, const std::string& name_slug, const std::string& sprite_slug) + : StaticTableRow(slug) + , pokemon( + LockMode::UNLOCK_WHILE_RUNNING, + format_display_name(slug), + ALL_POKEMON_SPRITES().get_throw(sprite_slug).icon + ) + , action( + NonBossAction_Database(), + LockMode::UNLOCK_WHILE_RUNNING, + NonBossAction::IGNORE + ) +{ + PA_ADD_STATIC(pokemon); + add_option(action, "Action"); +} + + +NonBossActionTable::NonBossActionTable() + : StaticTableOption("Other Pokemon Actions:", LockMode::UNLOCK_WHILE_RUNNING) +{ + + // Limited mode: only display Alolan-Raichu and Alolan-Marowak by default. If the developer wants, it can also add the full list + const bool LIMITED_MODE = true; + + if (LIMITED_MODE) { + std::vector slugs = {"raichu-alola", "marowak-alola"}; + for (const std::string& slug : slugs) { + const MaxLairSlugs& slugs_info = get_maxlair_slugs(slug); + const std::string& sprite_slug = *slugs_info.sprite_slugs.begin(); + const std::string& name_slug = slugs_info.name_slug; + add_row(std::make_unique(slug, name_slug, sprite_slug)); + } + } else { + std::set added_grouped_base; // Track base slugs for grouped forms + + for (const auto& item : all_rentals_by_dex()){ + const std::string& full_slug = item.second; + const MaxLairSlugs& slugs = get_maxlair_slugs(full_slug); + const std::string& sprite_slug = *slugs.sprite_slugs.begin(); + const std::string& name_slug = slugs.name_slug; + + if (full_slug == name_slug) { + add_row(std::make_unique(full_slug, name_slug, sprite_slug)); + continue; + } + + // Check Alola and Galar variants + if (full_slug.size() > 6 && full_slug.substr(full_slug.size() - 6) == "-alola") { + add_row(std::make_unique(full_slug, name_slug, sprite_slug)); + continue; + } + if (full_slug.size() > 6 && full_slug.substr(full_slug.size() - 6) == "-galar") { + add_row(std::make_unique(full_slug, name_slug, sprite_slug)); + continue; + } + + // All other pokemon with suffixes (e.g. Basculin) are grouped under one name + + if (added_grouped_base.find(name_slug) == added_grouped_base.end()) { + add_row(std::make_unique(name_slug, name_slug, sprite_slug)); + added_grouped_base.insert(name_slug); + } + } + } + finish_construction(); +} + +std::vector NonBossActionTable::make_header() const{ + std::vector ret{ + STRING_POKEMON, + "Action", + }; + return ret; +} + + + + + + +} +} +} +} diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_NonBossAction.h b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_NonBossAction.h new file mode 100644 index 000000000..646ef5b46 --- /dev/null +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_NonBossAction.h @@ -0,0 +1,47 @@ +/* Max Lair Boss Action + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonSwSh_MaxLair_Options_NonBossAction_H +#define PokemonAutomation_PokemonSwSh_MaxLair_Options_NonBossAction_H + +#include "Common/Cpp/Options/EnumDropdownOption.h" +#include "Common/Cpp/Options/StaticTableOption.h" +#include "CommonFramework/Options/LabelCellOption.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonSwSh{ +namespace MaxLairInternal{ + + +enum class NonBossAction{ + IGNORE, + STOP_PROGRAM, +}; + +class NonBossActionRow : public StaticTableRow{ +public: + NonBossActionRow(std::string slug, const std::string& name_slug, const std::string& sprite_slug); + + LabelCellOption pokemon; + EnumDropdownCell action; +}; + +class NonBossActionTable : public StaticTableOption{ +public: + NonBossActionTable(); + virtual std::vector make_header() const; +}; + + + + + +} +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp index 1b503f243..6b5e547d1 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp @@ -87,6 +87,7 @@ MaxLairBossFinder::MaxLairBossFinder() PA_ADD_OPTION(CONSOLES); PA_ADD_OPTION(BOSS_LIST); + PA_ADD_OPTION(NON_BOSS_LIST); PA_ADD_OPTION(HOSTING); PA_ADD_OPTION(TOUCH_DATE_INTERVAL); @@ -119,9 +120,10 @@ void MaxLairBossFinder::update_active_consoles(size_t switch_count){ class EndBattleDecider_BossFinder : public EndBattleDecider{ public: - EndBattleDecider_BossFinder(const Consoles& consoles, const BossActionTable& boss_list) + EndBattleDecider_BossFinder(const Consoles& consoles, const BossActionTable& boss_list, NonBossActionTable& non_boss_list) : m_consoles(consoles) , m_boss_list(boss_list) + , m_non_boss_list(non_boss_list) {} virtual const std::string& normal_ball( size_t console_index @@ -151,6 +153,28 @@ class EndBattleDecider_BossFinder : public EndBattleDecider{ } throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid enum."); } + virtual bool stop_for_non_boss(const std::string& slug) const override { + for (const StaticTableRow* row : m_non_boss_list.table()) { + const NonBossActionRow* nb_row = static_cast(row); + + const std::string& row_slug = nb_row->slug(); + + // Exact match + if (slug == row_slug) { + return nb_row->action == NonBossAction::STOP_PROGRAM; + } + + // If it has a specific variant, match any OCR slug that starts with it and has a hyphen (a form) + if (row_slug.find('-') == std::string::npos) { + if (slug.size() > row_slug.size() && + slug.substr(0, row_slug.size()) == row_slug && + slug[row_slug.size()] == '-') { + return nb_row->action == NonBossAction::STOP_PROGRAM; + } + } + } + return false; + } private: @@ -171,6 +195,7 @@ class EndBattleDecider_BossFinder : public EndBattleDecider{ const Consoles& m_consoles; const BossActionTable& m_boss_list; + const NonBossActionTable& m_non_boss_list; }; @@ -194,7 +219,7 @@ void MaxLairBossFinder::program(MultiSwitchProgramEnvironment& env, CancellableS } }); - EndBattleDecider_BossFinder decider(CONSOLES, BOSS_LIST); + EndBattleDecider_BossFinder decider(CONSOLES, BOSS_LIST, NON_BOSS_LIST); loop_adventures( env, scope, CONSOLES, diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.h b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.h index b8d66f8b4..0c1b95a2e 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.h +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.h @@ -16,6 +16,7 @@ #include "Options/PokemonSwSh_MaxLair_Options_Consoles.h" #include "Options/PokemonSwSh_MaxLair_Options_Hosting.h" #include "Options/PokemonSwSh_MaxLair_Options_BossAction.h" +#include "Options/PokemonSwSh_MaxLair_Options_NonBossAction.h" namespace PokemonAutomation{ @@ -46,6 +47,7 @@ class MaxLairBossFinder : public MultiSwitchProgramInstance{ MaxLairInternal::Consoles CONSOLES; MaxLairInternal::BossActionTable BOSS_LIST; + MaxLairInternal::NonBossActionTable NON_BOSS_LIST; MaxLairInternal::HostingSettings HOSTING; TouchDateIntervalOption TOUCH_DATE_INTERVAL; diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_Standard.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_Standard.cpp index 53280e6c1..d4aa98138 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_Standard.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_Standard.cpp @@ -166,6 +166,9 @@ class EndBattleDecider_Standard : public EndBattleDecider{ } return actions.no_shinies; } + virtual bool stop_for_non_boss(const std::string& slug) const override { + return false; + } private: const MaxLairStandard_ConsoleOptions& console(size_t index) const{ diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp index d8b2e2def..830c336fb 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp @@ -215,6 +215,9 @@ class EndBattleDecider_StrongBoss : public EndBattleDecider{ return CaughtScreenAction::TAKE_NON_BOSS_SHINY_AND_CONTINUE; } } + virtual bool stop_for_non_boss(const std::string& slug) const override { + return false; + } private: const MaxLairStrongBoss_ConsoleOptions& console(size_t index) const{ diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Battle.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Battle.cpp index 4bd7b7d19..d858a18da 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Battle.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Battle.cpp @@ -201,7 +201,8 @@ StateMachineAction run_move_select( OcrFailureWatchdog& ocr_watchdog, GlobalStateTracker& state_tracker, const ConsoleSpecificOptions& settings, - bool currently_dmaxed, bool cheer_only + bool currently_dmaxed, bool cheer_only, + const EndBattleDecider& decider ){ GlobalState& state = state_tracker[console_index]; size_t player_index = state.find_player_index(console_index); @@ -216,6 +217,14 @@ StateMachineAction run_move_select( )){ return StateMachineAction::RESET_RECOVER; } + + if (state.wins < 4) { + const std::string& opponent = state.opponent.empty() ? "" : *state.opponent.begin(); + if (!opponent.empty() && decider.stop_for_non_boss(opponent)) { + stream.log("Stopping program as " + opponent + " was encountered.", COLOR_PURPLE); + return StateMachineAction::STOP_PROGRAM; + } + } GlobalState inferred = state_tracker.synchronize(stream.logger(), console_index); diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Battle.h b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Battle.h index 2d7fb5bf3..fcb78dd93 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Battle.h +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Battle.h @@ -24,7 +24,8 @@ StateMachineAction run_move_select( OcrFailureWatchdog& ocr_watchdog, GlobalStateTracker& state_tracker, const ConsoleSpecificOptions& settings, - bool currently_dmaxed, bool cheer_only + bool currently_dmaxed, bool cheer_only, + const EndBattleDecider& decider ); StateMachineAction throw_balls( diff --git a/SerialPrograms/Source/PokemonSwSh/Resources/PokemonSwSh_MaxLairDatabase.cpp b/SerialPrograms/Source/PokemonSwSh/Resources/PokemonSwSh_MaxLairDatabase.cpp index 95d04e52e..6259802a3 100644 --- a/SerialPrograms/Source/PokemonSwSh/Resources/PokemonSwSh_MaxLairDatabase.cpp +++ b/SerialPrograms/Source/PokemonSwSh/Resources/PokemonSwSh_MaxLairDatabase.cpp @@ -198,6 +198,7 @@ struct MaxLairDatabase{ std::map m_bosses; std::map m_bosses_by_dex; + std::multimap m_rentals_by_dex; static MaxLairDatabase& instance(){ static MaxLairDatabase data; @@ -218,6 +219,15 @@ struct MaxLairDatabase{ } m_bosses_by_dex[iter->second] = item.first; } + + for (const auto& item : m_rentals){ + const MaxLairSlugs& slugs = get_maxlair_slugs(item.first); + auto iter = national_dex.find(slugs.name_slug); + if (iter == national_dex.end()){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Rental slug not found in national dex: " + slugs.name_slug); + } + m_rentals_by_dex.insert({iter->second, item.first}); + } #if 0 for (const auto& item : m_bosses_by_dex){ @@ -237,6 +247,10 @@ const std::map& all_bosses_by_dex(){ const MaxLairDatabase& database = MaxLairDatabase::instance(); return database.m_bosses_by_dex; } +const std::multimap& all_rentals_by_dex(){ + const MaxLairDatabase& database = MaxLairDatabase::instance(); + return database.m_rentals_by_dex; +} bool is_boss(const std::string& slug){ const MaxLairDatabase& database = MaxLairDatabase::instance(); auto iter = database.m_bosses.find(slug); diff --git a/SerialPrograms/Source/PokemonSwSh/Resources/PokemonSwSh_MaxLairDatabase.h b/SerialPrograms/Source/PokemonSwSh/Resources/PokemonSwSh_MaxLairDatabase.h index 1f6fbe76f..ab1c32933 100644 --- a/SerialPrograms/Source/PokemonSwSh/Resources/PokemonSwSh_MaxLairDatabase.h +++ b/SerialPrograms/Source/PokemonSwSh/Resources/PokemonSwSh_MaxLairDatabase.h @@ -55,6 +55,7 @@ struct MaxLairMon{ }; const std::map& all_bosses_by_dex(); +const std::multimap& all_rentals_by_dex(); bool is_boss(const std::string& slug); const MaxLairMon& get_maxlair_mon(const std::string& slug); diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index badf7c142..296feb2b1 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -2412,6 +2412,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_Consoles.h Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_Hosting.cpp Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_Hosting.h + Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_NonBossAction.cpp + Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_NonBossAction.h Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.h Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_Standard.cpp