diff --git a/app/src/main/java/com/quillstudios/pokegoalshelper/ScreenCaptureService.kt b/app/src/main/java/com/quillstudios/pokegoalshelper/ScreenCaptureService.kt index 351974f..f7be620 100644 --- a/app/src/main/java/com/quillstudios/pokegoalshelper/ScreenCaptureService.kt +++ b/app/src/main/java/com/quillstudios/pokegoalshelper/ScreenCaptureService.kt @@ -56,6 +56,8 @@ data class PokemonInfo( val level: Int?, val language: String?, val gameSource: String?, + val originMark: String?, + val isAlpha: Boolean, val isFavorited: Boolean, val nationalDexNumber: Int?, val species: String?, diff --git a/app/src/main/java/com/quillstudios/pokegoalshelper/data/PokemonDataExtractorImpl.kt b/app/src/main/java/com/quillstudios/pokegoalshelper/data/PokemonDataExtractorImpl.kt index 7468ff4..111c512 100644 --- a/app/src/main/java/com/quillstudios/pokegoalshelper/data/PokemonDataExtractorImpl.kt +++ b/app/src/main/java/com/quillstudios/pokegoalshelper/data/PokemonDataExtractorImpl.kt @@ -114,8 +114,10 @@ class PokemonDataExtractorImpl( async { extractTextFromDetection("nature", screenMat, detectionMap["nature_name"]?.firstOrNull()) }, async { extractTextFromDetection("ability", screenMat, detectionMap["ability_name"]?.firstOrNull()) }, async { extractTextFromDetection("otName", screenMat, detectionMap["original_trainer_name"]?.firstOrNull()) }, - async { extractTextFromDetection("otId", screenMat, detectionMap["original_trainder_number"]?.firstOrNull()) }, + async { extractTextFromDetection("otId", screenMat, detectionMap["original_trainer_number"]?.firstOrNull()) }, + async { extractTextFromDetection("language", screenMat, detectionMap["language"]?.firstOrNull()) }, async { extractLevelFromDetection(screenMat, detectionMap["pokemon_level"]?.maxByOrNull { it.boundingBox.width }) }, + async { extractNationalDexFromDetection(screenMat, detectionMap["national_dex_number"]?.maxByOrNull { it.boundingBox.width }) }, async { extractStatsFromDetections(screenMat, detectionMap) }, async { extractMovesFromDetections(screenMat, detectionMap) }, async { extractTypesFromDetections(screenMat, detectionMap) } @@ -139,10 +141,12 @@ class PokemonDataExtractorImpl( ability = results?.get(3) as? String, otName = results?.get(4) as? String, otId = results?.get(5) as? String, - level = results?.get(6) as? Int, - stats = results?.get(7) as? PokemonStats, - moves = results?.get(8) as? List ?: emptyList(), - types = results?.get(9) as? List ?: emptyList() + language = results?.get(6) as? String, + level = results?.get(7) as? Int, + nationalDexNumber = results?.get(8) as? String, + stats = results?.get(9) as? PokemonStats, + moves = results?.get(10) as? List ?: emptyList(), + types = results?.get(11) as? List ?: emptyList() ) } @@ -159,7 +163,9 @@ class PokemonDataExtractorImpl( pokeballType = detectPokeballType(detectionMap), isShiny = detectionMap["shiny_icon"]?.isNotEmpty() == true, teraType = detectTeraType(detectionMap), - gameSource = detectGameSource(detectionMap) + gameSource = detectGameSource(detectionMap), + originMark = detectOriginMark(detectionMap), + isAlpha = detectAlphaMark(detectionMap) ) } @@ -218,6 +224,21 @@ class PokemonDataExtractorImpl( return levelText?.replace("[^0-9]".toRegex(), "")?.toIntOrNull() } + /** + * Extract National Dex number with proper formatting (1-1400+, 0-padded to 4 digits) + */ + private suspend fun extractNationalDexFromDetection(screenMat: Mat, detection: Detection?): String? { + val dexText = extractTextFromDetection("national_dex", screenMat, detection) + val dexNumber = dexText?.replace("[^0-9]".toRegex(), "")?.toIntOrNull() + + return if (dexNumber != null && dexNumber > 0 && dexNumber <= 9999) { + // 0-pad to 4 digits (e.g., 1 -> "0001", 150 -> "0150", 1000 -> "1000") + String.format("%04d", dexNumber) + } else { + null + } + } + /** * Extract Pokemon stats from multiple stat detections */ @@ -300,7 +321,15 @@ class PokemonDataExtractorImpl( "ball_icon_greatball" to "Great Ball", "ball_icon_ultraball" to "Ultra Ball", "ball_icon_masterball" to "Master Ball", + "ball_icon_safariball" to "Safari Ball", + "ball_icon_levelball" to "Level Ball", + "ball_icon_lureball" to "Lure Ball", + "ball_icon_moonball" to "Moon Ball", + "ball_icon_friendball" to "Friend Ball", + "ball_icon_loveball" to "Love Ball", "ball_icon_heavyball" to "Heavy Ball", + "ball_icon_fastball" to "Fast Ball", + "ball_icon_sportball" to "Sport Ball", "ball_icon_premierball" to "Premier Ball", "ball_icon_repeatball" to "Repeat Ball", "ball_icon_timerball" to "Timer Ball", @@ -313,7 +342,13 @@ class PokemonDataExtractorImpl( "ball_icon_duskball" to "Dusk Ball", "ball_icon_cherishball" to "Cherish Ball", "ball_icon_dreamball" to "Dream Ball", - "ball_icon_beastball" to "Beast Ball" + "ball_icon_beastball" to "Beast Ball", + "ball_icon_strangeparts" to "Strange Ball", + "ball_icon_parkball" to "Park Ball", + "ball_icon_gsball" to "GS Ball", + "ball_icon_originball" to "Origin Ball", + "ball_icon_pokeball_hisui" to "Hisuian Poké Ball", + "ball_icon_ultraball_husui" to "Hisuian Ultra Ball" ) for ((className, ballName) in pokeballTypes) { @@ -361,13 +396,34 @@ class PokemonDataExtractorImpl( * Detect game source from stamp detections */ private fun detectGameSource(detectionMap: Map>): String? { - val gameSources = mapOf( - "last_game_stamp_sh" to "Sword/Shield", - "last_game_stamp_bank" to "Bank", + val gameStamps = mapOf( + "last_game_stamp_home" to "Pokémon HOME", + "last_game_stamp_lgp" to "Let's Go Pikachu", + "last_game_stamp_lge" to "Let's Go Eevee", + "last_game_stamp_sw" to "Sword", + "last_game_stamp_sh" to "Shield", + "last_game_stamp_bank" to "Pokémon Bank", + "last_game_stamp_bd" to "Brilliant Diamond", + "last_game_stamp_sp" to "Shining Pearl", "last_game_stamp_pla" to "Legends: Arceus", - "last_game_stamp_sc" to "Scarlet/Violet", + "last_game_stamp_sc" to "Scarlet", "last_game_stamp_vi" to "Violet", - "last_game_stamp_go" to "Pokémon GO", + "last_game_stamp_go" to "Pokémon GO" + ) + + for ((className, sourceName) in gameStamps) { + if (detectionMap[className]?.isNotEmpty() == true) { + return sourceName + } + } + return null + } + + /** + * Detect origin mark from icon detections + */ + private fun detectOriginMark(detectionMap: Map>): String? { + val originMarks = mapOf( "origin_icon_vc" to "Virtual Console", "origin_icon_xyoras" to "XY/ORAS", "origin_icon_smusum" to "SM/USUM", @@ -379,14 +435,21 @@ class PokemonDataExtractorImpl( "origin_icon_sv" to "Scarlet/Violet" ) - for ((className, sourceName) in gameSources) { + for ((className, originName) in originMarks) { if (detectionMap[className]?.isNotEmpty() == true) { - return sourceName + return originName } } return null } + /** + * Detect if Pokemon is Alpha from alpha mark + */ + private fun detectAlphaMark(detectionMap: Map>): Boolean { + return detectionMap["alpha_mark"]?.isNotEmpty() == true + } + /** * Expand bounding box for better OCR accuracy */ @@ -513,10 +576,12 @@ class PokemonDataExtractorImpl( nickname = textData.nickname, gender = iconData.gender, level = textData.level, - language = null, // TODO: Implement language detection + language = textData.language, gameSource = iconData.gameSource, + originMark = iconData.originMark, + isAlpha = iconData.isAlpha, isFavorited = false, // TODO: Detect favorite status - nationalDexNumber = null, // TODO: Implement dex number mapping + nationalDexNumber = textData.nationalDexNumber?.toIntOrNull(), species = textData.species, primaryType = textData.types.getOrNull(0), secondaryType = textData.types.getOrNull(1), @@ -587,7 +652,9 @@ class PokemonDataExtractorImpl( val ability: String?, val otName: String?, val otId: String?, + val language: String?, val level: Int?, + val nationalDexNumber: String?, val stats: PokemonStats?, val moves: List, val types: List @@ -601,6 +668,8 @@ class PokemonDataExtractorImpl( val pokeballType: String?, val isShiny: Boolean, val teraType: String?, - val gameSource: String? + val gameSource: String?, + val originMark: String?, + val isAlpha: Boolean ) } \ No newline at end of file