Push test changes from my home PC

This commit is contained in:
plastered_crab
2026-06-08 14:08:55 -07:00
parent fc1853a23b
commit 84039fcfaf
20 changed files with 913 additions and 144 deletions
@@ -37,6 +37,7 @@ import com.cobblemon.mod.common.api.pokemon.helditem.HeldItemProvider
import com.cobblemon.mod.common.api.pokemon.stats.EvCalculator
import com.cobblemon.mod.common.api.pokemon.stats.Generation8EvCalculator
import com.cobblemon.mod.common.api.pokemon.stats.StatProvider
import com.cobblemon.mod.common.api.pokedex.scan.BlockScanProviderRegistry
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
import com.cobblemon.mod.common.api.scheduling.ServerRealTimeTaskTracker
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
@@ -93,6 +94,8 @@ import com.cobblemon.mod.common.permission.LaxPermissionValidator
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.pokedex.photo.PokedexPhotoRepository
import com.cobblemon.mod.common.pokedex.photo.PrintedPhotoAssetRepository
import com.cobblemon.mod.common.pokedex.scanner.block.BerryBlockScanProvider
import com.cobblemon.mod.common.pokedex.scanner.block.PrintedPhotoBlockScanProvider
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.aspects.CHARACTERISTIC_RAINBOW_ASPECT
import com.cobblemon.mod.common.pokemon.aspects.COSMETIC_SLOT_ASPECT
@@ -239,6 +242,8 @@ object Cobblemon {
CobblemonGameRules // Init fields and register
ShoulderEffectRegistry.register()
BlockScanProviderRegistry.register(cobblemonResource("berry"), BerryBlockScanProvider)
BlockScanProviderRegistry.register(cobblemonResource("printed_photo_display"), PrintedPhotoBlockScanProvider)
DATA_SYNCHRONIZED.subscribe {
storage.onPlayerDataSync(it)
@@ -97,6 +97,7 @@ import com.cobblemon.mod.common.net.messages.client.pasture.OpenPasturePacket
import com.cobblemon.mod.common.net.messages.client.pasture.PokemonPasturedPacket
import com.cobblemon.mod.common.net.messages.client.pasture.PokemonUnpasturedPacket
import com.cobblemon.mod.common.net.messages.client.pasture.UpdatePastureConflictFlagPacket
import com.cobblemon.mod.common.net.messages.client.pokedex.BlockScanRegistrySyncPacket
import com.cobblemon.mod.common.net.messages.client.pokedex.ServerConfirmedRegisterPacket
import com.cobblemon.mod.common.net.messages.client.pokemon.update.*
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.AddEvolutionPacket
@@ -423,6 +424,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(BehaviourSyncPacket.ID, BehaviourSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(MarkRegistrySyncPacket.ID, MarkRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(RideSettingsSyncPacket.ID, RideSettingsSyncPacket::decode, RideSettingsSyncHandler))
list.add(PacketRegisterInfo(BlockScanRegistrySyncPacket.ID, BlockScanRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
// Effects
list.add(PacketRegisterInfo(SpawnSnowstormParticlePacket.ID, SpawnSnowstormParticlePacket::decode, SpawnSnowstormParticleHandler))
@@ -0,0 +1,87 @@
package com.cobblemon.mod.common.api.pokedex.scan
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import io.netty.buffer.ByteBuf
import java.util.Optional
import kotlin.jvm.optionals.getOrNull
import net.minecraft.network.codec.ByteBufCodecs
import net.minecraft.network.codec.StreamCodec
import net.minecraft.resources.ResourceLocation
data class BlockScanDefinition(
val blockEntityType: ResourceLocation,
val frames: List<Frame> = emptyList()
) {
data class Frame(
val slot: Int,
val text: ScanText,
val scale: Float? = null
)
sealed interface ScanText {
data class Literal(val text: String) : ScanText
data class Key(val key: String) : ScanText
data class Translate(val key: String, val with: List<ScanText> = emptyList()) : ScanText
}
companion object {
private val SCAN_TEXT_CODEC: Codec<ScanText> = Codec.recursive("ScanText") { self ->
Codec.either(
Codec.STRING,
RecordCodecBuilder.create<ScanText> { instance ->
instance.group(
Codec.STRING.optionalFieldOf("literal")
.forGetter { Optional.ofNullable((it as? ScanText.Literal)?.text) },
Codec.STRING.optionalFieldOf("key")
.forGetter { Optional.ofNullable((it as? ScanText.Key)?.key) },
Codec.STRING.optionalFieldOf("translate")
.forGetter { Optional.ofNullable((it as? ScanText.Translate)?.key) },
Codec.list(self).optionalFieldOf("with")
.forGetter { Optional.ofNullable((it as? ScanText.Translate)?.with) }
).apply(instance) { literal, key, translate, with ->
when {
literal.isPresent -> ScanText.Literal(literal.get())
key.isPresent -> ScanText.Key(key.get())
translate.isPresent -> ScanText.Translate(translate.get(), with.getOrNull() ?: emptyList())
else -> ScanText.Literal("")
}
}
}
).xmap(
{ either ->
either.left().getOrNull()?.let { ScanText.Literal(it) }
?: either.right().getOrNull()
?: ScanText.Literal("")
},
{ text ->
when (text) {
is ScanText.Literal -> com.mojang.datafixers.util.Either.left<String, ScanText>(text.text)
else -> com.mojang.datafixers.util.Either.right<String, ScanText>(text)
}
}
)
}
val FRAME_CODEC: Codec<Frame> = RecordCodecBuilder.create { instance ->
instance.group(
Codec.INT.fieldOf("slot").forGetter { it.slot },
SCAN_TEXT_CODEC.fieldOf("text").forGetter { it.text },
Codec.FLOAT.optionalFieldOf("scale").forGetter { Optional.ofNullable(it.scale) }
).apply(instance) { slot, text, scale ->
Frame(slot, text, scale.getOrNull())
}
}
val CODEC: Codec<BlockScanDefinition> = RecordCodecBuilder.create { instance ->
instance.group(
ResourceLocation.CODEC.fieldOf("block_entity_type").forGetter { it.blockEntityType },
FRAME_CODEC.listOf().optionalFieldOf("frames").forGetter { Optional.ofNullable(it.frames) }
).apply(instance) { blockEntityType, frames ->
BlockScanDefinition(blockEntityType, frames.getOrNull() ?: emptyList())
}
}
val STREAM_CODEC: StreamCodec<ByteBuf, BlockScanDefinition> = ByteBufCodecs.fromCodec(CODEC)
}
}
@@ -0,0 +1,117 @@
package com.cobblemon.mod.common.api.pokedex.scan
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.pokedex.BlockScanRegistrySyncPacket
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.FieldNamingPolicy
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonArray
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonParseException
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import com.google.gson.reflect.TypeToken
import java.lang.reflect.Type
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
object BlockScanDefinitions : JsonDataRegistry<BlockScanDefinition> {
override val id: ResourceLocation = cobblemonResource("pokedex_block_scans")
override val type: PackType = PackType.SERVER_DATA
override val observable = SimpleObservable<BlockScanDefinitions>()
override val typeToken: TypeToken<BlockScanDefinition> = TypeToken.get(BlockScanDefinition::class.java)
override val resourcePath: String = "pokedex_scans/block_entities"
override val gson: Gson = GsonBuilder()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(BlockScanDefinition.ScanText::class.java, ScanTextAdapter)
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.setPrettyPrinting()
.create()
private val byType = mutableMapOf<ResourceLocation, BlockScanDefinition>()
override fun sync(player: ServerPlayer) {
BlockScanRegistrySyncPacket(byType.values.toList()).sendToPlayer(player)
}
override fun reload(data: Map<ResourceLocation, BlockScanDefinition>) {
byType.clear()
data.values.forEach { definition ->
byType[definition.blockEntityType] = definition
}
observable.emit(this)
}
fun reloadEntries(entries: Collection<BlockScanDefinition>) {
byType.clear()
entries.forEach { definition ->
byType[definition.blockEntityType] = definition
}
observable.emit(this)
}
fun get(typeId: ResourceLocation): BlockScanDefinition? = byType[typeId]
private object ScanTextAdapter :
JsonDeserializer<BlockScanDefinition.ScanText>,
JsonSerializer<BlockScanDefinition.ScanText> {
private val SCAN_TEXT_TYPE: Type = object : TypeToken<BlockScanDefinition.ScanText>() {}.type
override fun deserialize(
json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext
): BlockScanDefinition.ScanText {
if (json.isJsonPrimitive && json.asJsonPrimitive.isString) {
return BlockScanDefinition.ScanText.Literal(json.asString)
}
if (!json.isJsonObject) {
throw JsonParseException("ScanText must be a string or an object")
}
val obj = json.asJsonObject
if (obj.has("literal")) return BlockScanDefinition.ScanText.Literal(obj["literal"].asString)
if (obj.has("key")) return BlockScanDefinition.ScanText.Key(obj["key"].asString)
if (obj.has("translate")) {
val key = obj["translate"].asString
val with = if (obj.has("with")) {
obj.getAsJsonArray("with").map { element ->
context.deserialize<BlockScanDefinition.ScanText>(element, SCAN_TEXT_TYPE)
}
} else {
emptyList()
}
return BlockScanDefinition.ScanText.Translate(key, with)
}
throw JsonParseException("ScanText object must contain one of: literal, key, translate")
}
override fun serialize(
src: BlockScanDefinition.ScanText,
typeOfSrc: Type,
context: JsonSerializationContext
): JsonElement = when (src) {
is BlockScanDefinition.ScanText.Literal -> JsonPrimitive(src.text)
is BlockScanDefinition.ScanText.Key -> JsonObject().apply { addProperty("key", src.key) }
is BlockScanDefinition.ScanText.Translate -> JsonObject().apply {
addProperty("translate", src.key)
if (src.with.isNotEmpty()) {
add("with", JsonArray().apply {
src.with.forEach { add(context.serialize(it, SCAN_TEXT_TYPE)) }
})
}
}
}
}
}
@@ -0,0 +1,14 @@
package com.cobblemon.mod.common.api.pokedex.scan
import com.cobblemon.mod.common.pokedex.scanner.block.BlockScanProvider
import net.minecraft.resources.ResourceLocation
object BlockScanProviderRegistry {
private val providers = mutableMapOf<ResourceLocation, BlockScanProvider>()
fun register(typeId: ResourceLocation, provider: BlockScanProvider) {
providers[typeId] = provider
}
fun get(typeId: ResourceLocation): BlockScanProvider? = providers[typeId]
}
@@ -217,6 +217,7 @@ object PokedexPhotoUploadPacketHandler : ServerNetworkPacketHandler<PokedexPhoto
) ?: return emptyList()
val primaryLookTargetMatched = isBrainLookingAtPlayer(primary, player)
val otherPokemonBonuses = calculateOtherPokemonBonuses(primary, withServerLookScores)
val familyCoverage = calculateFamilyCoverage(primary, withServerLookScores)
val entity = primary.entityUuid?.let { player.serverLevel().getEntity(it) } as? PokemonEntity
val serverShiny = entity?.pokemon?.shiny ?: primary.shiny
val serverAlpha = entity?.pokemon?.aspects?.contains("alpha") ?: primary.alpha
@@ -258,7 +259,8 @@ object PokedexPhotoUploadPacketHandler : ServerNetworkPacketHandler<PokedexPhoto
formScore = if (serverAlternateForm) Cobblemon.config.pokedexScannerPhotoFormBonus.coerceAtLeast(0) else 0,
rarityMultiplier = speciesId?.let(PokedexPhotoQuality::speciesRarityMultiplier) ?: 1F
),
otherPokemonBonuses = otherPokemonBonuses
otherPokemonBonuses = otherPokemonBonuses,
familyCoverage = familyCoverage
)
} else {
subject.copy(subjectScore = 0)
@@ -266,9 +268,14 @@ object PokedexPhotoUploadPacketHandler : ServerNetworkPacketHandler<PokedexPhoto
}.sortedWith(compareByDescending<PokedexPhotoSubjectQuality> { it.primarySubject }.thenByDescending { it.subjectScore })
}
private fun scorePrimarySubject(subject: PokedexPhotoSubjectQuality, otherPokemonBonuses: OtherPokemonBonuses): PokedexPhotoSubjectQuality {
private fun scorePrimarySubject(
subject: PokedexPhotoSubjectQuality,
otherPokemonBonuses: OtherPokemonBonuses,
familyCoverage: Float
): PokedexPhotoSubjectQuality {
val wholeBodyInFrame = subject.perimeterHits == 0
val sizeScore = calculateSizeScore(subject.rayCoverage.toDouble(), wholeBodyInFrame)
val scoringCoverage = (subject.rayCoverage + familyCoverage).coerceIn(0F, 1F)
val sizeScore = calculateSizeScore(scoringCoverage.toDouble(), wholeBodyInFrame)
val clippingMultiplier = if (wholeBodyInFrame) {
1.0
} else {
@@ -324,6 +331,19 @@ object PokedexPhotoUploadPacketHandler : ServerNetworkPacketHandler<PokedexPhoto
return OtherPokemonBonuses(extraPokemonScore, herdScore)
}
private fun calculateFamilyCoverage(
primary: PokedexPhotoSubjectQuality,
subjects: List<PokedexPhotoSubjectQuality>
): Float {
return subjects
.asSequence()
.filter { it !== primary }
.filter { subject -> isFamilyMember(primary.species, subject.species) }
.sumOf { subject -> subject.rayCoverage.toDouble() }
.toFloat()
.coerceIn(0F, 1F)
}
private fun calculateSecondaryPokemonBonus(subject: PokedexPhotoSubjectQuality): Int {
if (subject.rayHits <= 0) return 0
val visibility = (subject.rayCoverage / 0.08F).coerceIn(0.25F, 1F)
@@ -333,14 +353,15 @@ object PokedexPhotoUploadPacketHandler : ServerNetworkPacketHandler<PokedexPhoto
}
private fun calculateFamilyBonus(primary: PokedexPhotoSubjectQuality, subject: PokedexPhotoSubjectQuality): Int {
if (primary.species == subject.species) {
return Cobblemon.config.pokedexScannerPhotoFamilyBonus.coerceAtLeast(0)
}
return if (areSpeciesInSameEvolutionLine(primary.species, subject.species)) {
Cobblemon.config.pokedexScannerPhotoFamilyBonus.coerceAtLeast(0)
} else {
0
}
if (!isFamilyMember(primary.species, subject.species)) return 0
val visibility = (subject.rayCoverage / 0.12F).coerceIn(0F, 1F)
val placement = (1F - subject.centerDistance).coerceIn(0F, 1F)
val scoreCap = Cobblemon.config.pokedexScannerPhotoFamilyBonus.coerceAtLeast(0)
return (scoreCap * visibility * placement).roundToInt().coerceIn(0, scoreCap)
}
private fun isFamilyMember(primarySpecies: String, subjectSpecies: String): Boolean {
return primarySpecies == subjectSpecies || areSpeciesInSameEvolutionLine(primarySpecies, subjectSpecies)
}
private fun areSpeciesInSameEvolutionLine(firstSpecies: String, secondSpecies: String): Boolean {
@@ -10,6 +10,9 @@ package com.cobblemon.mod.common.client.pokedex
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.pokedex.PokedexLearnedInformation
import com.cobblemon.mod.common.api.pokedex.scan.BlockScanDefinition
import com.cobblemon.mod.common.api.pokedex.scan.BlockScanDefinitions
import com.cobblemon.mod.common.api.pokedex.scan.BlockScanProviderRegistry
import com.cobblemon.mod.common.api.text.*
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.CobblemonResources
@@ -29,9 +32,12 @@ import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.client.DeltaTracker
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth.clamp
import net.minecraft.world.level.block.entity.BlockEntity
import org.joml.Quaternionf
import kotlin.math.*
@@ -41,6 +47,11 @@ import kotlin.math.*
* @author Village
*/
class PokedexScannerRenderer {
private data class BlockScanRenderContext(
val definition: BlockScanDefinition,
val values: Map<String, Any?>
)
companion object {
val SCAN_OVERLAY_NOTCH_WIDTH = 200
val CENTER_INFO_FRAME_WIDTH = 128
@@ -91,13 +102,45 @@ class PokedexScannerRenderer {
fun infoFrameResource(isLeft: Boolean, tier: Int): ResourceLocation = cobblemonResource("textures/gui/pokedex/scan/scan_info_frame_${if (isLeft) "left" else "right"}_$tier.png")
}
private var lastFocusedBlockTypeId: ResourceLocation? = null
fun renderInfoFrames(graphics: GuiGraphics, poseStack: PoseStack, usageContext: PokedexUsageContext, centerX: Int, centerY: Int, opacity: Float) {
if (usageContext.focusIntervals > 0) {
var infoDisplayedCounter = 0
usageContext.availableInfoFrames.forEachIndexed {index, isLeftSide ->
if (isLeftSide !== null) {
if (infoDisplayedCounter > 1 && !usageContext.isPokemonInFocusOwned) return@forEachIndexed
val isPokemonFocus = usageContext.scannableEntityInFocus != null
if (isPokemonFocus && infoDisplayedCounter > 1 && !usageContext.isPokemonInFocusOwned) return@forEachIndexed
infoDisplayedCounter++
val blockEntityInFocus: BlockEntity? = usageContext.blockEntityInFocus
val pokedexEntityData = usageContext.scannableEntityInFocus?.resolvePokemonScan()
// store the block entity typeId so our close animation knows what to show
if (pokedexEntityData != null) {
lastFocusedBlockTypeId = null
} else if (blockEntityInFocus != null) {
lastFocusedBlockTypeId = BuiltInRegistries.BLOCK_ENTITY_TYPE.getKey(blockEntityInFocus.type)
} else if (usageContext.focusIntervals <= 0f) {
lastFocusedBlockTypeId = null
}
val effectiveTypeId = blockEntityInFocus
?.let { BuiltInRegistries.BLOCK_ENTITY_TYPE.getKey(it.type) }
?: lastFocusedBlockTypeId
// if a block entity is being focused on make sure we only show the frames needed
if (pokedexEntityData == null && effectiveTypeId != null) {
val def = BlockScanDefinitions.get(effectiveTypeId)
if (def != null) {
val hasFrameForSlot = def.frames.any { it.slot == infoDisplayedCounter }
if (!hasFrameForSlot) {
return@forEachIndexed
}
}
}
// Frames
val isInnerFrame = index == 1 || index == 2
val frameHeight = if (isInnerFrame) INNER_INFO_FRAME_HEIGHT else OUTER_INFO_FRAME_HEIGHT
@@ -119,105 +162,155 @@ class PokedexScannerRenderer {
val yOffsetText = when(index) { 0 -> 5; 1 -> 4; 2 -> 8; 3 -> 42; else -> 0 }
// Text
if (usageContext.focusIntervals == PokedexUsageContext.FOCUS_INTERVALS && usageContext.scannableEntityInFocus != null) {
val pokedexEntityData = usageContext.scannableEntityInFocus?.resolvePokemonScan() ?: return
if (infoDisplayedCounter == 1) {
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.lv.number", pokedexEntityData.pokemon.level).bold(),
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText,
shadow = true,
centered = true
)
}
if (infoDisplayedCounter == 2) {
val hasTrainer = (usageContext.scannableEntityInFocus?.resolveEntityScan() as? PokemonEntity)?.ownerUUID !== null
val speciesName = pokedexEntityData.getApparentSpecies().translatedName.bold()
var yOffsetName = if (hasTrainer) 2 else 0
if (hasTrainer) {
if (usageContext.focusIntervals == PokedexUsageContext.FOCUS_INTERVALS && (pokedexEntityData != null || blockEntityInFocus != null)) {
if (pokedexEntityData != null) {
if (infoDisplayedCounter == 1) {
drawScaledText(
context = graphics,
text = lang("ui.pokedex.scan.trainer_owned"),
font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.lv.number", pokedexEntityData.pokemon.level).bold(),
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText - yOffsetName,
y = centerY + yOffset + yOffsetText,
shadow = true,
centered = true
)
}
if (infoDisplayedCounter == 2) {
val hasTrainer =
(usageContext.scannableEntityInFocus?.resolveEntityScan() as? PokemonEntity)?.ownerUUID !== null
val speciesName = pokedexEntityData.getApparentSpecies().translatedName.bold()
var yOffsetName = if (hasTrainer) 2 else 0
if (hasTrainer) {
drawScaledText(
context = graphics,
text = lang("ui.pokedex.scan.trainer_owned"),
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText - yOffsetName,
shadow = true,
centered = true,
scale = 0.5F
)
}
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = speciesName,
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText + yOffsetName,
shadow = true,
centered = true
)
val gender = pokedexEntityData.pokemon.gender
val speciesNameWidth =
Minecraft.getInstance().font.width(speciesName.font(CobblemonResources.DEFAULT_LARGE))
if (gender != Gender.GENDERLESS) {
val isMale = gender == Gender.MALE
val textSymbol = if (isMale) "♂".text().bold() else "♀".text().bold()
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = textSymbol,
x = centerX + xOffset + xOffsetText + 2 + (speciesNameWidth / 2),
y = centerY + yOffset + yOffsetText + yOffsetName,
colour = if (isMale) 0x32CBFF else 0xFC5454,
shadow = true
)
}
if (usageContext.isPokemonInFocusOwned) {
blitk(
matrixStack = poseStack,
texture = caughtIndicator,
x = (centerX + xOffset + xOffsetText - 7 - (speciesNameWidth / 2)) / SCALE,
y = (centerY + yOffset + yOffsetText + yOffsetName + 2) / SCALE,
height = 10,
width = 10,
scale = SCALE
)
}
}
if (infoDisplayedCounter == 3) {
val typeText = lang(
"type.suffix",
pokedexEntityData.getApparentForm().types.map { it.displayName.copy() }
.reduce { acc, next -> acc.plus("/").plus(next) }).bold()
val typeWidth =
Minecraft.getInstance().font.width(typeText.font(CobblemonResources.DEFAULT_LARGE))
// Split into 2 lines if text width is too long
if (typeWidth > (OUTER_INFO_FRAME_WIDTH - 8) && pokedexEntityData.getApparentForm().secondaryType !== null) {
val splitLines = typeText.string.split("/")
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = text(splitLines[0], "/").bold(),
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText - 4,
shadow = true,
centered = true
)
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = text(splitLines[1]).bold(),
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText + 3,
shadow = true,
centered = true
)
} else {
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = typeText,
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText,
shadow = true,
centered = true
)
}
}
} else if (blockEntityInFocus != null) {
val typeId = BuiltInRegistries.BLOCK_ENTITY_TYPE.getKey(blockEntityInFocus.type) ?: return@forEachIndexed
/*Cobblemon.LOGGER.info(
"[ScannerRender] typeId={} focusIntervals={} usageIntervals={} defPresent={} providerPresent={}",
typeId,
usageContext.focusIntervals,
usageContext.usageIntervals,
BlockScanDefinitions.get(typeId) != null,
BlockScanProviderRegistry.get(typeId) != null
)*/
val def = BlockScanDefinitions.get(typeId) ?: return@forEachIndexed
val provider = BlockScanProviderRegistry.get(typeId) ?: return@forEachIndexed
val values = provider.getValues(blockEntityInFocus)
val frame = def.frames.firstOrNull { it.slot == infoDisplayedCounter } ?: return@forEachIndexed
val textComponent = resolveScanText(frame.text, values).bold()
val scale = frame.scale
if (scale != null) {
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = textComponent,
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText,
shadow = true,
centered = true,
scale = 0.5F
)
}
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = speciesName,
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText + yOffsetName,
shadow = true,
centered = true
)
val gender = pokedexEntityData.pokemon.gender
val speciesNameWidth = Minecraft.getInstance().font.width(speciesName.font(CobblemonResources.DEFAULT_LARGE))
if (gender != Gender.GENDERLESS) {
val isMale = gender == Gender.MALE
val textSymbol = if (isMale) "♂".text().bold() else "♀".text().bold()
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = textSymbol,
x = centerX + xOffset + xOffsetText + 2 + (speciesNameWidth / 2),
y = centerY + yOffset + yOffsetText + yOffsetName,
colour = if (isMale) 0x32CBFF else 0xFC5454,
shadow = true
)
}
if (usageContext.isPokemonInFocusOwned) {
blitk(
matrixStack = poseStack,
texture = caughtIndicator,
x = (centerX + xOffset + xOffsetText - 7 - (speciesNameWidth / 2)) / SCALE,
y = (centerY + yOffset + yOffsetText + yOffsetName + 2) / SCALE,
height = 10,
width = 10,
scale = SCALE
)
}
}
if (infoDisplayedCounter == 3) {
val typeText = lang("type.suffix", pokedexEntityData.getApparentForm().types.map { it.displayName.copy() }.reduce { acc, next -> acc.plus("/").plus(next) }).bold()
val typeWidth = Minecraft.getInstance().font.width(typeText.font(CobblemonResources.DEFAULT_LARGE))
// Split into 2 lines if text width is too long
if (typeWidth > (OUTER_INFO_FRAME_WIDTH - 8) && pokedexEntityData.getApparentForm().secondaryType !== null) {
val splitLines = typeText.string.split("/")
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = text(splitLines[0], "/").bold(),
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText - 4,
shadow = true,
centered = true
)
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = text(splitLines[1]).bold(),
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText + 3,
shadow = true,
centered = true
scale = scale
)
} else {
drawScaledText(
context = graphics,
font = CobblemonResources.DEFAULT_LARGE,
text = typeText,
text = textComponent,
x = centerX + xOffset + xOffsetText,
y = centerY + yOffset + yOffsetText,
shadow = true,
@@ -231,6 +324,33 @@ class PokedexScannerRenderer {
}
}
private fun BlockEntity.toBlockScanRenderContext(): BlockScanRenderContext? {
val typeId = BuiltInRegistries.BLOCK_ENTITY_TYPE.getKey(type) ?: return null
val definition = BlockScanDefinitions.get(typeId) ?: return null
val provider = BlockScanProviderRegistry.get(typeId) ?: return null
return BlockScanRenderContext(definition, provider.getValues(this))
}
private fun resolveScanText(spec: BlockScanDefinition.ScanText, values: Map<String, Any?>): MutableComponent {
return when (spec) {
is BlockScanDefinition.ScanText.Literal -> text(spec.text)
is BlockScanDefinition.ScanText.Key -> anyToComponent(values[spec.key])
is BlockScanDefinition.ScanText.Translate -> lang(
spec.key,
*spec.with.map { resolveScanText(it, values) }.toTypedArray()
)
}
}
private fun anyToComponent(value: Any?): MutableComponent {
return when (value) {
null -> text("")
is MutableComponent -> value
is Component -> value.copy()
else -> text(value.toString())
}
}
fun renderScanRings(poseStack: PoseStack, usageContext: PokedexUsageContext, centerX: Int, centerY: Int, opacity: Float) {
val rotation = usageContext.usageIntervals % 360
@@ -43,6 +43,7 @@ import com.cobblemon.mod.common.api.pokedex.DexAdditions
import com.cobblemon.mod.common.api.pokedex.Dexes
import com.cobblemon.mod.common.api.pokedex.entry.DexEntries
import com.cobblemon.mod.common.api.pokedex.entry.DexEntryAdditions
import com.cobblemon.mod.common.api.pokedex.scan.BlockScanDefinitions
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.feature.GlobalSpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatureAssignments
@@ -119,6 +120,7 @@ object CobblemonDataProvider : DataProvider {
this.register(SpawnBaitEffects, reloadable = false)
this.register(CobblemonRideSettings, reloadable = true)
this.register(TechnicalMachines, reloadable = false)
this.register(BlockScanDefinitions, reloadable = true)
SpawnBait.Effects.setupEffects()
PlatformEvents.SERVER_PLAYER_LOGOUT.subscribe {
@@ -0,0 +1,32 @@
package com.cobblemon.mod.common.net.messages.client.pokedex
import com.cobblemon.mod.common.api.pokedex.scan.BlockScanDefinition
import com.cobblemon.mod.common.api.pokedex.scan.BlockScanDefinitions
import com.cobblemon.mod.common.net.messages.client.data.DataRegistrySyncPacket
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.RegistryFriendlyByteBuf
class BlockScanRegistrySyncPacket(definitions: List<BlockScanDefinition>) :
DataRegistrySyncPacket<BlockScanDefinition, BlockScanRegistrySyncPacket>(definitions) {
companion object {
val ID = cobblemonResource("pokedex_block_scans")
fun decode(buffer: RegistryFriendlyByteBuf): BlockScanRegistrySyncPacket =
BlockScanRegistrySyncPacket(emptyList()).apply { decodeBuffer(buffer) }
}
override val id = ID
override fun encodeEntry(buffer: RegistryFriendlyByteBuf, entry: BlockScanDefinition) {
BlockScanDefinition.STREAM_CODEC.encode(buffer, entry)
}
override fun decodeEntry(buffer: RegistryFriendlyByteBuf): BlockScanDefinition {
return BlockScanDefinition.STREAM_CODEC.decode(buffer)
}
override fun synchronizeDecoded(entries: Collection<BlockScanDefinition>) {
BlockScanDefinitions.reloadEntries(entries)
}
}
@@ -0,0 +1,34 @@
/*
* 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.net.messages.server.pokedex.scanner
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.core.BlockPos
import net.minecraft.network.RegistryFriendlyByteBuf
class FinishPrintedPhotoScanningPacket(val blockPos: BlockPos, val zoomLevel: Int) : NetworkPacket<FinishPrintedPhotoScanningPacket> {
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeBlockPos(blockPos)
buffer.writeInt(zoomLevel)
}
companion object {
val ID = cobblemonResource("finish_printed_photo_scanning_packet")
fun decode(buffer: RegistryFriendlyByteBuf): FinishPrintedPhotoScanningPacket {
return FinishPrintedPhotoScanningPacket(
blockPos = buffer.readBlockPos(),
zoomLevel = buffer.readInt()
)
}
}
}
@@ -0,0 +1,63 @@
/*
* 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.net.serverhandling.pokedex.scanner
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.block.entity.PrintedPhotoDisplayBlockEntity
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.FinishPrintedPhotoScanningPacket
import com.cobblemon.mod.common.pokedex.photo.PrintedPhotoPokedexScanner
import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext
import com.cobblemon.mod.common.pokedex.scanner.PlayerScanningDetails
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.level.ClipContext
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.HitResult
import net.minecraft.world.phys.Vec3
object FinishPrintedPhotoScanningHandler : com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler<FinishPrintedPhotoScanningPacket> {
override fun handle(
packet: FinishPrintedPhotoScanningPacket,
server: MinecraftServer,
player: ServerPlayer
) {
val progressTick = PlayerScanningDetails.playerToTickMap[player.uuid]
val ticksScan = progressTick?.let { server.tickCount - it } ?: PokedexUsageContext.SUCCESS_SCAN_SERVER_TICKS
if (ticksScan < PokedexUsageContext.SUCCESS_SCAN_SERVER_TICKS) return
if (!isLookingAtPrintedPhoto(player, packet)) return
val component = (player.level().getBlockEntity(packet.blockPos) as? PrintedPhotoDisplayBlockEntity)?.component ?: return
val newlySeen = PrintedPhotoPokedexScanner.grantSeenEntries(player, component)
PrintedPhotoPokedexScanner.sendResultMessage(player, newlySeen, component.detectedSpecies.distinct().size)
}
private fun isLookingAtPrintedPhoto(player: ServerPlayer, packet: FinishPrintedPhotoScanningPacket): Boolean {
val maxDistance = Cobblemon.config.maxPokedexScanningDetectionRange +
(packet.zoomLevel * PokedexUsageContext.BLOCK_LENGTH_PER_ZOOM_STAGE)
if (player.position().distanceTo(Vec3.atCenterOf(packet.blockPos)) > maxDistance) {
return false
}
val eye = player.getEyePosition(1.0F)
val end = eye.add(player.getViewVector(1.0F).scale(maxDistance.toDouble()))
val hit = player.level().clip(
ClipContext(
eye,
end,
ClipContext.Block.COLLIDER,
ClipContext.Fluid.NONE,
player
)
)
if (hit.type != HitResult.Type.BLOCK) return false
val blockHit = hit as? BlockHitResult ?: return false
if (blockHit.blockPos != packet.blockPos) return false
return player.level().getBlockEntity(packet.blockPos) is PrintedPhotoDisplayBlockEntity
}
}
@@ -0,0 +1,50 @@
/*
* 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.pokedex.photo
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.item.components.PrintedPhotoComponent
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
object PrintedPhotoPokedexScanner {
fun grantSeenEntries(player: ServerPlayer, component: PrintedPhotoComponent): Int {
val pokedex = Cobblemon.playerDataManager.getPokedexData(player)
var newlySeen = 0
component.detectedSpecies
.asSequence()
.mapNotNull(ResourceLocation::tryParse)
.distinct()
.forEach { speciesId ->
if (pokedex.getKnowledgeForSpecies(speciesId) >= PokedexEntryProgress.ENCOUNTERED) {
return@forEach
}
val species = PokemonSpecies.getByIdentifier(speciesId) ?: return@forEach
pokedex.encounter(species.create(level = 1))
newlySeen++
}
return newlySeen
}
fun sendResultMessage(player: ServerPlayer, newlySeen: Int, totalSpecies: Int) {
val message = if (totalSpecies <= 0) {
Component.translatable("cobblemon.ui.pokedex.photo_scan.empty")
} else if (newlySeen > 0) {
Component.translatable("cobblemon.ui.pokedex.photo_scan.success", newlySeen, totalSpecies)
} else {
Component.translatable("cobblemon.ui.pokedex.photo_scan.already_seen", totalSpecies)
}
player.displayClientMessage(message, true)
}
}
@@ -57,6 +57,7 @@ import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import net.minecraft.world.entity.Entity
import net.minecraft.world.level.ClipContext
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.HitResult
@@ -132,6 +133,8 @@ class PokedexUsageContext {
var registerCompleted: Boolean = false
var scannedSpecies: ResourceLocation? = null
var scannableEntityInFocus: ScannableEntity? = null
var blockEntityInFocus: BlockEntity? = null
var blockPosInFocus: BlockPos? = null
var viewInfoTicks: Int = 0
var scanningProgress: Float = 0F
var displayRegisterInfoIntervals: Float = 0F
@@ -158,6 +161,8 @@ class PokedexUsageContext {
var showDebugPhotoScore = false
var showDebugPhotoRaycast = false
private fun hasAnyFocus(): Boolean = scannableEntityInFocus != null || blockEntityInFocus != null
private data class PhotoCaptureMetadata(
val detectedSpecies: List<String>,
val biome: String,
@@ -269,14 +274,18 @@ class PokedexUsageContext {
if (scanningGuiOpen && viewInfoTicks < VIEW_INFO_BUFFER_TICKS) {
if (transitionIntervals < TRANSITION_INTERVALS) transitionIntervals = min(transitionIntervals + updateInterval, TRANSITION_INTERVALS)
innerRingRotation = (if (scannableEntityInFocus != null) (innerRingRotation + (updateInterval * 10F)) else (innerRingRotation + updateInterval)) % 360
innerRingRotation = (if (hasAnyFocus()) (innerRingRotation + (updateInterval * 10F)) else (innerRingRotation + updateInterval)) % 360
usageIntervals += updateInterval
} else {
if (transitionIntervals > 0) {
if (transitionIntervals == TRANSITION_INTERVALS) playSound(CobblemonSounds.POKEDEX_SCAN_CLOSE)
transitionIntervals = max(transitionIntervals - updateInterval, 0F)
if (transitionIntervals <= 0) {
if (viewInfoTicks >= VIEW_INFO_BUFFER_TICKS) openPokedexGUI(type, scannableEntityInFocus!!.resolvePokemonScan()?.getApparentSpecies()?.resourceIdentifier)
if (viewInfoTicks >= VIEW_INFO_BUFFER_TICKS) {
scannableEntityInFocus?.resolvePokemonScan()?.getApparentSpecies()?.resourceIdentifier?.let { species ->
openPokedexGUI(type, species)
}
}
resetState()
}
}
@@ -301,9 +310,9 @@ class PokedexUsageContext {
true
)
}
if (focusIntervals > 0) focusIntervals = max(0F, focusIntervals - updateInterval)
if (focusIntervals < FOCUS_INTERVALS) focusIntervals = min(focusIntervals + updateInterval, FOCUS_INTERVALS)
} else {
if (scannableEntityInFocus != null) {
if (hasAnyFocus()) {
if (focusIntervals < FOCUS_INTERVALS) focusIntervals = min(focusIntervals + updateInterval, FOCUS_INTERVALS)
} else {
if (focusIntervals > 0) focusIntervals = max(0F, focusIntervals - updateInterval)
@@ -342,7 +351,7 @@ class PokedexUsageContext {
}
fun attackKeyHeld(isHeld: Boolean) {
if (isHeld && scannableEntityInFocus !== null && viewInfoTicks < VIEW_INFO_BUFFER_TICKS && scanningProgress == 0F) {
if (isHeld && hasAnyFocus() && viewInfoTicks < VIEW_INFO_BUFFER_TICKS && scanningProgress == 0F) {
viewInfoTicks++
if (viewInfoTicks % 2 == 0) playSound(CobblemonSounds.POKEDEX_SCAN_LOOP)
} else if (viewInfoTicks > 0 && viewInfoTicks < VIEW_INFO_BUFFER_TICKS) {
@@ -423,8 +432,11 @@ class PokedexUsageContext {
}
fun tryScanPokemon(user: LocalPlayer) {
// Pokémon/entity scan
val targetScannableEntity = PokemonScanner.findScannableEntity(user, zoomLevel.toInt())
if (targetScannableEntity != null) {
blockEntityInFocus = null
blockPosInFocus = null
resetPrintedPhotoScanState()
if (targetScannableEntity != scannableEntityInFocus) {
resetFocusedPokemonState()
@@ -441,9 +453,9 @@ class PokedexUsageContext {
// Randomize info frames for render
if (focusIntervals == 0F) {
availableInfoFrames = mutableListOf(null, null, null, null)
for (i in 0..2) {
for (i in 0..3) {
var randomIndex = Random.nextInt(availableInfoFrames.size)
if (availableInfoFrames[randomIndex] !== null) randomIndex = availableInfoFrames.indexOfFirst { it == null }
if (availableInfoFrames[randomIndex] != null) randomIndex = availableInfoFrames.indexOfFirst { it == null }
availableInfoFrames.set(randomIndex, Random.nextBoolean())
}
}
@@ -454,19 +466,50 @@ class PokedexUsageContext {
else scannedSpecies = resolvedPokemon.getApparentSpecies().resourceIdentifier
}
} else {
if (!tryScanPrintedPhoto(user)) {
if (!tryScanBlockEntity(user)) {
resetFocusedPokemonState()
return
}
// BlockEntity scan
val blockHit = PokemonScanner.findScannableBlockEntity(user, zoomLevel.toInt())
if (blockHit != null) {
val (pos, be) = blockHit
if (be != blockEntityInFocus || pos != blockPosInFocus) {
resetFocusedPokemonState()
blockEntityInFocus = be
/*Cobblemon.LOGGER.info(
"[PokedexUsageContext] blockEntityInFocus set: {} typeId={}",
blockEntityInFocus?.javaClass?.simpleName,
blockEntityInFocus?.type?.let { net.minecraft.core.registries.BuiltInRegistries.BLOCK_ENTITY_TYPE.getKey(it) }
)*/
blockPosInFocus = pos
if (focusIntervals == 0F) {
availableInfoFrames = mutableListOf(null, null, null, null)
for (i in 0..3) {
var randomIndex = Random.nextInt(availableInfoFrames.size)
if (availableInfoFrames[randomIndex] != null) randomIndex = availableInfoFrames.indexOfFirst { it == null }
availableInfoFrames[randomIndex] = Random.nextBoolean()
}
}
}
return
}
resetFocusedPokemonState()
}
}
private fun tryScanPrintedPhoto(user: LocalPlayer): Boolean {
val targetPos = findPrintedPhotoDisplayBlock(user)
if (targetPos == null) {
private fun tryScanBlockEntity(user: LocalPlayer): Boolean {
val target = PokemonScanner.findScannableBlockEntity(user, zoomLevel.toInt())
if (target == null) {
resetPrintedPhotoScanState()
return false
}
val (targetPos, targetBlockEntity) = target
scannableEntityInFocus = null
scannedSpecies = null
viewInfoTicks = 0
@@ -474,35 +517,32 @@ class PokedexUsageContext {
registerCompleted = false
isPokemonInFocusOwned = false
newPokemonInfo = PokedexLearnedInformation.NONE
blockEntityInFocus = targetBlockEntity
blockPosInFocus = targetPos.immutable()
if (printedPhotoBlockInFocus != targetPos) {
val isPrintedPhoto = targetBlockEntity is PrintedPhotoDisplayBlockEntity
if (!isPrintedPhoto) {
resetPrintedPhotoScanState()
scanningProgress = 0F
}
if (isPrintedPhoto && printedPhotoBlockInFocus != targetPos) {
printedPhotoBlockInFocus = targetPos.immutable()
printedPhotoScanCompleted = false
scanningProgress = 0F
if (focusIntervals == 0F) {
availableInfoFrames = mutableListOf(null, null, null, null)
}
if (focusIntervals == 0F) {
availableInfoFrames = mutableListOf(null, null, null, null)
for (i in 0..3) {
var randomIndex = Random.nextInt(availableInfoFrames.size)
if (availableInfoFrames[randomIndex] !== null) randomIndex = availableInfoFrames.indexOfFirst { it == null }
availableInfoFrames.set(randomIndex, Random.nextBoolean())
}
}
return true
}
private fun findPrintedPhotoDisplayBlock(user: LocalPlayer): BlockPos? {
val eyePos = user.getEyePosition(1.0F)
val maxDistance = Cobblemon.config.maxPokedexScanningDetectionRange + (zoomLevel.toInt() * BLOCK_LENGTH_PER_ZOOM_STAGE)
val hit = user.level().clip(
ClipContext(
eyePos,
eyePos.add(user.getViewVector(1.0F).scale(maxDistance.toDouble())),
ClipContext.Block.COLLIDER,
ClipContext.Fluid.NONE,
user
)
)
if (hit.type != HitResult.Type.BLOCK) return null
val blockHit = hit as? BlockHitResult ?: return null
return blockHit.blockPos.takeIf { user.level().getBlockEntity(it) is PrintedPhotoDisplayBlockEntity }
}
fun onServerConfirmedRegister(packet: ServerConfirmedRegisterPacket) {
if (scannedSpecies?.equals(packet.species) == true) {
newPokemonInfo = packet.newInformation
@@ -518,6 +558,8 @@ class PokedexUsageContext {
fun resetFocusedPokemonState() {
scannableEntityInFocus = null
blockEntityInFocus = null
blockPosInFocus = null
scannedSpecies = null
viewInfoTicks = 0
scanningProgress = 0F
@@ -1216,7 +1258,8 @@ class PokedexUsageContext {
) ?: return emptyList()
val primary = buildPrimarySubjectQuality(primaryStats)
val (otherPokemonBonus, herdBonus) = calculateOtherPokemonBonuses(primary, subjectStats)
val scoredPrimary = applyPrimarySubjectScoring(primary, otherPokemonBonus, herdBonus)
val familyCoverage = calculateFamilyCoverage(primary, subjectStats)
val scoredPrimary = applyPrimarySubjectScoring(primary, otherPokemonBonus, herdBonus, familyCoverage)
return subjectStats.map { stats ->
if (stats === primaryStats) {
@@ -1327,10 +1370,12 @@ class PokedexUsageContext {
private fun applyPrimarySubjectScoring(
primary: PokedexPhotoSubjectQuality,
otherPokemonBonus: Int,
herdBonus: Int
herdBonus: Int,
familyCoverage: Float
): PokedexPhotoSubjectQuality {
val wholeBodyInFrame = primary.perimeterHits == 0
val sizeScore = calculateSizeScore(primary.rayCoverage.toDouble(), wholeBodyInFrame)
val scoringCoverage = (primary.rayCoverage + familyCoverage).coerceIn(0F, 1F)
val sizeScore = calculateSizeScore(scoringCoverage.toDouble(), wholeBodyInFrame)
val clippingMultiplier = if (wholeBodyInFrame) {
1.0
} else {
@@ -1433,6 +1478,16 @@ class PokedexUsageContext {
return rawBonus.coerceIn(0, configuredCap) to herdBonus.coerceAtLeast(0)
}
private fun calculateFamilyCoverage(primary: PokedexPhotoSubjectQuality, subjects: List<PhotoSubjectStats>): Float {
return subjects
.asSequence()
.filter { it.accumulator.species.toString() != primary.species || it.accumulator.entity.uuid != primary.entityUuid }
.filter { subject -> isFamilyMember(primary.species, subject.accumulator.species.toString()) }
.sumOf { it.rayCoverage }
.toFloat()
.coerceIn(0F, 1F)
}
private fun calculateSecondaryPokemonBonus(subject: PhotoSubjectStats): Int {
if (subject.accumulator.rayHits <= 0) return 0
val visibility = (subject.rayCoverage.toFloat() / 0.08F).coerceIn(0.25F, 1F)
@@ -1442,15 +1497,15 @@ class PokedexUsageContext {
}
private fun calculateFamilyBonus(primary: PokedexPhotoSubjectQuality, subject: PhotoSubjectStats): Int {
val subjectSpecies = subject.accumulator.species.toString()
if (primary.species == subjectSpecies) {
return ServerSettings.pokedexScannerPhotoFamilyBonus.coerceAtLeast(0)
}
return if (areSpeciesInSameEvolutionLine(primary.species, subjectSpecies)) {
ServerSettings.pokedexScannerPhotoFamilyBonus.coerceAtLeast(0)
} else {
0
}
if (!isFamilyMember(primary.species, subject.accumulator.species.toString())) return 0
val visibility = (subject.rayCoverage.toFloat() / 0.12F).coerceIn(0F, 1F)
val placement = (1F - subject.centerDistance.toFloat()).coerceIn(0F, 1F)
val scoreCap = ServerSettings.pokedexScannerPhotoFamilyBonus.coerceAtLeast(0)
return (scoreCap * visibility * placement).roundToInt().coerceIn(0, scoreCap)
}
private fun isFamilyMember(primarySpecies: String, subjectSpecies: String): Boolean {
return primarySpecies == subjectSpecies || areSpeciesInSameEvolutionLine(primarySpecies, subjectSpecies)
}
private fun areSpeciesInSameEvolutionLine(firstSpecies: String, secondSpecies: String): Boolean {
@@ -9,10 +9,15 @@
package com.cobblemon.mod.common.pokedex.scanner
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.pokedex.scan.BlockScanDefinitions
import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext.Companion.BLOCK_LENGTH_PER_ZOOM_STAGE
import net.minecraft.core.BlockPos
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.world.entity.Entity
import net.minecraft.world.level.ClipContext
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.HitResult
//Handles the actual raycasting to figure out what pokemon we are looking at
@@ -97,6 +102,29 @@ object PokemonScanner {
return targetedEntity as? ScannableEntity
}
fun findScannableBlockEntity(castingEntity: Entity, zoomLevel: Int): Pair<BlockPos, BlockEntity>? {
val eyePos = castingEntity.getEyePosition(1.0F)
val lookVec = castingEntity.getViewVector(1.0F)
val maxDistance = Cobblemon.config.maxPokedexScanningDetectionRange + (zoomLevel * BLOCK_LENGTH_PER_ZOOM_STAGE)
val hit = castingEntity.level().clip(
ClipContext(
eyePos,
eyePos.add(lookVec.scale(maxDistance.toDouble())),
ClipContext.Block.OUTLINE,
ClipContext.Fluid.NONE,
castingEntity
)
)
if (hit.type != HitResult.Type.BLOCK) return null
val blockPos = (hit as? BlockHitResult)?.blockPos ?: return null
val blockEntity = castingEntity.level().getBlockEntity(blockPos) ?: return null
val typeId = BuiltInRegistries.BLOCK_ENTITY_TYPE.getKey(blockEntity.type) ?: return null
if (BlockScanDefinitions.get(typeId) == null) return null
return blockPos to blockEntity
}
fun isEntityInRange(castingEntity: Entity, targetEntity: Entity, zoomLevel: Int): Boolean {
return targetEntity.position().distanceTo(castingEntity.position()) <= Cobblemon.config.maxPokedexScanningDetectionRange + (zoomLevel * BLOCK_LENGTH_PER_ZOOM_STAGE)
}
@@ -0,0 +1,24 @@
package com.cobblemon.mod.common.pokedex.scanner.block
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.block.BerryBlock
import com.cobblemon.mod.common.block.entity.BerryBlockEntity
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.entity.BlockEntity
object BerryBlockScanProvider : BlockScanProvider {
override fun getValues(be: BlockEntity): Map<String, Any?> {
val berry = be as BerryBlockEntity
val stage = runCatching { berry.blockState.getValue(BerryBlock.AGE) }.getOrDefault(0)
val berryName = berry.berry()?.item()?.let { ItemStack(it).hoverName.copy().bold() }
?: berry.berryIdentifier.toString().text().bold()
return mapOf(
"stage" to stage,
"mulch_variant" to berry.mulchVariant.toString(),
"berry_identifier" to berry.berryIdentifier.toString(),
"berry_name" to berryName
)
}
}
@@ -0,0 +1,8 @@
package com.cobblemon.mod.common.pokedex.scanner.block
import net.minecraft.world.level.block.entity.BlockEntity
interface BlockScanProvider {
/** Return the values for keys used in the JSONs we create for each scannable Block Entity */
fun getValues(be: BlockEntity): Map<String, Any?>
}
@@ -0,0 +1,19 @@
package com.cobblemon.mod.common.pokedex.scanner.block
import com.cobblemon.mod.common.block.entity.PrintedPhotoDisplayBlockEntity
import com.cobblemon.mod.common.pokedex.photo.PokedexPhotoRatings
import net.minecraft.world.level.block.entity.BlockEntity
object PrintedPhotoBlockScanProvider : BlockScanProvider {
override fun getValues(be: BlockEntity): Map<String, Any?> {
val component = (be as? PrintedPhotoDisplayBlockEntity)?.component
val score = component?.qualityScore ?: 0
val speciesCount = component?.detectedSpecies?.size ?: 0
return mapOf(
"rating" to PokedexPhotoRatings.ratingText(score),
"score" to "$score/5000",
"species_count" to speciesCount
)
}
}
@@ -1330,6 +1330,12 @@
"cobblemon.ui.pokedex.scan.mode.capture_hint": "Left Click: Take Picture",
"cobblemon.ui.pokedex.scan.mode.scanning_enabled": "Scanning Mode Enabled",
"cobblemon.ui.pokedex.scan.mode.camera_enabled": "Camera Mode Enabled",
"cobblemon.cobblemon.ui.pokedex.scan.block.berry": "%1$s",
"cobblemon.cobblemon.ui.pokedex.scan.block.berry_stage": "Growth: %1$s",
"cobblemon.cobblemon.ui.pokedex.scan.block.berry_mulch": "Mulch: %1$s",
"cobblemon.cobblemon.ui.pokedex.scan.block.printed_photo_rating": "Rating: %1$s",
"cobblemon.cobblemon.ui.pokedex.scan.block.printed_photo_score": "Score: %1$s",
"cobblemon.cobblemon.ui.pokedex.scan.block.printed_photo_species_count": "Pokemon: %1$s",
"cobblemon.ui.pokedex.region.national": "National",
"cobblemon.ui.pokedex.region.alola": "Alola",
@@ -0,0 +1,41 @@
{
"block_entity_type": "cobblemon:berry",
"frames": [
{
"slot": 1,
"text": {
"translate": "cobblemon.ui.pokedex.scan.block.berry",
"with": [
{
"key": "berry_name"
}
]
},
"scale": 0.65
},
{
"slot": 2,
"text": {
"translate": "cobblemon.ui.pokedex.scan.block.berry_stage",
"with": [
{
"key": "stage"
}
]
},
"scale": 0.65
},
{
"slot": 3,
"text": {
"translate": "cobblemon.ui.pokedex.scan.block.berry_mulch",
"with": [
{
"key": "mulch_variant"
}
]
},
"scale": 0.65
}
]
}
@@ -0,0 +1,41 @@
{
"block_entity_type": "cobblemon:printed_photo_display",
"frames": [
{
"slot": 1,
"text": {
"translate": "cobblemon.ui.pokedex.scan.block.printed_photo_rating",
"with": [
{
"key": "rating"
}
]
},
"scale": 0.65
},
{
"slot": 2,
"text": {
"translate": "cobblemon.ui.pokedex.scan.block.printed_photo_score",
"with": [
{
"key": "score"
}
]
},
"scale": 0.65
},
{
"slot": 3,
"text": {
"translate": "cobblemon.ui.pokedex.scan.block.printed_photo_species_count",
"with": [
{
"key": "species_count"
}
]
},
"scale": 0.65
}
]
}