diff --git a/SerialPrograms/Source/CommonTools/OCR/OCR_StringNormalization.cpp b/SerialPrograms/Source/CommonTools/OCR/OCR_StringNormalization.cpp
index ea0244479..3250a85f1 100644
--- a/SerialPrograms/Source/CommonTools/OCR/OCR_StringNormalization.cpp
+++ b/SerialPrograms/Source/CommonTools/OCR/OCR_StringNormalization.cpp
@@ -19,6 +19,7 @@ namespace OCR{
std::u32string normalize_utf32(const std::string& text){
QString qstr = QString::fromStdString(text);
+ qstr = QString::fromStdU32String(run_character_reductions(qstr.toStdU32String()));
qstr = qstr.normalized(QString::NormalizationForm_KD);
std::u32string u32 = remove_non_alphanumeric(qstr.toStdU32String());
u32 = run_character_reductions(u32);
diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp
index cbaf44663..6b35f7252 100644
--- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp
+++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp
@@ -78,6 +78,7 @@ ShinyHunt_HyperspaceLegendary::ShinyHunt_HyperspaceLegendary()
, LEGENDARY("Legendary " + STRING_POKEMON + ":",
{
{Legendary::LATIOS, "latios", "Latios"},
+ {Legendary::LATIAS, "latias", "Latias"},
{Legendary::COBALION, "cobalion", "Cobalion"},
{Legendary::TERRAKION, "terrakion", "Terrakion"},
{Legendary::VIRIZION, "virizion", "Virizion"},
@@ -138,6 +139,172 @@ void hunt_latios(
}
+void hunt_latias(
+ SingleSwitchProgramEnvironment& env,
+ ProControllerContext& context,
+ ShinyHunt_HyperspaceLegendary_Descriptor::Stats& stats,
+ SimpleIntegerOption& MIN_CALORIE_TO_CATCH)
+{
+ // Start at the ladder and use it to fix the position and camera angle
+ detect_warp_pad(env.console, context); // This should be renamed
+ pbf_press_button(context, BUTTON_A, 160ms, 1200ms);
+ pbf_move_left_joystick(context, {0, -0.5}, 80ms, 1200ms);
+
+ // Roll backwards to align against the roof edge
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+
+ // Roll left on the overhang to get to the far corner
+ ssf_press_left_joystick(context, {-0.5, 0}, 0ms, 500ms, 0ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ // Slightly longer due to falling
+ pbf_press_button(context, BUTTON_Y, 100ms, 1200ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ // Roll to align against bulkhead
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+
+ // Roll down the corrider next to the bulkhead
+ ssf_press_left_joystick(context, {0, -0.5}, 0ms, 500ms, 0ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+
+ // Roll towards the corner of the rooftop
+ ssf_press_left_joystick(context, {-0.5, 0}, 0ms, 500ms, 0ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+
+ // Rolling at this angle puts you at the very edge of the roof, at the start of the reset path
+ ssf_press_left_joystick(context, {-0.20, +0.5}, 0ms, 500ms, 0ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ // Falls down 2 levels
+ pbf_press_button(context, BUTTON_Y, 100ms, 1200ms);
+
+ // Adjust the camera angle to face the direction of the reset path
+ pbf_move_left_joystick(context, {+0.357, +0.5}, 500ms, 500ms);
+ pbf_press_button(context, BUTTON_L, 80ms, 160ms);
+
+ // Climb up from the overhang to the lower rooftop
+ pbf_move_left_joystick(context, {0, +1}, 500ms, 1200ms);
+
+ // Start of actual reset cycle
+ while (true){
+ // Roll to the edge fo the lower rooftop
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+
+ // Climb onto the upper rooftop
+ pbf_move_left_joystick(context, {0, +1}, 500ms, 1200ms);
+
+ // Roll forward and align against the far chimney
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ // Lataias spawns here
+
+ context.wait_for_all_requests();
+ stats.spawns++;
+ env.update_stats();
+
+ // Roll back to the start
+ ssf_press_left_joystick(context, {0, -0.5}, 0ms, 500ms, 0ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1200ms);
+ // Latias despawns here
+
+ // Turn back to around
+ ssf_press_left_joystick(context, {0, +0.5}, 0ms, 500ms, 0ms);
+
+ const uint16_t min_calorie = MIN_CALORIE_TO_CATCH + 55 * 10;
+ if (check_calorie(env.console, context, min_calorie)){
+ break;
+ }
+ }
+
+ // Roll and climb onto the upper rooftop
+ pbf_move_left_joystick(context, {0, +1}, 500ms, 1200ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_move_left_joystick(context, {0, +1}, 500ms, 1200ms);
+
+ // Roll to the right, next to the rooftop with the bulkhead
+ ssf_press_left_joystick(context, {+0.5, 0}, 0ms, 500ms, 0ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+
+ // Climb onto the rooftop with the bulkhead
+ pbf_move_left_joystick(context, {0, -1}, 500ms, 1200ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+
+ // Roll right towards the next rooftop
+ ssf_press_left_joystick(context, {+0.5, 0}, 0ms, 500ms, 0ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+
+ // Adjust the camera angle back to the original angle
+ pbf_move_left_joystick(context, {-0.357, +0.5}, 80ms, 160ms);
+ pbf_press_button(context, BUTTON_L, 80ms, 160ms);
+
+ // Climb onto the next rooftop
+ ssf_press_left_joystick(context, {+0.5, 0}, 0ms, 500ms, 0ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+
+ // Roll towards the Munna rooftop
+ pbf_move_left_joystick(context, {+1, 0}, 500ms, 1200ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+
+ // Climb the Munna rooftop and roll forward
+ pbf_move_left_joystick(context, {+1, 0}, 500ms, 1200ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+
+ // Roll back towards the ladder
+ ssf_press_left_joystick(context, {0, +0.5}, 0ms, 500ms, 0ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+
+ context.wait_for_all_requests();
+ stats.spawns++;
+ env.update_stats();
+
+ // Snap to the ladder by detecting the A button prompt
+ ButtonWatcher ButtonA(
+ COLOR_RED,
+ ButtonType::ButtonA,
+ {0.4, 0.1, 0.2, 0.8},
+ &env.console.overlay(),
+ Milliseconds(100)
+ );
+ const int ret = run_until(
+ env.console, context,
+ [&](ProControllerContext& context){
+ pbf_move_left_joystick(context, {+0.5, +1}, 5000ms, 0ms);
+ },
+ {{ButtonA}}
+ );
+ if (ret < 0){
+ OperationFailedException::fire(
+ ErrorReport::SEND_ERROR_REPORT,
+ "detect_warp_pad(): Cannot detect ladder after 5 seconds",
+ env.console
+ );
+ } else {
+ env.console.log("Detected ladder.");
+ }
+
+ // Run to Latias to trigger potential shiny sound
+ env.log("Move to check Latias.");
+ env.add_overlay_log("To Check Latias");
+ pbf_press_button(context, BUTTON_A, 160ms, 80ms);
+ ssf_press_left_joystick(context, {0, +1}, 0ms, 4000ms, 0ms);
+ pbf_mash_button(context, BUTTON_Y, 4000ms);
+ context.wait_for_all_requests();
+}
+
+
void hunt_cobalion(
SingleSwitchProgramEnvironment& env,
ProControllerContext& context,
@@ -460,6 +627,8 @@ void ShinyHunt_HyperspaceLegendary::program(SingleSwitchProgramEnvironment& env,
[&](ProControllerContext& context){
if (LEGENDARY == Legendary::LATIOS){
hunt_latios(env, context, stats, MIN_CALORIE_TO_CATCH);
+ } else if (LEGENDARY == Legendary::LATIAS){
+ hunt_latias(env, context, stats, MIN_CALORIE_TO_CATCH);
} else if (LEGENDARY == Legendary::VIRIZION){
hunt_virizion_rooftop(env, context, stats, MIN_CALORIE_TO_CATCH, use_switch1_only_timings);
} else if (LEGENDARY == Legendary::TERRAKION){
diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h
index 1b2db114c..39cfcde04 100644
--- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h
+++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h
@@ -36,6 +36,7 @@ class ShinyHunt_HyperspaceLegendary : public SingleSwitchProgramInstance{
enum class Legendary{
LATIOS,
+ LATIAS,
COBALION,
TERRAKION,
VIRIZION,