diff --git a/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_LocationNameReader.cpp b/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_LocationNameReader.cpp index e75469963a..4077333317 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_LocationNameReader.cpp +++ b/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_LocationNameReader.cpp @@ -4,24 +4,28 @@ * */ +#include +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/OCR/OCR_Routines.h" #include "PokemonLZA_LocationNameReader.h" + namespace PokemonAutomation{ -namespace Pokemon{ +namespace NintendoSwitch{ +namespace PokemonLZA{ -LocationNameReader& LocationNameReader::instance(){ - static LocationNameReader reader; +LocationNameOCR& LocationNameOCR::instance(){ + static LocationNameOCR reader; return reader; } -LocationNameReader::LocationNameReader() +LocationNameOCR::LocationNameOCR() : SmallDictionaryMatcher("PokemonLZA/LocationName.json") {} -OCR::StringMatchResult LocationNameReader::read_substring( +OCR::StringMatchResult LocationNameOCR::read_substring( Logger& logger, Language language, const ImageViewRGB32& image, @@ -34,7 +38,45 @@ OCR::StringMatchResult LocationNameReader::read_substring( ); } +LocationNameReader::LocationNameReader(Color color) + : m_color(color) + , m_box_location_name(get_all_box_locations(ImageFloatBox(0.035, 0.254, 0.287, 0.050))) +{} + +void LocationNameReader::make_overlays(VideoOverlaySet& items) const{ + for (size_t c = 0; c < PAGE_SIZE; c++){ + items.add(m_color, m_box_location_name[c]); + } +} + +std::array LocationNameReader::get_all_box_locations(ImageFloatBox initial_box){ + std::array material_boxes; + double x = initial_box.x; + double width = initial_box.width; + double height = initial_box.height; + double initial_y = initial_box.y; + double y_spacing = 0.078; + for (size_t i = 0; i < LocationNameReader::PAGE_SIZE; i++){ + double y = initial_y + i*y_spacing; + material_boxes[i] = ImageFloatBox(x, y, width, height); + } + return material_boxes; +} + +OCR::StringMatchResult LocationNameReader::read_location_name( + const ImageViewRGB32& screen, + Logger& logger, + Language language, + size_t index +) const{ + ImageViewRGB32 image = extract_box_reference(screen, m_box_location_name[index]); + OCR::StringMatchResult results; + results = LocationNameOCR::instance().read_substring(logger, language, image, OCR::BLACK_OR_WHITE_TEXT_FILTERS()); + return results; +} + +} } } diff --git a/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_LocationNameReader.h b/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_LocationNameReader.h index c78f52b6cb..ab64826fc8 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_LocationNameReader.h +++ b/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_LocationNameReader.h @@ -7,20 +7,25 @@ #ifndef PokemonAutomation_Pokemon_LocationNameReader_H #define PokemonAutomation_Pokemon_LocationNameReader_H +#include +#include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonTools/OCR/OCR_SmallDictionaryMatcher.h" +#include "CommonTools/VisualDetector.h" namespace PokemonAutomation{ -namespace Pokemon{ +namespace NintendoSwitch{ +namespace PokemonLZA{ -class LocationNameReader : public OCR::SmallDictionaryMatcher{ +class LocationNameOCR : public OCR::SmallDictionaryMatcher{ +public: static constexpr double MAX_LOG10P = -1.40; static constexpr double MAX_LOG10P_SPREAD = 0.50; public: - LocationNameReader(); + LocationNameOCR(); - static LocationNameReader& instance(); + static LocationNameOCR& instance(); OCR::StringMatchResult read_substring( Logger& logger, @@ -31,7 +36,30 @@ class LocationNameReader : public OCR::SmallDictionaryMatcher{ ) const; }; +class LocationNameReader{ +public: + LocationNameReader(Color color = COLOR_WHITE); + static constexpr size_t PAGE_SIZE = 7; + + void make_overlays(VideoOverlaySet& items) const; + + // Read a single location display name via OCR given by the index + OCR::StringMatchResult read_location_name( + const ImageViewRGB32& screen, + Logger& logger, + Language language, + size_t index + ) const; +private: + Color m_color; + // Store the boxes that contain each location name in the fast travel menu + std::array m_box_location_name; + // Get the location of all location boxes based on the initial box location + std::array get_all_box_locations(ImageFloatBox initial_box); +}; + +} } } #endif diff --git a/SerialPrograms/Source/PokemonLZA/InferenceTraining/PokemonLZA_GenerateLocationNameOCR.cpp b/SerialPrograms/Source/PokemonLZA/InferenceTraining/PokemonLZA_GenerateLocationNameOCR.cpp index 7aaafb6994..a63277a3cb 100644 --- a/SerialPrograms/Source/PokemonLZA/InferenceTraining/PokemonLZA_GenerateLocationNameOCR.cpp +++ b/SerialPrograms/Source/PokemonLZA/InferenceTraining/PokemonLZA_GenerateLocationNameOCR.cpp @@ -44,7 +44,7 @@ GenerateLocationNameOCR_Descriptor::GenerateLocationNameOCR_Descriptor() GenerateLocationNameOCR::GenerateLocationNameOCR() : LANGUAGE( "Game Language:", - LocationNameReader::instance().languages(), + LocationNameOCR::instance().languages(), LockMode::LOCK_WHILE_RUNNING ) , MODE( @@ -66,7 +66,7 @@ void GenerateLocationNameOCR::read( Logger& logger, const ImageViewRGB32& image ) const{ - OCR::StringMatchResult result = LocationNameReader::instance().read_substring( + OCR::StringMatchResult result = LocationNameOCR::instance().read_substring( logger, LANGUAGE, image, OCR::BLACK_OR_WHITE_TEXT_FILTERS() ); diff --git a/SerialPrograms/Source/PokemonLZA/Programs/Farming/PokemonLZA_DonutMaker.cpp b/SerialPrograms/Source/PokemonLZA/Programs/Farming/PokemonLZA_DonutMaker.cpp index 6024a810f8..aeef26c6e1 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/Farming/PokemonLZA_DonutMaker.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/Farming/PokemonLZA_DonutMaker.cpp @@ -26,6 +26,7 @@ #include "PokemonLZA/Programs/PokemonLZA_GameEntry.h" #include "Pokemon/Pokemon_Strings.h" #include "PokemonLZA/Programs/PokemonLZA_BasicNavigation.h" +#include "PokemonLZA/Programs/PokemonLZA_FastTravelNavigation.h" #include "PokemonLZA/Programs/PokemonLZA_DonutBerrySession.h" #include "PokemonLZA_DonutMaker.h" #include @@ -403,44 +404,6 @@ void DonutMaker::open_berry_menu_from_ansha(SingleSwitchProgramEnvironment& env, ); } -// A generic function to fast travel to an index in the fast travel menu and watch for overworld -void fast_travel_to_index(SingleSwitchProgramEnvironment& env, ProControllerContext& context, int location_index=0){ - DonutMaker_Descriptor::Stats& stats = env.current_stats(); - - env.log("Fast traveling to location at index " + std::to_string(location_index)); - bool zoom_to_max = false; - const bool require_icons = false; - open_map(env.console, context, zoom_to_max, require_icons); - - // Press Y to load fast travel locaiton menu - pbf_press_button(context, BUTTON_Y, 100ms, 500ms); - context.wait_for_all_requests(); - - OverworldPartySelectionWatcher overworld(COLOR_WHITE, &env.console.overlay()); - int ret = run_until( - env.console, context, - [&](ProControllerContext& context){ - // Move cursor to desired location index - for (int i = 0; i < location_index; i++){ - pbf_press_dpad(context, DPAD_DOWN, 50ms, 500ms); - } - pbf_mash_button(context, BUTTON_A, Seconds(10)); - pbf_wait(context, Seconds(30)); // 30 sec to wait out potential day night change - }, - {overworld} - ); - if (ret != 0){ - stats.errors++; - env.update_stats(); - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "donut_maker(): Unable to find overworld after fast traveling to location index " + std::to_string(location_index), - env.console - ); - } - env.log("Detected overworld. Fast traveled to location index " + std::to_string(location_index)); -} - // Exit the game and load the backup save void load_backup_save(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ DonutMaker_Descriptor::Stats& stats = env.current_stats(); @@ -489,12 +452,8 @@ void reset_map_filter_state(SingleSwitchProgramEnvironment& env, ProControllerCo env.log("Resetting fast travel map filters."); open_map(env.console, context, false, false); - // Press Y and - to open fast travel filter menu - pbf_press_button(context, BUTTON_Y, 100ms, 500ms); - pbf_press_button(context, BUTTON_MINUS, 100ms, 500ms); - // Press Down and A to select "Facilities" filter - pbf_press_dpad(context, DPAD_DOWN, 100ms, 500ms); - pbf_press_button(context, BUTTON_A, 100ms, 500ms); + open_fast_travel_menu(env.console, context); + set_fast_travel_menu_filter(env.console, context, FAST_TRAVEL_FILTER::ALL_TRAVEL_SPOTS); // Close out of map exit_menu_to_overworld(env, context); @@ -505,10 +464,19 @@ void reset_map_filter_state(SingleSwitchProgramEnvironment& env, ProControllerCo } // Move to in front of Ansha with button A shown -void move_to_ansha(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ +void DonutMaker::move_to_ansha(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ DonutMaker_Descriptor::Stats& stats = env.current_stats(); - fast_travel_to_index(env, context, 3); // Fast travel to Hotel Z + FastTravelState travel_status = open_map_and_fly_to(env.console, context, LANGUAGE, Location::HOTEL_Z); + if (travel_status != FastTravelState::SUCCESS){ + stats.errors++; + env.update_stats(); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "donut_maker(): Cannot fast travel to Hotel Z.", + env.console + ); + } context.wait_for(100ms); // Wait for player control to return env.log("Detected overworld. Fast traveled to Hotel Zone"); @@ -564,7 +532,7 @@ void move_to_ansha(SingleSwitchProgramEnvironment& env, ProControllerContext& co } // Create a new backup save after making a donut to keep -void save_donut(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ +void DonutMaker::save_donut(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ // DonutMaker_Descriptor::Stats& stats = env.current_stats(); env.log("Creating new backup save to keep the last made donut."); @@ -572,10 +540,6 @@ void save_donut(SingleSwitchProgramEnvironment& env, ProControllerContext& conte // Stop talking to Ansha exit_menu_to_overworld(env, context); context.wait_for_all_requests(); - - // Fast travel to anywhere to set a new backup save after making a donut to keep - // Removed this since it's likely redundant because the program always fast travels to Hotel Z before making a donut - // fast_travel_to_index(env, context, 0, 3000ms); } // Check if all user defined limits are reached or the global max keepers limit is reached diff --git a/SerialPrograms/Source/PokemonLZA/Programs/Farming/PokemonLZA_DonutMaker.h b/SerialPrograms/Source/PokemonLZA/Programs/Farming/PokemonLZA_DonutMaker.h index c80087c65b..92942b2d2a 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/Farming/PokemonLZA_DonutMaker.h +++ b/SerialPrograms/Source/PokemonLZA/Programs/Farming/PokemonLZA_DonutMaker.h @@ -41,6 +41,8 @@ class DonutMaker : public SingleSwitchProgramInstance{ void animation_to_donut(SingleSwitchProgramEnvironment& env, ProControllerContext& context); void add_berries_and_make_donut(SingleSwitchProgramEnvironment& env, ProControllerContext& context); void open_berry_menu_from_ansha(SingleSwitchProgramEnvironment& env, ProControllerContext& context); + void move_to_ansha(SingleSwitchProgramEnvironment& env, ProControllerContext& context); + void save_donut(SingleSwitchProgramEnvironment& env, ProControllerContext& context); bool donut_iteration(SingleSwitchProgramEnvironment& env, ProControllerContext& context, std::vector& match_counts); private: diff --git a/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_BasicNavigation.cpp b/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_BasicNavigation.cpp index 7801de43ce..cb5ebbe614 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_BasicNavigation.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_BasicNavigation.cpp @@ -272,76 +272,81 @@ FastTravelState open_map_and_fly_in_place(ConsoleHandle& console, ProControllerC } -void move_map_cursor_from_entrance_to_zone(ConsoleHandle& console, ProControllerContext& context, WildZone zone){ +void move_map_cursor_from_entrance_to_zone(ConsoleHandle& console, ProControllerContext& context, Location zone){ pbf_wait(context, 300ms); switch(zone){ - case WildZone::WILD_ZONE_1: + case Location::WILD_ZONE_1: pbf_move_left_joystick(context, {-1, -0.173}, 120ms, 0ms); break; - case WildZone::WILD_ZONE_2: + case Location::WILD_ZONE_2: pbf_move_left_joystick(context, {-0.062, +1}, 100ms, 0ms); break; - case WildZone::WILD_ZONE_3: + case Location::WILD_ZONE_3: pbf_move_left_joystick(context, {0, +1}, 100ms, 0ms); break; - case WildZone::WILD_ZONE_4: + case Location::WILD_ZONE_4: pbf_move_left_joystick(context, {+1, 0}, 100ms, 0ms); break; - case WildZone::WILD_ZONE_5: + case Location::WILD_ZONE_5: pbf_move_left_joystick(context, {+0.331, +1}, 100ms, 0ms); break; - case WildZone::WILD_ZONE_6: + case Location::WILD_ZONE_6: pbf_move_left_joystick(context, {-0.375, +1}, 100ms, 0ms); break; - case WildZone::WILD_ZONE_7: + case Location::WILD_ZONE_7: pbf_move_left_joystick(context, {-1, +0.219}, 100ms, 0ms); break; - case WildZone::WILD_ZONE_8: + case Location::WILD_ZONE_8: pbf_move_left_joystick(context, {-1, -0.252}, 100ms, 0ms); break; - case WildZone::WILD_ZONE_9: + case Location::WILD_ZONE_9: pbf_move_left_joystick(context, {-0.453, +1}, 100ms, 0ms); break; - case WildZone::WILD_ZONE_10: + case Location::WILD_ZONE_10: pbf_move_left_joystick(context, {+1, +0.297}, 100ms, 0ms); break; - case WildZone::WILD_ZONE_11: + case Location::WILD_ZONE_11: pbf_move_left_joystick(context, {-1, +0.688}, 100ms, 0ms); break; - case WildZone::WILD_ZONE_12: + case Location::WILD_ZONE_12: pbf_move_left_joystick(context, {-0.844, +1}, 150ms, 0ms); break; - case WildZone::WILD_ZONE_13: + case Location::WILD_ZONE_13: pbf_move_left_joystick(context, {-1, -0.252}, 100ms, 0ms); break; - case WildZone::WILD_ZONE_14: + case Location::WILD_ZONE_14: pbf_move_left_joystick(context, {-0.141, -1}, 100ms, 0ms); break; - case WildZone::WILD_ZONE_15: + case Location::WILD_ZONE_15: pbf_move_left_joystick(context, {-1, +1}, 100ms, 0ms); break; - case WildZone::WILD_ZONE_16: + case Location::WILD_ZONE_16: pbf_move_left_joystick(context, {+0.724, +1}, 100ms, 0ms); break; - case WildZone::WILD_ZONE_17: + case Location::WILD_ZONE_17: pbf_move_left_joystick(context, {+0.646, +1}, 100ms, 0ms); break; - case WildZone::WILD_ZONE_18: + case Location::WILD_ZONE_18: pbf_move_left_joystick(context, {-0.844, -1}, 100ms, 0ms); break; - case WildZone::WILD_ZONE_19: + case Location::WILD_ZONE_19: pbf_move_left_joystick(context, {-0.375, -1}, 100ms, 0ms); break; - case WildZone::WILD_ZONE_20_NO_DISTORTION: + case Location::WILD_ZONE_20_NO_DISTORTION: pbf_move_left_joystick(context, {-1, +0.297}, 140ms, 0ms); break; - case WildZone::WILD_ZONE_20_WITH_DISTORTION: + case Location::WILD_ZONE_20_WITH_DISTORTION: // During the distortion happening on top of Lumiose Tower as part // of the Mega Dimension DLC story, the wild zone 20 fast travel // symbol on the map is moved to the entrance gate. So we only // need a tiny left joystick push. pbf_move_left_joystick(context, {-0.219, +0.219}, 100ms, 0ms); break; + default: + throw InternalProgramError( + nullptr, PA_CURRENT_FUNCTION, + "move_map_cursor_from_entrance_to_zone(): Unsupported zone location." + ); } pbf_wait(context, 300ms); } diff --git a/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_BasicNavigation.h b/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_BasicNavigation.h index 74911f2297..ca07ab7855 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_BasicNavigation.h +++ b/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_BasicNavigation.h @@ -8,7 +8,7 @@ #define PokemonAutomation_PokemonLZA_BasicNavigation_H #include "Common/Cpp/Time.h" -#include "PokemonLZA/Programs/PokemonLZA_Locations.h" +#include "PokemonLZA/Resources/PokemonLZA_Locations.h" namespace PokemonAutomation{ @@ -37,7 +37,8 @@ bool save_game_to_menu(ConsoleHandle& console, ProControllerContext& context); enum class FastTravelState{ SUCCESS, // Successfully did a fast travel PURSUED, // Being spotted and pursued by wild pokemon - NOT_AT_FLY_SPOT // the current map cursor is not on a fly spot + NOT_AT_FLY_SPOT, // the current map cursor is not on a fly spot + NOT_FOUND // Unable to find fast travel point, possibly not unlocked yet // Future work: in some main story session the fast travel is disabled. We will implement // that state if we need }; @@ -79,11 +80,15 @@ FastTravelState fly_from_map( // This is useful to fast travel back to the wild zone gate while in the zone. // This function basically just calls `open_map()` and `fly_from_map()`. See the comments of those two // functions for details. -FastTravelState open_map_and_fly_in_place(ConsoleHandle& console, ProControllerContext& context, bool zoom_to_max = false); +FastTravelState open_map_and_fly_in_place( + ConsoleHandle& console, + ProControllerContext& context, + bool zoom_to_max = false +); // Blind movement of map cursor from zone entrance to that zone fast travel icon on map // this blind movement only works on max zoom level (fully zoomed out)! -void move_map_cursor_from_entrance_to_zone(ConsoleHandle& console, ProControllerContext& context, WildZone zone); +void move_map_cursor_from_entrance_to_zone(ConsoleHandle& console, ProControllerContext& context, Location zone); // Mash button B to leave map view and back to overworld // If there is a day/night change, this function will wait for day/night change to finish. diff --git a/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_FastTravelNavigation.cpp b/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_FastTravelNavigation.cpp new file mode 100644 index 0000000000..fb6f3bb00a --- /dev/null +++ b/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_FastTravelNavigation.cpp @@ -0,0 +1,468 @@ +/* Fast Travel Navigation + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Tools/GlobalThreadPools.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "NintendoSwitch/NintendoSwitch_ConsoleHandle.h" +#include "PokemonLZA_FastTravelNavigation.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonLZA{ + + +const std::vector& FAST_TRAVEL_ARROW_BOXES(){ + static const std::vector boxes = { + {0.014000, 0.242000, 0.033000, 0.066000}, + {0.014000, 0.320000, 0.033000, 0.066000}, + {0.014000, 0.398000, 0.033000, 0.066000}, + {0.014000, 0.476000, 0.033000, 0.066000}, + {0.014000, 0.554000, 0.033000, 0.066000}, + {0.014000, 0.632000, 0.033000, 0.066000}, + {0.014000, 0.710000, 0.033000, 0.066000} + }; + return boxes; +} + +const std::vector& FAST_TRAVEL_FILTER_ARROW_BOXES(){ + static const std::vector boxes = { + {0.013000, 0.390000, 0.033000, 0.066000}, + {0.013000, 0.463000, 0.033000, 0.066000}, + {0.013000, 0.536000, 0.033000, 0.066000}, + {0.013000, 0.608000, 0.033000, 0.066000}, + {0.013000, 0.679000, 0.033000, 0.066000}, + {0.013000, 0.752000, 0.033000, 0.066000} + }; + return boxes; +} + +int get_current_selector_index( + ConsoleHandle& console, + const std::vector& arrow_boxes +){ + const ImageViewRGB32& screen = console.video().snapshot(); + for (size_t i = 0; i < arrow_boxes.size(); i++) { + SelectionArrowWatcher arrow( + COLOR_GREEN, + &console.overlay(), + SelectionArrowType::RIGHT, + arrow_boxes[i] + ); + if (arrow.detect(screen)) { + return static_cast(i); + } + } + return -1; +} + +bool should_navigate_down( + const LocationItem& current_selection, + const LocationItem& target_destination +){ + const size_t total_locations = LOCATION_ENUM_MAPPINGS().size(); + size_t down_distance = (target_destination.index + total_locations - current_selection.index) % total_locations; + size_t up_distance = (current_selection.index + total_locations - target_destination.index) % total_locations; + return down_distance <= up_distance; +} + +bool navigate_to_destination_page_in_fast_travel_menu( + ConsoleHandle& console, + ProControllerContext& context, + Language language, + const LocationItem& target_destination +){ + WallClock deadline = current_time() + 30s; + + do{ + LocationNameReader location_name_reader; + std::vector first_and_last_locations_on_page(2); + GlobalThreadPools::normal_inference().run_in_parallel( + [&](size_t index){ + OCR::StringMatchResult result = location_name_reader.read_location_name(console.video().snapshot(), console.logger(), language, index * (LocationNameReader::PAGE_SIZE - 1)); + result.clear_beyond_log10p(LocationNameOCR::MAX_LOG10P); + result.clear_beyond_spread(LocationNameOCR::MAX_LOG10P_SPREAD); + for (auto& item : result.results){ + first_and_last_locations_on_page[index] = get_location_item_from_slug(item.second.token); + } + }, + 0, 2 + ); + LocationItem top_location = first_and_last_locations_on_page[0]; + LocationItem bottom_location = first_and_last_locations_on_page[1]; + + // Workaround: lumiouse-sewers-1 and lumiouse-sewers-2 have the same OCR result, dont use them for paging + if (top_location.location == Location::LUMIOSE_SEWERS_1 || top_location.location == Location::LUMIOSE_SEWERS_2){ + OCR::StringMatchResult result = location_name_reader.read_location_name(console.video().snapshot(), console.logger(), language, 1); + result.clear_beyond_log10p(LocationNameOCR::MAX_LOG10P); + result.clear_beyond_spread(LocationNameOCR::MAX_LOG10P_SPREAD); + for (auto& item : result.results){ + top_location = get_location_item_from_slug(item.second.token); + } + } + if (bottom_location.location == Location::LUMIOSE_SEWERS_1 || bottom_location.location == Location::LUMIOSE_SEWERS_2){ + OCR::StringMatchResult result = location_name_reader.read_location_name(console.video().snapshot(), console.logger(), language, LocationNameReader::PAGE_SIZE - 2); + result.clear_beyond_log10p(LocationNameOCR::MAX_LOG10P); + result.clear_beyond_spread(LocationNameOCR::MAX_LOG10P_SPREAD); + for (auto& item : result.results){ + bottom_location = get_location_item_from_slug(item.second.token); + } + } + + if (target_destination.index >= top_location.index && target_destination.index <= bottom_location.index){ + console.log("Stopping at destination page between " + top_location.slug + " and " + bottom_location.slug); + return true; + } + bool navigate_down = should_navigate_down(top_location, target_destination); + if (navigate_down){ + pbf_press_button(context, BUTTON_RIGHT, 100ms, 500ms); + }else{ + pbf_press_button(context, BUTTON_LEFT, 100ms, 500ms); + } + context.wait_for_all_requests(); + } while (current_time() < deadline); + return false; +} + + +int get_target_location_index_within_page( + const LocationItem& target_destination, + const std::vector& current_page_locations +){ + for (size_t index = 0; index < current_page_locations.size(); index++){ + if (current_page_locations[index].slug == target_destination.slug){ + return static_cast(index); + } + } + return -1; +} + +bool navigate_to_destination_within_page( + ConsoleHandle& console, + ProControllerContext& context, + Language language, + const LocationItem& target_destination +){ + WallClock deadline = current_time() + 30s; + do{ + std::vector current_page_locations = read_current_page_location_items(console, language); + int target_index = get_target_location_index_within_page(target_destination, current_page_locations); + if (target_index == -1){ + return false; + } + int selector_index = get_current_selector_index(console, FAST_TRAVEL_ARROW_BOXES()); + if (selector_index == -1){ + return false; + } + // The target location can move due to how the game scrolls the list + if (selector_index == target_index){ + console.log("Found destination: " + target_destination.slug); + return true; + }else{ + int delta = target_index - selector_index; + for (; delta > 0; delta--){ + pbf_press_dpad(context, DPAD_DOWN, 100ms, 200ms); + } + for (; delta < 0; delta++){ + pbf_press_dpad(context, DPAD_UP, 100ms, 200ms); + } + context.wait_for_all_requests(); + } + } while (current_time() < deadline); + console.log("Timeout navigating to destination: " + target_destination.slug); + return false; +} + +bool navigate_to_lumiose_sewers_location( + ConsoleHandle& console, + ProControllerContext& context, + Language language, + const LocationItem& target_destination +){ + LocationNameReader location_name_reader; + // Use this box to detect cafe woof icon to differentiate between lumiose-sewers-1 and lumiose-sewers-2 + const ImageFloatBox& cafe_woof_box = {0.537000, 0.783000, 0.043000, 0.077000}; + // Zoom in for detection + for(int i = 0; i < 5; i++){ + pbf_move_right_joystick(context, {0, +1}, 100ms, 300ms); // Zoom in + } + // Set filter to facilities to reduce number of locations on the list + set_fast_travel_menu_filter(console, context, FAST_TRAVEL_FILTER::FACILITIES); + context.wait_for_all_requests(); + + WallClock deadline = current_time() + 60s; + do { + // Set selector to second to last spot + do { + int current_selector_index = get_current_selector_index(console, FAST_TRAVEL_ARROW_BOXES()); + if (current_selector_index == -1){ + return false; + } + if (current_selector_index == (LocationNameReader::PAGE_SIZE - 2)){ + break; + } + // Only move down + int delta = (LocationNameReader::PAGE_SIZE - 2) - current_selector_index; + for (; delta > 0; delta--){ + pbf_press_dpad(context, DPAD_DOWN, 100ms, 200ms); + } + context.wait_for_all_requests(); + } while (current_time() < deadline); + + // Read second to last spot only + LocationItem second_to_last_location; + OCR::StringMatchResult result = location_name_reader.read_location_name(console.video().snapshot(), console.logger(), language, LocationNameReader::PAGE_SIZE - 2); + result.clear_beyond_log10p(LocationNameOCR::MAX_LOG10P); + result.clear_beyond_spread(LocationNameOCR::MAX_LOG10P_SPREAD); + for (auto& item : result.results){ + second_to_last_location = get_location_item_from_slug(item.second.token); + } + + // lumiose-sewers-# in position + if (second_to_last_location.location == Location::LUMIOSE_SEWERS_1 || second_to_last_location.location == Location::LUMIOSE_SEWERS_2){ + console.log("Hovering over sewer location"); + MapIconDetector cafe_woof_icon(COLOR_ORANGE, MapIconType::CafeFlyable, cafe_woof_box, &console.overlay()); + // cafe woof icon detected means lumiose-sewers-1 + if (cafe_woof_icon.detect(console.video().snapshot()) && target_destination.location == Location::LUMIOSE_SEWERS_1){ + console.log("Lumiose Sewers 1 detected"); + for(int i = 0; i < 5; i++){ + pbf_move_right_joystick(context, {0, -1}, 100ms, 300ms); // Zoom out + } + context.wait_for_all_requests(); + return true; + } + // cafe woof icon not detected means lumiose-sewers-2 + // there are no unobstructable icons near lumiose-sewers-2 to use for detection + else if (!cafe_woof_icon.detect(console.video().snapshot()) && target_destination.location == Location::LUMIOSE_SEWERS_2){ + console.log("Lumiose Sewers 2 detected"); + for(int i = 0; i < 5; i++){ + pbf_move_right_joystick(context, {0, -1}, 100ms, 300ms); // Zoom out + } + context.wait_for_all_requests(); + return true; + } + } + pbf_press_dpad(context, DPAD_DOWN, 100ms, 200ms); + context.wait_for_all_requests(); + } while (current_time() < deadline); + console.log("Timeout navigating to sewers location: " + target_destination.slug); + return false; +} + +bool navigate_to_destination_in_fast_travel_menu( + ConsoleHandle& console, + ProControllerContext& context, + Language language, + const LocationItem& target_destination +){ + // lumiose-sewers-1 and lumiose-sewers-2 have the same OCR result, handle that case separately here + if (target_destination.location == Location::LUMIOSE_SEWERS_1 || target_destination.location == Location::LUMIOSE_SEWERS_2){ + return navigate_to_lumiose_sewers_location(console, context, language, target_destination); + } + bool reached_destination_page = navigate_to_destination_page_in_fast_travel_menu(console, context, language, target_destination); + if (!reached_destination_page){ + console.log("Unable to reach destination page for: " + target_destination.slug); + return false; + } + bool found_destination = navigate_to_destination_within_page(console, context, language, target_destination); + if (!found_destination){ + console.log("Unable to find destination on current page: " + target_destination.slug); + return false; + } + return true; +} + +void set_fast_travel_menu_filter( + ConsoleHandle& console, + ProControllerContext& context, + FAST_TRAVEL_FILTER filter +){ + SelectionArrowWatcher first_filter_arrow( + COLOR_YELLOW, + &console.overlay(), + SelectionArrowType::RIGHT, + FAST_TRAVEL_FILTER_ARROW_BOXES().at(0) + ); + int ret = run_until( + console, context, + [&](ProControllerContext& context){ + for(int i = 0; i < 4; i++){ + pbf_mash_button(context, BUTTON_MINUS, 1000ms); + } + }, + {first_filter_arrow} + ); + switch (ret){ + case 0: + console.log("Fast travel filter menu opened."); + break; + default: + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "set_fast_travel_menu_filter(): Unable to open fast travel filter menu.", + console + ); + } + + int target_filter_index = static_cast(filter); + WallClock deadline = current_time() + 30s; + do { + int selected_filter_index = get_current_selector_index(console, FAST_TRAVEL_FILTER_ARROW_BOXES()); + if (selected_filter_index == -1){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "set_fast_travel_menu_filter(): Unable to read current fast travel filter selection.", + console + ); + } + if (selected_filter_index == target_filter_index){ + SelectionArrowDetector selector_arrow_on_target( + COLOR_YELLOW, + &console.overlay(), + SelectionArrowType::RIGHT, + FAST_TRAVEL_FILTER_ARROW_BOXES().at(target_filter_index) + ); + for (size_t i = 0; i < 4; i++){ + pbf_press_button(context, BUTTON_A, 100ms, 1000ms); + context.wait_for_all_requests(); + bool arrow_present = selector_arrow_on_target.detect(console.video().snapshot()); + if (!arrow_present){ + console.log("Fast travel filter set."); + return; + } + } + } + + int delta = target_filter_index - selected_filter_index; + for (; delta > 0; delta--){ + pbf_press_dpad(context, DPAD_DOWN, 100ms, 200ms); + } + for (; delta < 0; delta--){ + pbf_press_dpad(context, DPAD_UP, 100ms, 200ms); + } + context.wait_for_all_requests(); + } while (current_time() < deadline); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "set_fast_travel_menu_filter(): Unable to set fast travel filter.", + console + ); +} + +void open_fast_travel_menu( + ConsoleHandle& console, + ProControllerContext& context +){ + MapOverWatcher map_over(COLOR_RED, &console.overlay()); + int ret = run_until( + console, context, + [&](ProControllerContext& context){ + pbf_press_button(context, BUTTON_Y, 160ms, 1000ms); + }, + {map_over} + ); + switch (ret){ + case 0: + console.log("Fast travel menu opened."); + return; + default: + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "open_fast_travel_menu(): Unable to open fast travel menu.", + console + ); + } +} + +std::vector read_current_page_location_items(ConsoleHandle& console, Language language){ + std::vector locations(LocationNameReader::PAGE_SIZE); + LocationNameReader location_name_reader; + GlobalThreadPools::normal_inference().run_in_parallel( + [&](size_t index){ + OCR::StringMatchResult result = location_name_reader.read_location_name(console.video().snapshot(), console.logger(), language, index); + result.clear_beyond_log10p(LocationNameOCR::MAX_LOG10P); + result.clear_beyond_spread(LocationNameOCR::MAX_LOG10P_SPREAD); + for (auto& item : result.results){ + locations[index] = get_location_item_from_slug(item.second.token); + console.log("Fast Travel Menu Item " + std::to_string(index) + ": " + item.second.token); + } + }, + 0, LocationNameReader::PAGE_SIZE + ); + if (locations.size() != LocationNameReader::PAGE_SIZE){ + console.log("Unable to read all location names in fast travel menu."); + } + return locations; +} + +FastTravelState open_map_and_fly_to(ConsoleHandle& console, ProControllerContext& context, Language language, Location location, bool zoom_to_max, bool clear_filters){ + bool can_fast_travel = open_map(console, context, zoom_to_max, true); + if (!can_fast_travel){ + return FastTravelState::PURSUED; + } + open_fast_travel_menu(console, context); + if (clear_filters){ + set_fast_travel_menu_filter(console, context, FAST_TRAVEL_FILTER::ALL_TRAVEL_SPOTS); + } + + LocationItem location_item = get_location_item_from_enum(location); + std::string target_slug = location_item.slug; + + console.log("Fast traveling to " + target_slug); + + bool success = navigate_to_destination_in_fast_travel_menu(console, context, language, location_item); + if (!success){ + return FastTravelState::NOT_FOUND; + } + + BlackScreenWatcher fly_confirmed(COLOR_RED); + BlueDialogWatcher pursued_warning(COLOR_BLUE, &console.overlay(), 50ms); + OverworldPartySelectionWatcher overworld(COLOR_WHITE, &console.overlay()); + int ret = run_until( + console, context, + [&](ProControllerContext& context){ + for(int i = 0; i < 4; i++){ + pbf_mash_button(context, BUTTON_A, 1000ms); + } + }, + {fly_confirmed, pursued_warning} + ); + switch (ret){ + case 0: + console.log("Fly confirmed to " + target_slug); + break; + case 1: + console.log("Pursued by wild pokemon while flying to " + target_slug); + return FastTravelState::PURSUED; + default: + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "open_map_and_fly_to(): Unable to fast travel to " + target_slug, + console + ); + } + ret = wait_until( + console, context, 30s, // 30s to load overworld on Switch 1 + {overworld} + ); + switch(ret){ + case 0: + console.log("Arrived at " + target_slug); + return FastTravelState::SUCCESS; + default: + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "open_map_and_fly_to(): Overworld not detected", + console + ); + } + return FastTravelState::NOT_FOUND; +} + +} +} +} \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_FastTravelNavigation.h b/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_FastTravelNavigation.h new file mode 100644 index 0000000000..1ef2d33f96 --- /dev/null +++ b/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_FastTravelNavigation.h @@ -0,0 +1,141 @@ +/* Fast Travel Navigation + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonLZA_FastTravelNavigation_H +#define PokemonAutomation_PokemonLZA_FastTravelNavigation_H + +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "PokemonLZA_BasicNavigation.h" +#include "PokemonLZA/Inference/Map/PokemonLZA_MapIconDetector.h" +#include "PokemonLZA/Inference/Map/PokemonLZA_MapDetector.h" +#include "PokemonLZA/Inference/Map/PokemonLZA_LocationNameReader.h" +#include "PokemonLZA/Inference/PokemonLZA_DialogDetector.h" +#include "PokemonLZA/Inference/PokemonLZA_OverworldPartySelectionDetector.h" +#include "PokemonLZA/Inference/PokemonLZA_SelectionArrowDetector.h" +#include "PokemonLZA/Resources/PokemonLZA_Locations.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonLZA{ + + + +// The arrow box positions for fast travel menu items +const std::vector& FAST_TRAVEL_ARROW_BOXES(); + +// The arrow box positions for the fast travel menu filter +const std::vector& FAST_TRAVEL_FILTER_ARROW_BOXES(); + +// Get the index of the currently selected item in arrow_boxes using the SelectionArrowDetector +// Return -1 if unable to determine the current selector index +int get_current_selector_index( + ConsoleHandle& console, + const std::vector& arrow_boxes +); + +// Determine whether it's better to navigate up or down in the fast travel menu +bool should_navigate_down( + const LocationItem& current_selection, + const LocationItem& target_destination +); + +// Navigate pages in the fast travel menu via dpad left and right to find the page containing the target destination +// What locations are on a page can depend on filters and if a location is unlocked or not +// Return true when the page should contain the target destination based on indexes +// Return false if no pages contain the target index in their range after checking all pages +// This function does not own the validation of whether the target destination exists or not +bool navigate_to_destination_page_in_fast_travel_menu( + ConsoleHandle& console, + ProControllerContext& context, + Language language, + LocationItem& target_destination +); + +// Get the index of the target destination in the current page's location items +// Return -1 if not found +int get_target_location_index_within_page( + const LocationItem& target_destination, + const std::vector& current_page_locations +); + +// Navigate to the target destination assuming we are on the correct page already +// Return true if the target destination is found, hover over the option +// Return false if the target destination is not found +bool navigate_to_destination_within_page( + ConsoleHandle& console, + ProControllerContext& context, + Language language, + const LocationItem& target_destination +); + +// Special handling for lumiouse-sewers-1 and lumiouse-sewers-2 since they have the same OCR result +// Disambiguate using a detector on the background map +// Return true if the target destination is found, hover over the option +// Return false if the target destination is not found +bool navigate_to_lumiose_sewers_location( + ConsoleHandle& console, + ProControllerContext& context, + Language language, + const LocationItem& target_destination +); + +// Navigate to the target destination in the fast travel menu +// Find the correct page first with navigate_to_destination_page_in_fast_travel_menu() +// Then validate whether the target destination exists on the current page +// Return true if the target destination is found on the current page, hover over the option +// Return false if the target destination is not found on the current page +bool navigate_to_destination_in_fast_travel_menu( + ConsoleHandle& console, + ProControllerContext& context, + Language language, + const LocationItem& target_destination +); + +// From the fast travel menu, set the fast travel menu filter to the specified option +void set_fast_travel_menu_filter( + ConsoleHandle& console, + ProControllerContext& context, + FAST_TRAVEL_FILTER filter +); + +// From the map screen, open the fast travel menu +void open_fast_travel_menu( + ConsoleHandle& console, + ProControllerContext& context +); + +// With the fast travel menu opened, read all visible locations in the menu +// Use parallelized OCR to read the 7 items +// Use the dictionary to get the information for each given display name +// Returns a list of LocationItem in the order that they appear +std::vector read_current_page_location_items( + ConsoleHandle& console, + Language language +); + +// Open map and fast travel to a specified location. +// This function calls `open_map()`, and uses the fast travel menu to fast travel to the location. +// Assumes the filter is set to show all available fast travel locations. +// Return FastTravelState: +// - SUCCESS: fast travel successful. After the function returns, the player character is on the overworld +// - PURSUED: spotted and pursued by wild pokemon, cannot fast travel. After the function returns, the game +// is in fly map with the spotted dialog. +// - NOT_FOUND: unable to find the specified location on map, possibly not unlocked yet. After the function +// returns, the game is in fly map. +FastTravelState open_map_and_fly_to( + ConsoleHandle& console, + ProControllerContext& context, + Language language, + Location location, + bool zoom_to_max = false, + bool clear_filters = false +); + +} +} +} +#endif \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_Locations.h b/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_Locations.h deleted file mode 100644 index 113b4c872f..0000000000 --- a/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_Locations.h +++ /dev/null @@ -1,48 +0,0 @@ -/* Pokemon Legends Z-A Locations - * - * From: https://github.com/PokemonAutomation/ - * - */ - -#ifndef PokemonAutomation_PokemonLZA_Locations_H -#define PokemonAutomation_PokemonLZA_Locations_H - -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonLZA{ - -enum class WildZone{ - WILD_ZONE_1, - WILD_ZONE_2, - WILD_ZONE_3, - WILD_ZONE_4, - WILD_ZONE_5, - WILD_ZONE_6, - WILD_ZONE_7, - WILD_ZONE_8, - WILD_ZONE_9, - WILD_ZONE_10, - WILD_ZONE_11, - WILD_ZONE_12, - WILD_ZONE_13, - WILD_ZONE_14, - WILD_ZONE_15, - WILD_ZONE_16, - WILD_ZONE_17, - WILD_ZONE_18, - WILD_ZONE_19, - WILD_ZONE_20_NO_DISTORTION, - WILD_ZONE_20_WITH_DISTORTION, -}; - -enum class WildZoneCafe{ - CAFE_BATAILLE, - CAFE_ULTIMO, -}; - - - -} -} -} -#endif diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp index 3a6aaf83e6..1973e58d54 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp @@ -19,7 +19,6 @@ #include "PokemonLZA/Inference/PokemonLZA_WeatherDetector.h" #include "PokemonLZA_ShinyHunt_HelioptileHunter.h" #include "PokemonLZA/Programs/PokemonLZA_BasicNavigation.h" -#include "PokemonLZA/Programs/PokemonLZA_Locations.h" #include #include @@ -207,7 +206,7 @@ void ShinyHunt_HelioptileHunter::program(SingleSwitchProgramEnvironment& env, Pr hunt_loops = 0; } else { env.log("Correct weather. Continuing"); - move_map_cursor_from_entrance_to_zone(env.console, context, WildZone::WILD_ZONE_14); + move_map_cursor_from_entrance_to_zone(env.console, context, Location::WILD_ZONE_14); fly_from_map(env.console, context); } diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_WildZoneCafe.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_WildZoneCafe.cpp index b70973614b..d0be8f477e 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_WildZoneCafe.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_WildZoneCafe.cpp @@ -74,11 +74,11 @@ ShinyHunt_WildZoneCafe::ShinyHunt_WildZoneCafe() : CAFE( "Caf\u00e9:", { - {WildZoneCafe::CAFE_BATAILLE, "cafe-bataille", "Wild Zone 6 - Caf\u00e9 Bataille"}, - {WildZoneCafe::CAFE_ULTIMO, "cafe-ultimo", "Wild Zone 15 - Caf\u00e9 Ultimo"}, + {Location::CAFE_BATAILLE, "cafe-bataille", "Wild Zone 6 - Caf\u00e9 Bataille"}, + {Location::CAFE_ULTIMO, "cafe-ultimo", "Wild Zone 15 - Caf\u00e9 Ultimo"}, }, LockMode::LOCK_WHILE_RUNNING, - WildZoneCafe::CAFE_BATAILLE + Location::CAFE_BATAILLE ) , NUM_VISITS("Number of Visits:
Stop after this many visits. 0 means no limit.", LockMode::UNLOCK_WHILE_RUNNING, 0) , SHINY_DETECTED("Shiny Detected", "", "2000 ms", ShinySoundDetectedAction::NOTIFY_ON_FIRST_ONLY) @@ -102,7 +102,7 @@ ShinyHunt_WildZoneCafe::ShinyHunt_WildZoneCafe() void do_one_cafe_trip( SingleSwitchProgramEnvironment& env, ProControllerContext& context, - WildZoneCafe cafe, + Location cafe, ShinySoundHandler& shiny_sound_handler, bool to_zoom_to_max ){ @@ -127,14 +127,19 @@ void do_one_cafe_trip( if (can_fast_travel){ pbf_wait(context, 300ms); switch(cafe){ - case WildZoneCafe::CAFE_BATAILLE: + case Location::CAFE_BATAILLE: env.log("Move to Cafe Bataille icon"); pbf_move_left_joystick(context, {+0.157, +0.844}, 100ms, 0ms); break; - case WildZoneCafe::CAFE_ULTIMO: + case Location::CAFE_ULTIMO: env.log("Move to Cafe Ultimo icon"); pbf_move_left_joystick(context, {-0.609, +0.219}, 100ms, 0ms); break; + default: + throw InternalProgramError( + nullptr, PA_CURRENT_FUNCTION, + "do_one_cafe_trip(): Unsupported cafe location." + ); } pbf_wait(context, 300ms); FastTravelState travel_status = fly_from_map(env.console, context); @@ -167,14 +172,19 @@ void do_one_cafe_trip( double move_x = -1, move_y = +1; switch(cafe){ - case WildZoneCafe::CAFE_BATAILLE: + case Location::CAFE_BATAILLE: env.log("Move to zone gate from Cafe Bataille"); move_x = +1; move_y = -0.25; break; - case WildZoneCafe::CAFE_ULTIMO: + case Location::CAFE_ULTIMO: env.log("Move to zone gate from Cafe Ultimo"); move_x = 0; move_y = -1; break; + default: + throw InternalProgramError( + nullptr, PA_CURRENT_FUNCTION, + "do_one_cafe_trip(): Unsupported cafe location." + ); } int ret = run_towards_gate_with_A_button(env.console, context, move_x, move_y, Seconds(10)); @@ -243,14 +253,19 @@ void do_one_cafe_trip( // move map cursor to cafe: pbf_wait(context, 300ms); switch(cafe){ - case WildZoneCafe::CAFE_BATAILLE: + case Location::CAFE_BATAILLE: env.log("Move to Cafe Bataille icon from zone gate"); pbf_move_left_joystick(context, {-0.25, -0.252}, 100ms, 0ms); break; - case WildZoneCafe::CAFE_ULTIMO: + case Location::CAFE_ULTIMO: env.log("Move to Cafe Ultimo icon from zone gate"); pbf_move_left_joystick(context, {+0.488, -0.126}, 100ms, 0ms); break; + default: + throw InternalProgramError( + nullptr, PA_CURRENT_FUNCTION, + "do_one_cafe_trip(): Unsupported cafe location." + ); } pbf_wait(context, 300ms); diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_WildZoneCafe.h b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_WildZoneCafe.h index a4a6a16e48..5426c1ad58 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_WildZoneCafe.h +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_WildZoneCafe.h @@ -33,7 +33,7 @@ class ShinyHunt_WildZoneCafe : public SingleSwitchProgramInstance{ virtual void program(SingleSwitchProgramEnvironment& env, ProControllerContext& context) override; private: - EnumDropdownOption CAFE; + EnumDropdownOption CAFE; PokemonLA::ShinyRequiresAudioText SHINY_REQUIRES_AUDIO; SimpleIntegerOption NUM_VISITS; diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_WildZoneEntrance.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_WildZoneEntrance.cpp index 7d629035b0..26d8d9d1e0 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_WildZoneEntrance.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_WildZoneEntrance.cpp @@ -34,33 +34,33 @@ namespace PokemonAutomation::NintendoSwitch::PokemonLZA { using namespace Pokemon; WildZoneOption::WildZoneOption() - : EnumDropdownOption( + : EnumDropdownOption( "Wild Zone:", { - {WildZone::WILD_ZONE_1, "wild-zone-1", "Wild Zone 1"}, - {WildZone::WILD_ZONE_2, "wild-zone-2", "Wild Zone 2"}, - {WildZone::WILD_ZONE_3, "wild-zone-3", "Wild Zone 3"}, - {WildZone::WILD_ZONE_4, "wild-zone-4", "Wild Zone 4"}, - {WildZone::WILD_ZONE_5, "wild-zone-5", "Wild Zone 5"}, - {WildZone::WILD_ZONE_6, "wild-zone-6", "Wild Zone 6"}, - {WildZone::WILD_ZONE_7, "wild-zone-7", "Wild Zone 7"}, - {WildZone::WILD_ZONE_8, "wild-zone-8", "Wild Zone 8"}, - {WildZone::WILD_ZONE_9, "wild-zone-9", "Wild Zone 9"}, - {WildZone::WILD_ZONE_10, "wild-zone-10", "Wild Zone 10"}, - {WildZone::WILD_ZONE_11, "wild-zone-11", "Wild Zone 11"}, - {WildZone::WILD_ZONE_12, "wild-zone-12", "Wild Zone 12"}, - {WildZone::WILD_ZONE_13, "wild-zone-13", "Wild Zone 13"}, - {WildZone::WILD_ZONE_14, "wild-zone-14", "Wild Zone 14"}, - {WildZone::WILD_ZONE_15, "wild-zone-15", "Wild Zone 15"}, - {WildZone::WILD_ZONE_16, "wild-zone-16", "Wild Zone 16"}, - {WildZone::WILD_ZONE_17, "wild-zone-17", "Wild Zone 17"}, - {WildZone::WILD_ZONE_18, "wild-zone-18", "Wild Zone 18"}, - {WildZone::WILD_ZONE_19, "wild-zone-19", "Wild Zone 19"}, - {WildZone::WILD_ZONE_20_NO_DISTORTION, "wild-zone-20", "Wild Zone 20 Without Distortion"}, - {WildZone::WILD_ZONE_20_WITH_DISTORTION, "wild-zone-20-distortion", "Wild Zone 20 With Distortion"}, + {Location::WILD_ZONE_1, "wild-zone-1", "Wild Zone 1"}, + {Location::WILD_ZONE_2, "wild-zone-2", "Wild Zone 2"}, + {Location::WILD_ZONE_3, "wild-zone-3", "Wild Zone 3"}, + {Location::WILD_ZONE_4, "wild-zone-4", "Wild Zone 4"}, + {Location::WILD_ZONE_5, "wild-zone-5", "Wild Zone 5"}, + {Location::WILD_ZONE_6, "wild-zone-6", "Wild Zone 6"}, + {Location::WILD_ZONE_7, "wild-zone-7", "Wild Zone 7"}, + {Location::WILD_ZONE_8, "wild-zone-8", "Wild Zone 8"}, + {Location::WILD_ZONE_9, "wild-zone-9", "Wild Zone 9"}, + {Location::WILD_ZONE_10, "wild-zone-10", "Wild Zone 10"}, + {Location::WILD_ZONE_11, "wild-zone-11", "Wild Zone 11"}, + {Location::WILD_ZONE_12, "wild-zone-12", "Wild Zone 12"}, + {Location::WILD_ZONE_13, "wild-zone-13", "Wild Zone 13"}, + {Location::WILD_ZONE_14, "wild-zone-14", "Wild Zone 14"}, + {Location::WILD_ZONE_15, "wild-zone-15", "Wild Zone 15"}, + {Location::WILD_ZONE_16, "wild-zone-16", "Wild Zone 16"}, + {Location::WILD_ZONE_17, "wild-zone-17", "Wild Zone 17"}, + {Location::WILD_ZONE_18, "wild-zone-18", "Wild Zone 18"}, + {Location::WILD_ZONE_19, "wild-zone-19", "Wild Zone 19"}, + {Location::WILD_ZONE_20_NO_DISTORTION, "wild-zone-20", "Wild Zone 20 Without Distortion"}, + {Location::WILD_ZONE_20_WITH_DISTORTION, "wild-zone-20-distortion", "Wild Zone 20 With Distortion"}, }, LockMode::LOCK_WHILE_RUNNING, - WildZone::WILD_ZONE_1 + Location::WILD_ZONE_1 ) {} @@ -169,11 +169,11 @@ void ShinyHunt_WildZoneEntrance::on_config_value_changed(void* object){ void go_to_entrance( SingleSwitchProgramEnvironment& env, ProControllerContext& context, - WildZone wildzone + Location wildzone ){ double starting_direction = 0; double joystick_x = 0; - if (wildzone == WildZone::WILD_ZONE_1){ + if (wildzone == Location::WILD_ZONE_1){ // The fast travel point of Wild Zone 1 does not face the zone entrance directly. We need // to turn a little bit to the right starting_direction = get_facing_direction(env.console, context); @@ -184,7 +184,7 @@ void go_to_entrance( case 0: // detected button A. Reached gate break; case 1: // day/night change happened. - if (wildzone == WildZone::WILD_ZONE_1 && + if (wildzone == Location::WILD_ZONE_1 && get_angle_between_facing_directions(starting_direction, get_facing_direction(env.console, context)) > 2.5){ joystick_x = 0; // we've already turned. Just need to go forward to enter the zone } @@ -210,7 +210,7 @@ void go_to_entrance( void fast_travel_outside_zone( SingleSwitchProgramEnvironment& env, ProControllerContext& context, - WildZone wild_zone, + Location wild_zone, bool to_max_zoom_level_on_map, std::string extra_error_msg = "", bool map_already_opened = false @@ -256,7 +256,7 @@ void leave_zone_and_reset_spawns( SingleSwitchProgramEnvironment& env, ProControllerContext& context, Milliseconds walk_time_in_zone, - WildZone wild_zone, + Location wild_zone, ShinySoundHandler& shiny_sound_handler, bool to_max_zoom_level_on_map ){ @@ -438,7 +438,7 @@ void do_one_wild_zone_trip( size_t movement_mode, Milliseconds walk_time_in_zone, bool running, - WildZone wild_zone, + Location wild_zone, ShinySoundHandler& shiny_sound_handler, bool to_max_zoom_level_on_map ){ diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_WildZoneEntrance.h b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_WildZoneEntrance.h index 5c827d705e..21fe2ae294 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_WildZoneEntrance.h +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_WildZoneEntrance.h @@ -13,12 +13,12 @@ #include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" #include "PokemonLA/Options/PokemonLA_ShinyDetectedAction.h" #include "PokemonLZA/Options/PokemonLZA_ShinyDetectedAction.h" -#include "PokemonLZA/Programs/PokemonLZA_Locations.h" +#include "PokemonLZA/Resources/PokemonLZA_Locations.h" namespace PokemonAutomation::NintendoSwitch::PokemonLZA { -class WildZoneOption : public EnumDropdownOption{ +class WildZoneOption : public EnumDropdownOption{ public: WildZoneOption(); }; diff --git a/SerialPrograms/Source/PokemonLZA/Resources/PokemonLZA_Locations.cpp b/SerialPrograms/Source/PokemonLZA/Resources/PokemonLZA_Locations.cpp new file mode 100644 index 0000000000..9048026631 --- /dev/null +++ b/SerialPrograms/Source/PokemonLZA/Resources/PokemonLZA_Locations.cpp @@ -0,0 +1,221 @@ +/* Locations + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "Common/Cpp/Json/JsonValue.h" +#include "Common/Cpp/Json/JsonArray.h" +#include "Common/Cpp/Json/JsonObject.h" +#include "CommonFramework/Globals.h" +#include "PokemonLZA_Locations.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonLZA{ + +struct LocationNameDatabase{ +public: + LocationNameDatabase(); + + static const LocationNameDatabase& instance(){ + static LocationNameDatabase database; + return database; + } + + std::map database; + std::map reverse_lookup; + std::vector slugs; +private: + void create_database(const std::string& path); + void create_reverse_lookup(const std::string& path); +}; + +// Form the location name database with all languages, mapping slugs to display names by parsing the JSON +// The JSON resembles the following: +// "eng": { +// "location-1-slug": [ "Location 1 Display Name" ], +// ... +// }, +// ... +LocationNameDatabase::LocationNameDatabase(){ + std::string path = RESOURCE_PATH() + "PokemonLZA/LocationName.json"; + create_database(path); + create_reverse_lookup(path); +} + +// Create the database that maps slugs to display names in various languages +void LocationNameDatabase::create_database(const std::string& path){ + // Load the JSON file containing location names in all languages from Resources area + JsonValue json = load_json_file(path); + JsonObject& object = json.to_object_throw(path); // Whole JSON object + // Iterate through each language in the whole JSON + for (const auto& language_block : object){ + Language language = language_code_to_enum(language_block.first); + const JsonObject& language_object = language_block.second.to_object_throw(path); + // For each language, iterate through each slug in the respective language block + for (const auto& slug : language_object){ + // Each display name is wrapped in an array, so the display name is extracted from the array + const JsonArray& names = slug.second.to_array_throw(path); + if (names.empty()){ + throw JsonParseException(path, "Expected at least one name for: " + language_block.first + " : " + slug.first); + } + database[slug.first].m_display_names[language] = names[0].to_string_throw(path); + } + } +} + +// Create the reverse lookup table that allows for getting slugs from display names +// Also register each slug into a vector +void LocationNameDatabase::create_reverse_lookup(const std::string& path){ + // Set the English display name as the default display name for each location + // Build a reverse lookup table using the English display name + for (auto& item : database){ + auto iter = item.second.m_display_names.find(Language::English); + if (iter == item.second.m_display_names.end()){ + throw JsonParseException(path, "Expected English name for location slug: " + item.first); + } + item.second.m_display_name = iter->second; + // reverse_lookup[iter->second] = item.first; + slugs.push_back(item.first); + } +} + +// Get the Locations object given its slug using the database +const Locations& get_location_name(const std::string& slug){ + const std::map& database = LocationNameDatabase::instance().database; + auto iter = database.find(slug); + if (iter == database.end()){ + throw InternalProgramError( + nullptr, PA_CURRENT_FUNCTION, + "Location slug not found in database: " + slug + ); + } + return iter->second; +} + +// Using the reverse lookup table, get the slug given the English display name +const std::string& parse_location_name(const std::string& display_name){ + const std::map& database = LocationNameDatabase::instance().reverse_lookup; + auto iter = database.find(display_name); + if (iter == database.end()){ + throw InternalProgramError( + nullptr, PA_CURRENT_FUNCTION, + "Location name not found in database: " + display_name + ); + } + return iter->second; +} + +// Get all location slugs in a vector +const std::vector& LOCATION_SLUGS(){ + return LocationNameDatabase::instance().slugs; +} + +// Mapping between location enum, slug, and index in menu +const std::vector& LOCATION_ENUM_MAPPINGS(){ + static const std::vector database{ + {Location::CENTRICO_PLAZA, "centrico-plaza", 0}, + {Location::GARE_DE_LUMIOSE, "gare-de-lumiose", 1}, + {Location::POKEMON_RESEARCH_LAB, "pokemon-research-lab", 2}, + {Location::HOTEL_Z, "hotel-z", 3}, + {Location::VERT_POKEMON_CENTER, "vert-pokemon-center", 4}, + {Location::RACINE_CONSTRUCTION, "racine-construction", 5}, + {Location::RESTAURANT_LE_NAH, "restaurant-le-nah", 6}, + {Location::CAFE_CYCLONE, "cafe-cyclone", 7}, + {Location::CAFE_CLASSE, "cafe-classe", 8}, + {Location::CAFE_INTROVERSION, "cafe-introversion", 9}, + {Location::NOUVEAU_CAFE, "nouveau-cafe", 10}, + {Location::RUST_SYNDICATE_OFFICE, "rust-syndicate-office", 11}, + {Location::LUMIOSE_SEWERS_1, "lumiose-sewers-1", 12}, + {Location::BLEU_POKEMON_CENTER, "bleu-pokemon-center", 13}, + {Location::VERNAL_POKEMON_CENTER, "vernal-pokemon-center", 14}, + {Location::CAFE_WOOF, "cafe-woof", 15}, + {Location::CAFE_SOLEIL, "cafe-soleil", 16}, + {Location::SHUTTERBUG_CAFE, "shutterbug-cafe", 17}, + {Location::NOUVEAU_CAFE_TRUCK_NO_2, "nouveau-cafe-truck-no-2", 18}, + {Location::QUASARTICO_INC, "quasartico-inc", 19}, + {Location::LYSANDRE_CAFE, "lysandre-cafe", 20}, + {Location::LUMIOSE_SEWERS_2, "lumiose-sewers-2", 21}, + {Location::MAGENTA_POKEMON_CENTER, "magenta-pokemon-center", 22}, + {Location::MAGENTA_PLAZA_POKEMON_CENTER, "magenta-plaza-pokemon-center", 23}, + {Location::CAFE_POKEMON_AMIE, "cafe-pokemon-amie", 24}, + {Location::CAFE_ROULEAU, "cafe-rouleau", 25}, + {Location::CAFE_GALLANT, "cafe-gallant", 26}, + {Location::CAFE_TRISTE, "cafe-triste", 27}, + {Location::NOUVEAU_CAFE_TRUCK_NO_3, "nouveau-cafe-truck-no-3", 28}, + {Location::HOTEL_RICHISSIME, "hotel-richissime", 29}, + {Location::LOOKER_BUREAU, "looker-bureau", 30}, + {Location::LUMIOSE_MUSEUM, "lumiose-museum", 31}, + {Location::ROUGE_POKEMON_CENTER, "rouge-pokemon-center", 32}, + {Location::CENTRICO_POKEMON_CENTER, "centrico-pokemon-center", 33}, + {Location::RESTAURANT_LE_YEAH, "restaurant-le-yeah", 34}, + {Location::SUSHI_HIGH_ROLLER, "sushi-high-roller", 35}, + {Location::RESTAURANT_LE_WOW, "restaurant-le-wow", 36}, + {Location::JUSTICE_DOJO, "justice-dojo", 37}, + {Location::JAUNE_POKEMON_CENTER, "jaune-pokemon-center", 38}, + {Location::HIBERNAL_POKEMON_CENTER, "hibernal-pokemon-center", 39}, + {Location::CAFE_ULTIMO, "cafe-ultimo", 40}, + {Location::CAFE_ACTION, "cafe-action", 41}, + {Location::CAFE_KIZUNA, "cafe-kizuna", 42}, + {Location::CAFE_BATAILLE, "cafe-bataille", 43}, + {Location::WILD_ZONE_1, "wild-zone-1", 44}, + {Location::WILD_ZONE_2, "wild-zone-2", 45}, + {Location::WILD_ZONE_3, "wild-zone-3", 46}, + {Location::WILD_ZONE_4, "wild-zone-4", 47}, + {Location::WILD_ZONE_5, "wild-zone-5", 48}, + {Location::WILD_ZONE_6, "wild-zone-6", 49}, + {Location::WILD_ZONE_7, "wild-zone-7", 50}, + {Location::WILD_ZONE_8, "wild-zone-8", 51}, + {Location::WILD_ZONE_9, "wild-zone-9", 52}, + {Location::WILD_ZONE_10, "wild-zone-10", 53}, + {Location::WILD_ZONE_11, "wild-zone-11", 54}, + {Location::WILD_ZONE_12, "wild-zone-12", 55}, + {Location::WILD_ZONE_13, "wild-zone-13", 56}, + {Location::WILD_ZONE_14, "wild-zone-14", 57}, + {Location::WILD_ZONE_15, "wild-zone-15", 58}, + {Location::WILD_ZONE_16, "wild-zone-16", 59}, + {Location::WILD_ZONE_17, "wild-zone-17", 60}, + {Location::WILD_ZONE_18, "wild-zone-18", 61}, + {Location::WILD_ZONE_19, "wild-zone-19", 62}, + {Location::WILD_ZONE_20_NO_DISTORTION, "wild-zone-20", 63}, + {Location::WILD_ZONE_20_WITH_DISTORTION, "wild-zone-20", 63}, + {Location::BATTLE_ZONE_1, "battle-zone-1", 64}, + {Location::BATTLE_ZONE_2, "battle-zone-2", 65}, + {Location::BATTLE_ZONE_3, "battle-zone-3", 66}, + {Location::HYPERSPACE_ENTRY_POINT, "hyperspace-entry-point", 0}, // Not a valid slug currently + }; + return database; +} + +// Get the whole LocationItem given a Location enum +const LocationItem& get_location_item_from_enum(const Location location){ + const std::vector& database = LOCATION_ENUM_MAPPINGS(); + for (const LocationItem& item : database){ + if (item.location == location){ + return item; + } + } + throw InternalProgramError( + nullptr, PA_CURRENT_FUNCTION, + "Location enumnot found in database: Enum #" + std::to_string((int)location) + ); +} + +// Get the whole LocationItem given a slug +const LocationItem& get_location_item_from_slug(const std::string& slug){ + const std::vector& database = LOCATION_ENUM_MAPPINGS(); + for (const LocationItem& item : database){ + if (item.slug == slug){ + return item; + } + } + throw InternalProgramError( + nullptr, PA_CURRENT_FUNCTION, + "Location slug not found in database: " + slug + ); +} + +} +} +} \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonLZA/Resources/PokemonLZA_Locations.h b/SerialPrograms/Source/PokemonLZA/Resources/PokemonLZA_Locations.h new file mode 100644 index 0000000000..bc0422cefb --- /dev/null +++ b/SerialPrograms/Source/PokemonLZA/Resources/PokemonLZA_Locations.h @@ -0,0 +1,134 @@ +/* Locations + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonLZA_Locations_H +#define PokemonAutomation_PokemonLZA_Locations_H + +#include +#include +#include "CommonFramework/Language.h" +#include "Common/Cpp/EnumStringMap.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonLZA{ + +enum class Location{ + CENTRICO_PLAZA, + GARE_DE_LUMIOSE, + POKEMON_RESEARCH_LAB, + HOTEL_Z, + VERT_POKEMON_CENTER, + RACINE_CONSTRUCTION, + RESTAURANT_LE_NAH, + CAFE_CYCLONE, + CAFE_CLASSE, + CAFE_INTROVERSION, + NOUVEAU_CAFE, + RUST_SYNDICATE_OFFICE, + LUMIOSE_SEWERS_1, + BLEU_POKEMON_CENTER, + VERNAL_POKEMON_CENTER, + CAFE_WOOF, + CAFE_SOLEIL, + SHUTTERBUG_CAFE, + NOUVEAU_CAFE_TRUCK_NO_2, + QUASARTICO_INC, + LYSANDRE_CAFE, + LUMIOSE_SEWERS_2, + MAGENTA_POKEMON_CENTER, + MAGENTA_PLAZA_POKEMON_CENTER, + CAFE_POKEMON_AMIE, + CAFE_ROULEAU, + CAFE_GALLANT, + CAFE_TRISTE, + NOUVEAU_CAFE_TRUCK_NO_3, + HOTEL_RICHISSIME, + LOOKER_BUREAU, + LUMIOSE_MUSEUM, + ROUGE_POKEMON_CENTER, + CENTRICO_POKEMON_CENTER, + RESTAURANT_LE_YEAH, + SUSHI_HIGH_ROLLER, + RESTAURANT_LE_WOW, + JUSTICE_DOJO, + JAUNE_POKEMON_CENTER, + HIBERNAL_POKEMON_CENTER, + CAFE_ULTIMO, + CAFE_ACTION, + CAFE_KIZUNA, + CAFE_BATAILLE, + WILD_ZONE_1, + WILD_ZONE_2, + WILD_ZONE_3, + WILD_ZONE_4, + WILD_ZONE_5, + WILD_ZONE_6, + WILD_ZONE_7, + WILD_ZONE_8, + WILD_ZONE_9, + WILD_ZONE_10, + WILD_ZONE_11, + WILD_ZONE_12, + WILD_ZONE_13, + WILD_ZONE_14, + WILD_ZONE_15, + WILD_ZONE_16, + WILD_ZONE_17, + WILD_ZONE_18, + WILD_ZONE_19, + WILD_ZONE_20_NO_DISTORTION, + WILD_ZONE_20_WITH_DISTORTION, + BATTLE_ZONE_1, + BATTLE_ZONE_2, + BATTLE_ZONE_3, + HYPERSPACE_ENTRY_POINT +}; + +enum class FAST_TRAVEL_FILTER{ + ALL_TRAVEL_SPOTS, + FACILITIES, + POKEMON_CENTERS, + CAFES, + ZONES +}; + +struct LocationItem{ + Location location; + std::string slug; + uint8_t index; +}; + +class Locations{ +public: + const std::string& display_name() const { return m_display_name; } +private: + friend struct LocationNameDatabase; + + // The english in-game name of a location + std::string m_display_name; + // A map between a language and the location in-game name in that language + std::map m_display_names; +}; + +// Search the LocationNameDatabase for a Locations object using its slug +const Locations& get_location_name(const std::string& slug); + +// Search for the slug of a location given its English display name +const std::string& parse_location_name(const std::string& display_name); + +// A list of all location slugs +const std::vector& LOCATION_SLUGS(); + +// Map between Location enums and the slug for ease-of-use elsewhere +const std::vector& LOCATION_ENUM_MAPPINGS(); +const LocationItem& get_location_item_from_enum(const Location location); +const LocationItem& get_location_item_from_slug(const std::string& slug); + +} +} +} +#endif \ No newline at end of file diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index beef342af8..838db64ff7 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1714,6 +1714,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonLZA/Programs/NonShinyHunting/PokemonLZA_WeatherFinder.h Source/PokemonLZA/Programs/PokemonLZA_BasicNavigation.cpp Source/PokemonLZA/Programs/PokemonLZA_BasicNavigation.h + Source/PokemonLZA/Programs/PokemonLZA_FastTravelNavigation.cpp + Source/PokemonLZA/Programs/PokemonLZA_FastTravelNavigation.h Source/PokemonLZA/Programs/PokemonLZA_HyperspaceNavigation.cpp Source/PokemonLZA/Programs/PokemonLZA_HyperspaceNavigation.h Source/PokemonLZA/Programs/PokemonLZA_BoxSorter.cpp @@ -1724,7 +1726,6 @@ file(GLOB LIBRARY_SOURCES Source/PokemonLZA/Programs/PokemonLZA_DonutBerrySession.h Source/PokemonLZA/Programs/PokemonLZA_GameEntry.cpp Source/PokemonLZA/Programs/PokemonLZA_GameEntry.h - Source/PokemonLZA/Programs/PokemonLZA_Locations.h Source/PokemonLZA/Programs/PokemonLZA_MegaShardFarmer.cpp Source/PokemonLZA/Programs/PokemonLZA_MegaShardFarmer.h Source/PokemonLZA/Programs/PokemonLZA_MenuNavigation.cpp @@ -1775,6 +1776,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonLZA/Resources/PokemonLZA_DonutBerries.h Source/PokemonLZA/Resources/PokemonLZA_HyperspaceRewardNames.cpp Source/PokemonLZA/Resources/PokemonLZA_HyperspaceRewardNames.h + Source/PokemonLZA/Resources/PokemonLZA_Locations.cpp + Source/PokemonLZA/Resources/PokemonLZA_Locations.h Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h Source/PokemonRSE/Inference/PokemonRSE_ShinyNumberDetector.cpp