diff --git a/SerialPrograms/Source/Pokemon/Inference/Pokemon_TypeReader.cpp b/SerialPrograms/Source/Pokemon/Inference/Pokemon_TypeReader.cpp index a337613d80..0c68f8a579 100644 --- a/SerialPrograms/Source/Pokemon/Inference/Pokemon_TypeReader.cpp +++ b/SerialPrograms/Source/Pokemon/Inference/Pokemon_TypeReader.cpp @@ -22,24 +22,12 @@ namespace Pokemon { using namespace Kernels; using namespace Kernels::Waterfill; - class TypeSprite { public: - TypeSprite(const std::string& slug, PokemonTypeGeneration generation) + TypeSprite(const std::string& slug, const std::string& resource_location) : m_slug(slug) { - ImageRGB32 sprite; - switch (generation) - { - case PokemonAutomation::Pokemon::PokemonTypeGeneration::GEN8: - sprite = ImageRGB32(RESOURCE_PATH() + "Pokemon/Types/Gen8/" + slug + ".png"); - break; - case PokemonAutomation::Pokemon::PokemonTypeGeneration::GEN9: - sprite = ImageRGB32(RESOURCE_PATH() + "Pokemon/Types/Gen9/" + slug + ".png"); - break; - default: - throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid enum."); - } + ImageRGB32 sprite(RESOURCE_PATH() + resource_location); sprite = filter_rgb32_range( sprite, @@ -85,7 +73,7 @@ struct Gen8TypeSpriteDatabase { if (item.first == PokemonType::NONE){ continue; } - m_type_map.emplace(item.first, TypeSprite(item.second, PokemonTypeGeneration::GEN8)); + m_type_map.emplace(item.first, TypeSprite(item.second, "Pokemon/Types/Gen8/" + item.second + ".png")); } } }; @@ -103,7 +91,7 @@ struct Gen9TypeSpriteDatabase { if (item.first == PokemonType::NONE){ continue; } - m_type_map.emplace(item.first, TypeSprite(item.second, PokemonTypeGeneration::GEN9)); + m_type_map.emplace(item.first, TypeSprite(item.second, "Pokemon/Types/Gen9/" + item.second + ".png")); } } }; @@ -132,26 +120,38 @@ size_t distance_sqr(const ImagePixelBox& a, const ImagePixelBox& b){ return dist_x * dist_x + dist_y * dist_y; } -std::pair match_type_symbol(const ImageViewRGB32& image, PokemonTypeGeneration generation){ +bool image_validation(const ImageViewRGB32& image){ size_t width = image.width(); size_t height = image.height(); if (width * height < 100){ - return { 1.0, PokemonType::NONE }; + return false; } if (width > 2 * height){ - return { 1.0, PokemonType::NONE }; + return false; } if (height > 2 * width){ - return { 1.0, PokemonType::NONE }; + return false; } ImageStats stats = image_stats(image); if (stats.stddev.sum() < 50){ // if (print){ // cout << "stats.stddev.sum() = " << stats.stddev.sum() << endl; // } + return false; + } + + return true; +} + +std::pair match_type_symbol(const ImageViewRGB32& image, PokemonTypeGeneration generation){ + + if (!image_validation(image)){ return { 1.0, PokemonType::NONE }; } + size_t width = image.width(); + size_t height = image.height(); + double aspect_ratio = (double)width / height; // static int c = 0; @@ -309,7 +309,6 @@ void find_type_symbol_candidates( // cout << "candidates = " << candidates.size() << endl; } - std::multimap> find_type_symbols( const ImageViewPlanar32& original_screen, const ImageViewRGB32& image, diff --git a/SerialPrograms/Source/Pokemon/Options/Pokemon_BoxSortingTable.h b/SerialPrograms/Source/Pokemon/Options/Pokemon_BoxSortingTable.h index a8a5320169..8e2de08c5b 100644 --- a/SerialPrograms/Source/Pokemon/Options/Pokemon_BoxSortingTable.h +++ b/SerialPrograms/Source/Pokemon/Options/Pokemon_BoxSortingTable.h @@ -23,7 +23,8 @@ enum class SortingRuleType Alpha, Ball_Slug, Gender, - Type + Type, + Tera_Type, }; struct SortingRule diff --git a/SerialPrograms/Source/Pokemon/Pokemon_CollectedPokemonInfo.cpp b/SerialPrograms/Source/Pokemon/Pokemon_CollectedPokemonInfo.cpp index b2dc725640..e225914298 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_CollectedPokemonInfo.cpp +++ b/SerialPrograms/Source/Pokemon/Pokemon_CollectedPokemonInfo.cpp @@ -27,8 +27,9 @@ bool operator==(const CollectedPokemonInfo& lhs, const CollectedPokemonInfo& rhs lhs.ball_slug == rhs.ball_slug && lhs.gender == rhs.gender && lhs.ot_id == rhs.ot_id && - lhs.primaryType == rhs.primaryType && - lhs.secondaryType == rhs.secondaryType; + lhs.primary_type == rhs.primary_type && + lhs.secondary_type == rhs.secondary_type && + lhs.tera_type == rhs.tera_type; } @@ -75,11 +76,16 @@ bool operator<(const std::optional& lhs, const std::option } break; case SortingRuleType::Type: - if (lhs->primaryType != rhs->primaryType){ - return (lhs->primaryType < rhs->primaryType) != preference.reverse; + if (lhs->primary_type != rhs->primary_type){ + return (lhs->primary_type < rhs->primary_type) != preference.reverse; } - if (lhs->secondaryType != rhs->secondaryType){ - return (lhs->secondaryType < rhs->secondaryType) != preference.reverse; + if (lhs->secondary_type != rhs->secondary_type){ + return (lhs->secondary_type < rhs->secondary_type) != preference.reverse; + } + break; + case SortingRuleType::Tera_Type: + if (lhs->tera_type != rhs->tera_type){ + return (lhs->tera_type < rhs->tera_type) != preference.reverse; } break; default: @@ -103,8 +109,9 @@ std::ostream& operator<<(std::ostream& os, const std::optionalball_slug << " "; os << "gender:" << gender_to_string(pokemon->gender) << " "; os << "ot_id:" << pokemon->ot_id << " "; - os << "primaryType:" << POKEMON_TYPE_SLUGS().get_string(pokemon->primaryType) << " "; - os << "secondaryType:" << POKEMON_TYPE_SLUGS().get_string(pokemon->secondaryType) << " "; + os << "primaryType:" << POKEMON_TYPE_SLUGS().get_string(pokemon->primary_type) << " "; + os << "secondaryType:" << POKEMON_TYPE_SLUGS().get_string(pokemon->secondary_type) << " "; + os << "teraType:" << POKEMON_TERA_TYPE_SLUGS().get_string(pokemon->tera_type) << " "; os << ")"; }else{ os << "(empty)"; @@ -155,8 +162,9 @@ void save_boxes_data_to_json(const std::vectorball_slug; pokemon["gender"] = gender_to_string(current_pokemon->gender); pokemon["ot_id"] = current_pokemon->ot_id; - pokemon["primaryType"] = POKEMON_TYPE_SLUGS().get_string(current_pokemon->primaryType); - pokemon["secondaryType"] = POKEMON_TYPE_SLUGS().get_string(current_pokemon->secondaryType); + pokemon["primary_type"] = POKEMON_TYPE_SLUGS().get_string(current_pokemon->primary_type); + pokemon["secondary_type"] = POKEMON_TYPE_SLUGS().get_string(current_pokemon->secondary_type); + pokemon["tera_type"] = POKEMON_TERA_TYPE_SLUGS().get_string(current_pokemon->tera_type); } pokemon_data.push_back(std::move(pokemon)); } diff --git a/SerialPrograms/Source/Pokemon/Pokemon_CollectedPokemonInfo.h b/SerialPrograms/Source/Pokemon/Pokemon_CollectedPokemonInfo.h index 7bd6117f0c..8e0935e56d 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_CollectedPokemonInfo.h +++ b/SerialPrograms/Source/Pokemon/Pokemon_CollectedPokemonInfo.h @@ -32,8 +32,9 @@ struct CollectedPokemonInfo{ std::string ball_slug = ""; StatsHuntGenderFilter gender = StatsHuntGenderFilter::Genderless; uint32_t ot_id = 0; // original trainer ID - PokemonType primaryType = PokemonType::NONE; - PokemonType secondaryType = PokemonType::NONE; + PokemonType primary_type = PokemonType::NONE; + PokemonType secondary_type = PokemonType::NONE; + PokemonTeraType tera_type = PokemonTeraType::NONE; }; bool operator==(const CollectedPokemonInfo& lhs, const CollectedPokemonInfo& rhs); diff --git a/SerialPrograms/Source/Pokemon/Pokemon_Types.cpp b/SerialPrograms/Source/Pokemon/Pokemon_Types.cpp index 460616a381..fbbcd12e52 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_Types.cpp +++ b/SerialPrograms/Source/Pokemon/Pokemon_Types.cpp @@ -9,7 +9,31 @@ namespace PokemonAutomation{ namespace Pokemon{ - +const EnumStringMap& POKEMON_TERA_TYPE_SLUGS(){ + static EnumStringMap database{ + {PokemonTeraType::BUG, "bug"}, + {PokemonTeraType::DARK, "dark"}, + {PokemonTeraType::DRAGON, "dragon"}, + {PokemonTeraType::ELECTRIC, "electric"}, + {PokemonTeraType::FAIRY, "fairy"}, + {PokemonTeraType::FIGHTING, "fighting"}, + {PokemonTeraType::FIRE, "fire"}, + {PokemonTeraType::FLYING, "flying"}, + {PokemonTeraType::GHOST, "ghost"}, + {PokemonTeraType::GRASS, "grass"}, + {PokemonTeraType::GROUND, "ground"}, + {PokemonTeraType::ICE, "ice"}, + {PokemonTeraType::NONE, "none"}, + {PokemonTeraType::NORMAL, "normal"}, + {PokemonTeraType::POISON, "poison"}, + {PokemonTeraType::PSYCHIC, "psychic"}, + {PokemonTeraType::ROCK, "rock"}, + {PokemonTeraType::STEEL, "steel"}, + {PokemonTeraType::STELLAR, "stellar"}, + {PokemonTeraType::WATER, "water"} + }; + return database; +} const EnumStringMap& POKEMON_TYPE_SLUGS(){ static EnumStringMap database{ diff --git a/SerialPrograms/Source/Pokemon/Pokemon_Types.h b/SerialPrograms/Source/Pokemon/Pokemon_Types.h index 86417a2ea8..04ae64c671 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_Types.h +++ b/SerialPrograms/Source/Pokemon/Pokemon_Types.h @@ -21,6 +21,30 @@ enum class MoveCategory{ UNKNOWN, }; +enum class PokemonTeraType{ + BUG, + DARK, + DRAGON, + ELECTRIC, + FAIRY, + FIGHTING, + FIRE, + FLYING, + GHOST, + GRASS, + GROUND, + ICE, + NONE, + NORMAL, + POISON, + PSYCHIC, + ROCK, + STEEL, + STELLAR, + WATER +}; +const EnumStringMap& POKEMON_TERA_TYPE_SLUGS(); + enum class PokemonType{ NONE, NORMAL, diff --git a/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_GigantamaxDetector.cpp b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_GigantamaxDetector.cpp new file mode 100644 index 0000000000..4ac8da8414 --- /dev/null +++ b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_GigantamaxDetector.cpp @@ -0,0 +1,90 @@ +/* Gigantamax Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "Kernels/Waterfill/Kernels_Waterfill_Types.h" +#include "CommonTools/ImageMatch/WaterfillTemplateMatcher.h" +#include "CommonTools/Images/WaterfillUtilities.h" +#include "PokemonHome_GigantamaxDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonHome{ + + +class GigantamaxMatcher : public ImageMatch::WaterfillTemplateMatcher{ +public: + GigantamaxMatcher() + : WaterfillTemplateMatcher( + "PokemonHome/Home_Gigantamax.png", + Color(196, 32, 88), Color(255, 220, 235), + 100 + ) + { + m_aspect_ratio_lower = 0.9; + m_aspect_ratio_upper = 1.1; + m_area_ratio_lower = 0.85; + m_area_ratio_upper = 1.1; + } + + static const GigantamaxMatcher& instance(){ + static GigantamaxMatcher matcher; + return matcher; + } +}; + + +GigantamaxDetector::GigantamaxDetector( + Color color, + VideoOverlay* overlay, + const ImageFloatBox& box +) + : m_color(color) + , m_overlay(overlay) + , m_box(box) +{} +void GigantamaxDetector::make_overlays(VideoOverlaySet& items) const{ + items.add(m_color, m_box); +} +bool GigantamaxDetector::detect(const ImageViewRGB32& screen){ + const double screen_rel_size = (screen.height() / 1080.0); + const double screen_rel_size_2 = screen_rel_size * screen_rel_size; + + const double min_area_1080p = 400; + const double rmsd_threshold = 80; + const size_t min_area = size_t(screen_rel_size_2 * min_area_1080p); + + const std::vector> FILTERS = { + {0xffc42058, 0xffffdceb}, + }; + + const bool found = match_template_by_waterfill( + screen.size(), + extract_box_reference(screen, m_box), + GigantamaxMatcher::instance(), + FILTERS, + {min_area, SIZE_MAX}, + rmsd_threshold, + [&](Kernels::Waterfill::WaterfillObject& object) -> bool { + m_last_detected = translate_to_parent(screen, m_box, object); + return true; + } + ); + + if (m_overlay){ + if (found){ + m_last_detected_box.emplace(*m_overlay, m_last_detected, COLOR_GREEN); + }else{ + m_last_detected_box.reset(); + } + } + + return found; +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_GigantamaxDetector.h b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_GigantamaxDetector.h new file mode 100644 index 0000000000..313e9a00cc --- /dev/null +++ b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_GigantamaxDetector.h @@ -0,0 +1,57 @@ +/* Gigantamax Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonHome_GigantamaxDetector_H +#define PokemonAutomation_PokemonHome_GigantamaxDetector_H + +#include +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/VisualDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonHome{ + + +class GigantamaxDetector : public StaticScreenDetector{ +public: + GigantamaxDetector( + Color color, + VideoOverlay* overlay, + const ImageFloatBox& box + ); + + const ImageFloatBox& last_detected() const{ return m_last_detected; } + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool detect(const ImageViewRGB32& screen) override; + +private: + const Color m_color; + VideoOverlay* m_overlay; + const ImageFloatBox m_box; + + ImageFloatBox m_last_detected; + std::optional m_last_detected_box; +}; +class GigantamaxWatcher : public DetectorToFinder{ +public: + GigantamaxWatcher( + Color color, + VideoOverlay* overlay, + const ImageFloatBox& box, + std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250) + ) + : DetectorToFinder("GigantamaxWatcher", hold_duration, color, overlay, box) + {} +}; + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_TeraTypeReader.cpp b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_TeraTypeReader.cpp new file mode 100644 index 0000000000..b2011c908f --- /dev/null +++ b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_TeraTypeReader.cpp @@ -0,0 +1,289 @@ +/* Tera Type Reader + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonFramework/Globals.h" +#include "CommonFramework/ImageTools/ImageStats.h" +#include "CommonTools/ImageMatch/ExactImageMatcher.h" +#include "CommonTools/Images/ImageFilter.h" +#include "CommonTools/Images/BinaryImage_FilterRgb32.h" +#include "Kernels/Waterfill/Kernels_Waterfill.h" +#include "PokemonHome_TeraTypeReader.h" + +namespace PokemonAutomation { +namespace Pokemon { + +using namespace Kernels; +using namespace Kernels::Waterfill; + +namespace { + +class TypeSprite { +public: + TypeSprite(const std::string& slug, const std::string& resource_location) + : m_slug(slug) + { + ImageRGB32 sprite(RESOURCE_PATH() + resource_location); + + sprite = filter_rgb32_range( + sprite, + 0xff000000, 0xffffffff, + Color(0), false + ); + + PackedBinaryMatrix matrix = compress_rgb32_to_binary_min(sprite, 224, 224, 224); + std::vector objects = find_objects_inplace(matrix, 10); + + WaterfillObject object; + for (const WaterfillObject& item : objects){ + object.merge_assume_no_overlap(item); + } + + m_aspect_ratio = object.aspect_ratio(); + m_matcher = std::make_unique( + sprite.sub_image(object.min_x, object.min_y, object.width(), object.height()).copy(), + ImageMatch::WeightedExactImageMatcher::InverseStddevWeight{ 1, 64 } + ); + } + + const std::string& slug() const { return m_slug; } + double aspect_ratio() const { return m_aspect_ratio; } + const ImageMatch::WeightedExactImageMatcher& matcher() const { return *m_matcher; } + +private: + std::string m_slug; + double m_aspect_ratio; + std::unique_ptr m_matcher; +}; + +struct TeraTypeSpriteDatabase { + std::map m_type_map; + + static TeraTypeSpriteDatabase& instance(){ + static TeraTypeSpriteDatabase data; + return data; + } + + TeraTypeSpriteDatabase(){ + for (const auto& item : POKEMON_TERA_TYPE_SLUGS()){ + if (item.first == PokemonTeraType::NONE){ + continue; + } + m_type_map.emplace(item.first, TypeSprite(item.second, "PokemonHome/TeraTypes/" + item.second + ".png")); + } + } +}; + +size_t distance_sqr(const ImagePixelBox& a, const ImagePixelBox& b){ + bool overlap_x = a.min_x <= b.max_x && b.min_x <= a.max_x; + bool overlap_y = a.min_y <= b.max_y && b.min_y <= a.max_y; + if (overlap_x && overlap_y){ + return 0; + } + + size_t dist_x = 0; + if (!overlap_x){ + dist_x = a.max_x < b.min_x + ? b.min_x - a.max_x + : a.min_x - b.max_x; + } + + size_t dist_y = 0; + if (!overlap_y){ + dist_y = a.max_y < b.min_y + ? b.min_y - a.max_y + : a.min_y - b.max_y; + } + + return dist_x * dist_x + dist_y * dist_y; +} + +bool image_validation(const ImageViewRGB32& image){ + size_t width = image.width(); + size_t height = image.height(); + if (width * height < 100){ + return false; + } + if (width > 2 * height){ + return false; + } + if (height > 2 * width){ + return false; + } + ImageStats stats = image_stats(image); + if (stats.stddev.sum() < 50){ + return false; + } + + return true; +} + +std::pair match_tera_type_symbol(const ImageViewRGB32& image){ + if (!image_validation(image)){ + return { 1.0, PokemonTeraType::NONE }; + } + + size_t width = image.width(); + size_t height = image.height(); + + double aspect_ratio = (double)width / height; + + const std::map* type_sprite_map = &TeraTypeSpriteDatabase::instance().m_type_map; + + double best_score = 0.45; + PokemonTeraType best_type = PokemonTeraType::NONE; + for (const auto& item : *type_sprite_map){ + double expected_aspect_ratio = item.second.aspect_ratio(); + double ratio = aspect_ratio / expected_aspect_ratio; + + if (std::abs(ratio - 1) > 0.2){ + continue; + } + + double rmsd_alpha = item.second.matcher().diff(image); + + if (best_score > rmsd_alpha){ + best_score = rmsd_alpha; + best_type = item.first; + } + } + + return { best_score, best_type }; +} + +void find_tera_type_symbol_candidates( + std::multimap>& candidates, + const ImageViewPlanar32& original_screen, + const ImageViewRGB32& image, + PackedBinaryMatrix& matrix, + double max_area_ratio +){ + size_t max_area = (size_t)(image.width() * image.height() * max_area_ratio); + std::vector objects = find_objects_inplace( + matrix, + (size_t)(20. * original_screen.total_pixels() / (1920 * 1080)) + ); + + std::map objmap; + for (size_t c = 0; c < objects.size(); c++){ + if (objects[c].area > max_area){ + continue; + } + objmap[c] = objects[c]; + } + + bool changed; + do{ + changed = false; + for (auto iter0 = objmap.begin(); iter0 != objmap.end(); ++iter0){ + for (auto iter1 = objmap.begin(); iter1 != objmap.end();){ + if (iter0->first >= iter1->first){ + ++iter1; + continue; + } + const WaterfillObject& obj0 = iter0->second; + const WaterfillObject& obj1 = iter1->second; + size_t distance = distance_sqr( + ImagePixelBox(obj0.min_x, obj0.min_y, obj0.max_x, obj0.max_y), + ImagePixelBox(obj1.min_x, obj1.min_y, obj1.max_x, obj1.max_y) + ); + if (distance < 5 * 5){ + iter0->second.merge_assume_no_overlap(iter1->second); + iter1 = objmap.erase(iter1); + changed = true; + } else{ + ++iter1; + } + } + } + } + while (changed); + + for (const auto& item : objmap){ + ImageViewRGB32 img = extract_box_reference(image, item.second); + + std::pair result = match_tera_type_symbol(img); + if (result.second != PokemonTeraType::NONE){ + const WaterfillObject& obj = item.second; + candidates.emplace( + result.first, + std::pair( + result.second, + ImagePixelBox(obj.min_x, obj.min_y, obj.max_x, obj.max_y) + ) + ); + } + } +} + +} + +std::multimap> find_tera_type_symbols( + const ImageViewPlanar32& original_screen, + const ImageViewRGB32& image, + double max_area_ratio +){ + std::multimap> candidates; + + { + std::vector matrices = compress_rgb32_to_binary_range( + image, + { + {0xff808060, 0xffffffff}, + {0xffa0a060, 0xffffffff}, + {0xff606060, 0xffffffff}, + {0xff707070, 0xffffffff}, + {0xff808080, 0xffffffff}, + {0xff909090, 0xffffffff}, + {0xffa0a0a0, 0xffffffff}, + {0xffb0b0b0, 0xffffffff}, + {0xffc0c0c0, 0xffffffff}, + {0xffd0d0d0, 0xffffffff}, + {0xffe0e0e0, 0xffffffff}, + } + ); + for (PackedBinaryMatrix& matrix : matrices){ + find_tera_type_symbol_candidates(candidates, original_screen, image, matrix, max_area_ratio); + } + } + + std::multimap> filtered; + for (const auto& candidate : candidates){ + bool is_dupe = false; + for (const auto& item : filtered){ + if (distance_sqr(candidate.second.second, item.second.second) == 0){ + is_dupe = true; + break; + } + } + if (!is_dupe){ + filtered.emplace(candidate); + } + } + + return filtered; +} + +PokemonTeraType read_pokemon_tera_type( + const ImageViewRGB32& original_screen, + const ImageFloatBox& box +){ + ImageViewRGB32 image = extract_box_reference(original_screen, box); + + std::multimap> filtered = find_tera_type_symbols( + original_screen, + image, + 0.20 + ); + + if (filtered.empty()){ + return PokemonTeraType::NONE; + } + + return filtered.begin()->second.first; +} + +} +} diff --git a/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_TeraTypeReader.h b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_TeraTypeReader.h new file mode 100644 index 0000000000..6c087ec17b --- /dev/null +++ b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_TeraTypeReader.h @@ -0,0 +1,34 @@ +/* Tera Type Reader + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonHome_TeraTypeReader_H +#define PokemonAutomation_PokemonHome_TeraTypeReader_H + +#include +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "Pokemon/Pokemon_Types.h" + +namespace PokemonAutomation { +namespace Pokemon { + +// Find all tera type symbols inside the image. +std::multimap> find_tera_type_symbols( + const ImageViewPlanar32& original_screen, + const ImageViewRGB32& image, + double max_area_ratio +); + +// Reads the tera type of a Pokemon. +PokemonTeraType read_pokemon_tera_type( + const ImageViewRGB32& original_screen, + const ImageFloatBox& box +); + +} +} + +#endif diff --git a/SerialPrograms/Source/PokemonHome/Programs/PokemonHome_BoxSorter.cpp b/SerialPrograms/Source/PokemonHome/Programs/PokemonHome_BoxSorter.cpp index ec2e679715..72e32d83f5 100644 --- a/SerialPrograms/Source/PokemonHome/Programs/PokemonHome_BoxSorter.cpp +++ b/SerialPrograms/Source/PokemonHome/Programs/PokemonHome_BoxSorter.cpp @@ -50,6 +50,8 @@ language #include "PokemonHome/Inference/PokemonHome_ButtonDetector.h" #include "PokemonHome/Inference/PokemonHome_BoxGenderDetector.h" #include "PokemonHome/Inference/PokemonHome_BallReader.h" +#include "PokemonHome/Inference/PokemonHome_GigantamaxDetector.h" +#include "PokemonHome/Inference/PokemonHome_TeraTypeReader.h" #include "PokemonHome_BoxSorter.h" namespace PokemonAutomation{ @@ -391,9 +393,7 @@ void read_summary_screen( ImageFloatBox national_dex_number_box(0.448, 0.245, 0.049, 0.04); //pokemon national dex number pos ImageFloatBox shiny_symbol_box(0.702, 0.09, 0.04, 0.06); // shiny symbol pos - // TODO: gmax symbol is at the same location as Tera type symbol! Need better detection to tell apart - // gmax symbol and tera types - ImageFloatBox gmax_symbol_box(0.463, 0.09, 0.04, 0.06); // gmax symbol pos + ImageFloatBox gmax_tera_symbol_box(0.463, 0.09, 0.04, 0.06); // gmax OR tera symbol pos ImageFloatBox origin_symbol_box(0.623, 0.095, 0.033, 0.05); // origin symbol pos ImageFloatBox pokemon_box(0.69, 0.18, 0.28, 0.46); // pokemon render pos ImageFloatBox level_box(0.546, 0.099, 0.044, 0.041); // Level @@ -407,7 +407,7 @@ void read_summary_screen( video_overlay_set.add(COLOR_WHITE, national_dex_number_box); video_overlay_set.add(COLOR_BLUE, shiny_symbol_box); - video_overlay_set.add(COLOR_RED, gmax_symbol_box); + video_overlay_set.add(COLOR_RED, gmax_tera_symbol_box); video_overlay_set.add(COLOR_RED, alpha_box); video_overlay_set.add(COLOR_DARKGREEN, origin_symbol_box); video_overlay_set.add(COLOR_DARK_BLUE, pokemon_box); @@ -442,10 +442,12 @@ void read_summary_screen( cur_pokemon_info.shiny = is_shiny; env.console.log("Shiny detection stddev:" + std::to_string(shiny_stddev_value) + " is shiny:" + std::to_string(is_shiny)); - const int gmax_stddev_value = (int)image_stddev(extract_box_reference(screen, gmax_symbol_box)).sum(); - const bool is_gmax = gmax_stddev_value > 30; + GigantamaxDetector gmax_detector(COLOR_RED, &env.console.overlay(), gmax_tera_symbol_box); + bool is_gmax = gmax_detector.detect(screen); cur_pokemon_info.gmax = is_gmax; - env.console.log("Gmax detection stddev:" + std::to_string(gmax_stddev_value) + " is gmax:" + std::to_string(is_gmax)); + + PokemonTeraType tera_type = read_pokemon_tera_type(screen, gmax_tera_symbol_box); + cur_pokemon_info.tera_type = tera_type; const int alpha_stddev_value = (int)image_stddev(extract_box_reference(screen, alpha_box)).sum(); const bool is_alpha = alpha_stddev_value > 40; @@ -465,10 +467,10 @@ void read_summary_screen( } cur_pokemon_info.ot_id = ot_id; - auto [primaryType, secondaryType] = read_pokemon_types(screen, type_box, PokemonTypeGeneration::GEN9); + auto [primary_type, secondary_type] = read_pokemon_types(screen, type_box, PokemonTypeGeneration::GEN9); - cur_pokemon_info.primaryType = primaryType; - cur_pokemon_info.secondaryType = secondaryType; + cur_pokemon_info.primary_type = primary_type; + cur_pokemon_info.secondary_type = secondary_type; env.add_overlay_log(create_overlay_info(cur_pokemon_info)); video_overlay_set.clear(); diff --git a/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_BoxSorter.cpp b/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_BoxSorter.cpp index 54ded44d1b..a1c608e869 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_BoxSorter.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_BoxSorter.cpp @@ -320,7 +320,7 @@ void BoxSorter::program(SingleSwitchProgramEnvironment& env, ProControllerContex name_slug = LUMIOSE_DEX_SLUGS()[dex_number-1]; } - auto [primaryType, secondaryType] = read_pokemon_types(screen, ImageFloatBox(0.467, 0.256, 0.110, 0.041), PokemonTypeGeneration::GEN9); + auto [primary_type, secondary_type] = read_pokemon_types(screen, ImageFloatBox(0.467, 0.256, 0.110, 0.041), PokemonTypeGeneration::GEN9); boxes_data.push_back( CollectedPokemonInfo{ .preferences = &sort_preferences, @@ -328,8 +328,8 @@ void BoxSorter::program(SingleSwitchProgramEnvironment& env, ProControllerContex .name_slug = name_slug, .shiny = shiny_detector.detect(screen), .alpha = alpha_detector.detect(screen), - .primaryType = primaryType, - .secondaryType = secondaryType, + .primary_type = primary_type, + .secondary_type = secondary_type, } ); ss << "\u2705 " ; // checkbox diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index ca35f885a2..c9725d6b86 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1551,6 +1551,10 @@ file(GLOB LIBRARY_SOURCES Source/PokemonHome/Inference/PokemonHome_BoxGenderDetector.h Source/PokemonHome/Inference/PokemonHome_ButtonDetector.cpp Source/PokemonHome/Inference/PokemonHome_ButtonDetector.h + Source/PokemonHome/Inference/PokemonHome_GigantamaxDetector.cpp + Source/PokemonHome/Inference/PokemonHome_GigantamaxDetector.h + Source/PokemonHome/Inference/PokemonHome_TeraTypeReader.cpp + Source/PokemonHome/Inference/PokemonHome_TeraTypeReader.h Source/PokemonHome/PokemonHome_Panels.cpp Source/PokemonHome/PokemonHome_Panels.h Source/PokemonHome/PokemonHome_Settings.cpp