Added new filters to the Pokedex. More work on showing undiscovered/discovered level up moves per species in Move Dex

This commit is contained in:
Plastered_Crab
2026-02-13 13:45:56 -08:00
parent 589aab3b1a
commit 2dfb88467b
9 changed files with 378 additions and 9 deletions
@@ -11,5 +11,6 @@ package com.cobblemon.mod.common.api.pokedex.filter
enum class SearchByType {
SPECIES,
ABILITIES,
MOVES,
DROPS;
}
@@ -9,14 +9,21 @@
package com.cobblemon.mod.common.api.pokedex.filter
import com.cobblemon.mod.common.api.drop.ItemDropEntry
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.pokedex.AbstractPokedexManager
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.pokemon.abilities.HiddenAbility
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.itemRegistry
import net.minecraft.client.Minecraft
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.ItemStack
/**
@@ -47,6 +54,23 @@ class SearchFilter(val pokedexManager: AbstractPokedexManager, val searchString:
}
return abilityList.any { it.contains(searchString.trim().lowercase()) }
}
SearchByType.MOVES -> {
if (pokedexManager.getHighestKnowledgeFor(entry) !== PokedexEntryProgress.CAUGHT) return false
val search = searchString.trim().lowercase()
val learnedTMs = CobblemonClient.clientTMMoveData.learnedTMs
val speciesLevels = CobblemonClient.clientSpeciesLevelData.speciesLevels
val forms = pokedexManager.getCaughtForms(entry)
val formData = if (forms.isEmpty()) {
listOf(species.standardForm)
} else {
forms.map { form ->
species.forms.find { it.name.equals(form.displayForm, ignoreCase = true) } ?: species.standardForm
}
}
return formData.any { form ->
hasDiscoveredMove(form, search, speciesLevels, learnedTMs)
}
}
SearchByType.DROPS -> {
if (pokedexManager.getHighestKnowledgeFor(entry) !== PokedexEntryProgress.CAUGHT) return false
val dropsList = mutableListOf<String>()
@@ -68,4 +92,132 @@ class SearchFilter(val pokedexManager: AbstractPokedexManager, val searchString:
}
}
}
private fun hasDiscoveredMove(
form: FormData,
search: String,
speciesLevels: Map<ResourceLocation, Int>,
learnedTMs: Set<ResourceLocation>
): Boolean {
val evolutionMoveLevels = buildEvolutionMoveLevelIndex(form)
fun matches(move: MoveTemplate): Boolean {
return move.displayName.string.lowercase().contains(search)
}
fun isLevelUpDiscovered(move: MoveTemplate, level: Int): Boolean {
val currentLevel = speciesLevels[form.species.resourceIdentifier] ?: 0
if (currentLevel >= level) return true
for ((evolutionSpeciesId, moveLevels) in evolutionMoveLevels) {
val evolutionLevel = moveLevels[move.name] ?: continue
val evolutionHighest = speciesLevels[evolutionSpeciesId] ?: 0
if (evolutionHighest >= evolutionLevel) return true
}
return false
}
form.moves.levelUpMoves.forEach { (level, moves) ->
for (move in moves) {
if (matches(move) && isLevelUpDiscovered(move, level)) return true
}
}
form.moves.tmMoves.forEach { move ->
val tmId = TechnicalMachines.moveToTM[move]?.id
val discovered = tmId == null || tmId in learnedTMs
if (discovered && matches(move)) return true
}
val alwaysDiscovered = sequenceOf(
form.moves.tutorMoves,
form.moves.eggMoves,
form.moves.evolutionMoves,
form.moves.formChangeMoves,
form.moves.specialMoves,
form.moves.legacyMoves
)
for (moves in alwaysDiscovered) {
for (move in moves) {
if (matches(move)) return true
}
}
return false
}
private fun buildEvolutionMoveLevelIndex(form: FormData): Map<ResourceLocation, Map<String, Int>> {
val evolutionForms = collectEvolutionForms(form)
val levelsBySpecies = mutableMapOf<ResourceLocation, MutableMap<String, Int>>()
for (evolutionForm in evolutionForms) {
val speciesId = evolutionForm.species.resourceIdentifier
val moveLevels = levelsBySpecies.getOrPut(speciesId) { mutableMapOf() }
buildMoveLevelIndex(evolutionForm).forEach { (moveName, level) ->
val current = moveLevels[moveName]
if (current == null || level < current) {
moveLevels[moveName] = level
}
}
}
return levelsBySpecies
}
private fun buildMoveLevelIndex(form: FormData): Map<String, Int> {
val levels = mutableMapOf<String, Int>()
form.moves.levelUpMoves.forEach { (level, moves) ->
moves.forEach { move ->
val current = levels[move.name]
if (current == null || level < current) {
levels[move.name] = level
}
}
}
return levels
}
private fun collectEvolutionForms(rootForm: FormData): List<FormData> {
val results = mutableListOf<FormData>()
val visited = mutableSetOf<String>()
fun key(form: FormData): String {
return "${form.species.resourceIdentifier}|${form.name.lowercase()}"
}
fun nextEvolutions(current: FormData): Set<Evolution> {
return if (current.evolutions.isNotEmpty()) {
current.evolutions
} else {
current.species.evolutions
}
}
fun traverse(current: FormData) {
for (evolution in nextEvolutions(current)) {
val evolutionForm = resolveEvolutionForm(evolution) ?: continue
val evolutionKey = key(evolutionForm)
if (!visited.add(evolutionKey)) continue
results.add(evolutionForm)
traverse(evolutionForm)
}
}
traverse(rootForm)
return results
}
private fun resolveEvolutionForm(evolution: Evolution): FormData? {
val speciesId = evolution.result.species?.asIdentifierDefaultingNamespace() ?: return null
val species = PokemonSpecies.getByIdentifier(speciesId) ?: return null
val formId = evolution.result.form
return if (formId == null) {
species.standardForm
} else {
species.forms.firstOrNull {
it.formOnlyShowdownId().equals(formId, ignoreCase = true) || it.name.equals(formId, ignoreCase = true)
} ?: species.standardForm
}
}
}
@@ -0,0 +1,167 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokedex.filter
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.pokedex.AbstractPokedexManager
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import net.minecraft.resources.ResourceLocation
enum class SecondaryPokedexFilterType {
ALL,
CAUGHT,
SEEN,
UNDISCOVERED,
LEVEL_UP_TM_UNDISCOVERED
}
class SecondaryPokedexFilter(
private val pokedexManager: AbstractPokedexManager,
private val filterType: SecondaryPokedexFilterType
) : EntryFilter() {
override fun test(entry: PokedexEntry): Boolean {
return when (filterType) {
SecondaryPokedexFilterType.ALL -> true
SecondaryPokedexFilterType.CAUGHT -> pokedexManager.getHighestKnowledgeFor(entry) >= PokedexEntryProgress.CAUGHT
SecondaryPokedexFilterType.SEEN -> pokedexManager.getHighestKnowledgeFor(entry) == PokedexEntryProgress.ENCOUNTERED
SecondaryPokedexFilterType.UNDISCOVERED -> pokedexManager.getHighestKnowledgeFor(entry) == PokedexEntryProgress.NONE
SecondaryPokedexFilterType.LEVEL_UP_TM_UNDISCOVERED -> hasUndiscoveredLevelUpTM(entry)
}
}
private fun hasUndiscoveredLevelUpTM(entry: PokedexEntry): Boolean {
if (pokedexManager.getHighestKnowledgeFor(entry) != PokedexEntryProgress.CAUGHT) return false
val species = PokemonSpecies.getByIdentifier(entry.speciesId) ?: return false
val speciesLevels = CobblemonClient.clientSpeciesLevelData.speciesLevels
val forms = pokedexManager.getCaughtForms(entry)
val formData = if (forms.isEmpty()) {
listOf(species.standardForm)
} else {
forms.map { form ->
species.forms.find { it.name.equals(form.displayForm, ignoreCase = true) } ?: species.standardForm
}
}
return formData.any { form ->
hasUndiscoveredLevelUpTM(form, speciesLevels)
}
}
private fun hasUndiscoveredLevelUpTM(
form: FormData,
speciesLevels: Map<ResourceLocation, Int>
): Boolean {
val evolutionMoveLevels = buildEvolutionMoveLevelIndex(form)
fun isLevelUpDiscovered(move: MoveTemplate, level: Int): Boolean {
val currentLevel = speciesLevels[form.species.resourceIdentifier] ?: 0
if (currentLevel >= level) return true
for ((evolutionSpeciesId, moveLevels) in evolutionMoveLevels) {
val evolutionLevel = moveLevels[move.name] ?: continue
val evolutionHighest = speciesLevels[evolutionSpeciesId] ?: 0
if (evolutionHighest >= evolutionLevel) return true
}
return false
}
form.moves.levelUpMoves.forEach { (level, moves) ->
for (move in moves) {
if (TechnicalMachines.moveToTM[move] != null && !isLevelUpDiscovered(move, level)) {
return true
}
}
}
return false
}
private fun buildEvolutionMoveLevelIndex(form: FormData): Map<ResourceLocation, Map<String, Int>> {
val evolutionForms = collectEvolutionForms(form)
val levelsBySpecies = mutableMapOf<ResourceLocation, MutableMap<String, Int>>()
for (evolutionForm in evolutionForms) {
val speciesId = evolutionForm.species.resourceIdentifier
val moveLevels = levelsBySpecies.getOrPut(speciesId) { mutableMapOf() }
buildMoveLevelIndex(evolutionForm).forEach { (moveName, level) ->
val current = moveLevels[moveName]
if (current == null || level < current) {
moveLevels[moveName] = level
}
}
}
return levelsBySpecies
}
private fun buildMoveLevelIndex(form: FormData): Map<String, Int> {
val levels = mutableMapOf<String, Int>()
form.moves.levelUpMoves.forEach { (level, moves) ->
moves.forEach { move ->
val current = levels[move.name]
if (current == null || level < current) {
levels[move.name] = level
}
}
}
return levels
}
private fun collectEvolutionForms(rootForm: FormData): List<FormData> {
val results = mutableListOf<FormData>()
val visited = mutableSetOf<String>()
fun key(form: FormData): String {
return "${form.species.resourceIdentifier}|${form.name.lowercase()}"
}
fun nextEvolutions(current: FormData): Set<Evolution> {
return if (current.evolutions.isNotEmpty()) {
current.evolutions
} else {
current.species.evolutions
}
}
fun traverse(current: FormData) {
for (evolution in nextEvolutions(current)) {
val evolutionForm = resolveEvolutionForm(evolution) ?: continue
val evolutionKey = key(evolutionForm)
if (!visited.add(evolutionKey)) continue
results.add(evolutionForm)
traverse(evolutionForm)
}
}
traverse(rootForm)
return results
}
private fun resolveEvolutionForm(evolution: Evolution): FormData? {
val speciesId = evolution.result.species?.asIdentifierDefaultingNamespace() ?: return null
val species = PokemonSpecies.getByIdentifier(speciesId) ?: return null
val formId = evolution.result.form
return if (formId == null) {
species.standardForm
} else {
species.forms.firstOrNull {
it.formOnlyShowdownId().equals(formId, ignoreCase = true) || it.name.equals(formId, ignoreCase = true)
} ?: species.standardForm
}
}
}
@@ -22,6 +22,8 @@ import com.cobblemon.mod.common.api.pokedex.entry.PokedexForm
import com.cobblemon.mod.common.api.pokedex.filter.EntryFilter
import com.cobblemon.mod.common.api.pokedex.filter.SearchByType
import com.cobblemon.mod.common.api.pokedex.filter.SearchFilter
import com.cobblemon.mod.common.api.pokedex.filter.SecondaryPokedexFilter
import com.cobblemon.mod.common.api.pokedex.filter.SecondaryPokedexFilterType
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.storage.player.client.ClientPokedexManager
import com.cobblemon.mod.common.api.text.bold
@@ -84,6 +86,7 @@ class PokedexGUI private constructor(
private val arrowDownIcon = cobblemonResource("textures/gui/pokedex/arrow_down.png")
private val tabSelectArrow = cobblemonResource("textures/gui/pokedex/select_arrow.png")
private val secondaryFilterIcon = cobblemonResource("textures/gui/pokedex/filter_icon_button.png")
private val tabIcons = arrayOf(
cobblemonResource("textures/gui/pokedex/tab_info.png"),
cobblemonResource("textures/gui/pokedex/tab_abilities.png"),
@@ -129,11 +132,13 @@ class PokedexGUI private constructor(
private lateinit var regionSelectWidgetUp: ScaledButton
private lateinit var regionSelectWidgetDown: ScaledButton
private lateinit var searchByTypeButton: ScaledButton
private lateinit var secondaryFilterButton: ScaledButton
private lateinit var scrollScreen: EntriesScrollingWidget
private lateinit var pokemonInfoWidget: PokemonInfoWidget
private lateinit var searchWidget: SearchWidget
private var selectedSearchByType: SearchByType = SearchByType.SPECIES
private var selectedSecondaryFilter: SecondaryPokedexFilterType = SecondaryPokedexFilterType.ALL
private val tabButtons: MutableList<ScaledButton> = mutableListOf()
lateinit var tabInfoElement: GuiEventListener
@@ -165,7 +170,7 @@ class PokedexGUI private constructor(
displaytabInfoElement(tabInfoIndex, false)
if (::searchWidget.isInitialized) removeWidget(searchWidget)
searchWidget = SearchWidget(x + 26, y + 28, 128, HEADER_BAR_HEIGHT, update = ::updateFilters)
searchWidget = SearchWidget(x + 26, y + 28, 120, HEADER_BAR_HEIGHT, update = ::updateFilters)
addRenderableWidget(searchWidget)
if (::regionSelectWidgetUp.isInitialized) removeWidget(regionSelectWidgetUp)
@@ -210,6 +215,23 @@ class PokedexGUI private constructor(
)
addRenderableWidget(searchByTypeButton)
if (::secondaryFilterButton.isInitialized) removeWidget(secondaryFilterButton)
secondaryFilterButton = ScaledButton(
buttonX = (x + 146.5).toFloat(),
buttonY = (y + 29.5).toFloat(),
buttonWidth = 16,
buttonHeight = 16,
scale = SCALE,
resource = secondaryFilterIcon,
clickAction = {
val filters = SecondaryPokedexFilterType.entries.toList()
val selectedIndex = filters.indexOf(selectedSecondaryFilter)
selectedSecondaryFilter = filters[if (selectedIndex == filters.lastIndex) 0 else (selectedIndex + 1)]
updateFilters()
}
)
addRenderableWidget(secondaryFilterButton)
updateFilters(true)
}
@@ -333,6 +355,18 @@ class PokedexGUI private constructor(
-14
)
}
if (secondaryFilterButton.isButtonHovered(mouseX, mouseY)) {
val filterText = lang("ui.pokedex.filter.${selectedSecondaryFilter.name.lowercase()}").bold()
renderTooltip(
context,
filterText,
mouseX,
mouseY,
delta,
-14
)
}
}
override fun onClose() {
@@ -443,6 +477,7 @@ class PokedexGUI private constructor(
fun getFilters(): Collection<EntryFilter> {
val filters: MutableList<EntryFilter> = mutableListOf()
filters.add(SecondaryPokedexFilter(CobblemonClient.clientPokedexData, selectedSecondaryFilter))
filters.add(SearchFilter(CobblemonClient.clientPokedexData, searchWidget.value, selectedSearchByType))
return filters
@@ -85,6 +85,7 @@ class MovesLearnsetWidget(val pX: Int, val pY: Int) : SoundlessWidget(
private val sortAlphaIcon = cobblemonResource("textures/gui/pokedex/moves_sort_alpha.png")
private val sortTypeIcon = cobblemonResource("textures/gui/pokedex/moves_sort_type.png")
private val sortSourceIcon = cobblemonResource("textures/gui/pokedex/moves_sort_source.png")
private val sortDiscoveredIcon = cobblemonResource("textures/gui/pokedex/moves_sort_discovered.png")
private val tmDiscIcon = cobblemonResource("textures/item/tms/tm.png")
private val movesPowerIconResource = cobblemonResource("textures/gui/summary/summary_moves_icon_power.png")
private val movesAccuracyIconResource = cobblemonResource("textures/gui/summary/summary_moves_icon_accuracy.png")
@@ -445,7 +446,6 @@ class MovesLearnsetWidget(val pX: Int, val pY: Int) : SoundlessWidget(
private fun applyFilter() {
val filtered = when (currentFilter()) {
LearnsetFilter.ALL -> moveEntries
LearnsetFilter.DISCOVERED -> moveEntries.filter { it.isDiscovered }
LearnsetFilter.LEVEL_UP -> moveEntries.filter { it.source == LearnsetSource.LEVEL_UP }
LearnsetFilter.TM -> moveEntries.filter { it.source == LearnsetSource.TM }
LearnsetFilter.EGG -> moveEntries.filter { it.source == LearnsetSource.EGG }
@@ -462,21 +462,27 @@ class MovesLearnsetWidget(val pX: Int, val pY: Int) : SoundlessWidget(
}
private fun sortEntries(entries: List<LearnsetMoveEntry>): List<LearnsetMoveEntry> {
val baseEntries = if (sortMode == LearnsetSort.DISCOVERED) {
entries.filter { it.isDiscovered }
} else {
entries
}
return when (sortMode) {
LearnsetSort.ALPHABETICAL -> entries.sortedWith(
LearnsetSort.ALPHABETICAL -> baseEntries.sortedWith(
compareBy(
{ it.move.displayName.string.lowercase() },
{ it.level ?: Int.MAX_VALUE }
)
)
LearnsetSort.TYPE -> entries.sortedWith(
LearnsetSort.TYPE -> baseEntries.sortedWith(
compareBy(
{ it.move.elementalType.displayName.string.lowercase() },
{ it.move.displayName.string.lowercase() },
{ it.level ?: Int.MAX_VALUE }
)
)
LearnsetSort.SOURCE -> entries.sortedWith(
LearnsetSort.SOURCE,
LearnsetSort.DISCOVERED -> baseEntries.sortedWith(
compareBy(
{ sourceOrder(it.source) },
{ it.level ?: Int.MAX_VALUE },
@@ -500,12 +506,14 @@ class MovesLearnsetWidget(val pX: Int, val pY: Int) : SoundlessWidget(
sortMode = when (sortMode) {
LearnsetSort.ALPHABETICAL -> LearnsetSort.TYPE
LearnsetSort.TYPE -> LearnsetSort.SOURCE
LearnsetSort.SOURCE -> LearnsetSort.ALPHABETICAL
LearnsetSort.SOURCE -> LearnsetSort.DISCOVERED
LearnsetSort.DISCOVERED -> LearnsetSort.ALPHABETICAL
}
sortButton.resource = when (sortMode) {
LearnsetSort.ALPHABETICAL -> sortAlphaIcon
LearnsetSort.TYPE -> sortTypeIcon
LearnsetSort.SOURCE -> sortSourceIcon
LearnsetSort.DISCOVERED -> sortDiscoveredIcon
}
applyFilter()
}
@@ -726,7 +734,6 @@ class MovesLearnsetWidget(val pX: Int, val pY: Int) : SoundlessWidget(
private enum class LearnsetFilter(val label: MutableComponent) {
ALL(Component.literal("All")),
DISCOVERED(lang("ui.moves.learnset.tm.discovered")),
LEVEL_UP(Component.literal("Level-Up")),
TM(Component.literal("TM")),
EGG(Component.literal("Egg")),
@@ -736,7 +743,8 @@ class MovesLearnsetWidget(val pX: Int, val pY: Int) : SoundlessWidget(
private enum class LearnsetSort {
ALPHABETICAL,
TYPE,
SOURCE
SOURCE,
DISCOVERED
}
private data class LearnsetMoveEntry(
@@ -1294,7 +1294,13 @@
"cobblemon.ui.pokedex.search.search_by": "Search by %1$s",
"cobblemon.ui.pokedex.search.type.species": "Species Name",
"cobblemon.ui.pokedex.search.type.abilities": "Ability",
"cobblemon.ui.pokedex.search.type.moves": "Move Name",
"cobblemon.ui.pokedex.search.type.drops": "Drops",
"cobblemon.ui.pokedex.filter.all": "All",
"cobblemon.ui.pokedex.filter.caught": "Caught",
"cobblemon.ui.pokedex.filter.seen": "Seen",
"cobblemon.ui.pokedex.filter.undiscovered": "Undiscovered",
"cobblemon.ui.pokedex.filter.level_up_tm_undiscovered": "Undiscovered TM (Level-Up)",
"cobblemon.ui.pokedex.info.ability": "Ability: %1$s",
"cobblemon.ui.pokedex.info.entry": "Entry",
Binary file not shown.

After

Width:  |  Height:  |  Size: 224 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 250 B

After

Width:  |  Height:  |  Size: 266 B