mirror of
https://gitlab.com/cable-mc/cobblemon.git
synced 2026-10-20 14:16:26 -04:00
Push test changes from my home PC
This commit is contained in:
@@ -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))
|
||||
|
||||
+87
@@ -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)
|
||||
}
|
||||
}
|
||||
+117
@@ -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)) }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
@@ -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]
|
||||
}
|
||||
+32
-11
@@ -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 {
|
||||
|
||||
+210
-90
@@ -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 {
|
||||
|
||||
+32
@@ -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)
|
||||
}
|
||||
}
|
||||
+34
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+63
@@ -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
|
||||
}
|
||||
}
|
||||
+50
@@ -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)
|
||||
}
|
||||
}
|
||||
+98
-43
@@ -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)
|
||||
}
|
||||
|
||||
+24
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
+8
@@ -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?>
|
||||
}
|
||||
+19
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
+41
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user