Compare commits

...
15 changed files with 557 additions and 6 deletions
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.client
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonClientImplementation
@@ -27,6 +28,7 @@ import com.cobblemon.mod.common.client.gui.PartyOverlay
import com.cobblemon.mod.common.client.gui.PartyOverlayDataControl
import com.cobblemon.mod.common.client.gui.RideControlsOverlay
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
import com.cobblemon.mod.common.client.gui.pokedex.page.PokedexPages
import com.cobblemon.mod.common.client.gui.cookingpot.CookingPotScreen
import com.cobblemon.mod.common.client.gui.tmmachine.TMMachineScreen
import com.cobblemon.mod.common.client.gui.party.PartyTutorialToasts
@@ -146,6 +148,7 @@ object CobblemonClient {
fun initialize(implementation: CobblemonClientImplementation) {
LOGGER.info("Initializing Cobblemon client")
this.implementation = implementation
Cobblemon.dataProvider.register(PokedexPages, reloadable = true)
PlatformEvents.CLIENT_PLAYER_LOGIN.subscribe { onLogin() }
PlatformEvents.CLIENT_PLAYER_LOGOUT.subscribe { onLogout() }
@@ -33,6 +33,9 @@ import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.pokedex.page.MainDexPokedexPage
import com.cobblemon.mod.common.client.gui.pokedex.page.PokedexGuiPage
import com.cobblemon.mod.common.client.gui.pokedex.page.PokedexPages
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.BASE_HEIGHT
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.BASE_WIDTH
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HEADER_BAR_HEIGHT
@@ -81,6 +84,8 @@ class PokedexGUI private constructor(
private val globeIcon = cobblemonResource("textures/gui/pokedex/globe_icon.png")
private val caughtSeenIcon = cobblemonResource("textures/gui/pokedex/caught_seen_icon.png")
private val pageArrowLeftIcon = cobblemonResource("textures/gui/pokedex/forms_arrow_left.png")
private val pageArrowRightIcon = cobblemonResource("textures/gui/pokedex/forms_arrow_right.png")
private val arrowUpIcon = cobblemonResource("textures/gui/pokedex/arrow_up.png")
private val arrowDownIcon = cobblemonResource("textures/gui/pokedex/arrow_down.png")
@@ -96,6 +101,10 @@ class PokedexGUI private constructor(
cobblemonResource("textures/gui/pokedex/tab_moves.png")
)
private const val PAGE_ARROW_WIDTH = 10
private const val PAGE_ARROW_HEIGHT = 16
private const val PAGE_SELECTOR_GAP = 7
/**
* Attempts to open this screen for a client.
*/
@@ -140,9 +149,13 @@ class PokedexGUI private constructor(
private var selectedSearchByType: SearchByType = SearchByType.SPECIES
private var selectedSecondaryFilter: SecondaryPokedexFilterType = SecondaryPokedexFilterType.ALL
private val tabButtons: MutableList<ScaledButton> = mutableListOf()
private var selectedPageIndex = 0
private var pages: List<PokedexGuiPage> = emptyList()
lateinit var tabInfoElement: GuiEventListener
var tabInfoIndex = TAB_DESCRIPTION
private val currentPage: PokedexGuiPage?
get() = pages.getOrNull(selectedPageIndex)
override fun renderBlurredBackground(delta: Float) {}
override fun renderMenuBackground(context: GuiGraphics) {}
@@ -150,6 +163,7 @@ class PokedexGUI private constructor(
public override fun init() {
super.init()
clearWidgets()
selectedPageIndex = 0
availableRegions = Dexes.dexEntryMap.keys.toList()
selectedRegionIndex = 0
@@ -233,6 +247,11 @@ class PokedexGUI private constructor(
addRenderableWidget(secondaryFilterButton)
updateFilters(true)
pages = PokedexPages.resolvePages()
if (pages.isNotEmpty()) {
selectedPageIndex = selectedPageIndex.coerceIn(0, pages.lastIndex)
currentPage?.onSelected(this)
}
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
@@ -259,6 +278,13 @@ class PokedexGUI private constructor(
height = BASE_HEIGHT
)
renderPageNavigation(context, x, y, mouseX, mouseY)
val page = currentPage
if (page != null && page !is MainDexPokedexPage) {
page.render(this, context, x, y, mouseX, mouseY, delta)
return
}
// Region
blitk(
matrixStack = matrices,
@@ -371,11 +397,18 @@ class PokedexGUI private constructor(
override fun onClose() {
if (blockPos != null) AdjustBlockEntityViewerCountPacket(blockPos, false).sendToServer()
pages.forEach { it.onClose(this) }
playSound(CobblemonSounds.POKEDEX_CLOSE)
super.onClose()
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (handlePageNavigationClick(mouseX, mouseY)) return true
val page = currentPage
if (page != null && page !is MainDexPokedexPage) {
return page.mouseClicked(this, mouseX, mouseY, button)
}
val canDisplayEntry = true //selectedForm?.unlockForms
if (::pokemonInfoWidget.isInitialized
@@ -396,12 +429,20 @@ class PokedexGUI private constructor(
}
override fun mouseReleased(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
val page = currentPage
if (page != null && page !is MainDexPokedexPage) {
return page.mouseReleased(this, pMouseX, pMouseY, pButton)
}
if (canDragRender) canDragRender = false
if (isDragging) isDragging = false
return super.mouseReleased(pMouseX, pMouseY, pButton)
}
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
val page = currentPage
if (page != null && page !is MainDexPokedexPage) {
return page.mouseDragged(this, mouseX, mouseY, button, deltaX, deltaY)
}
if (isDragging && canDragRender) {
val dragOffsetY = (oldDragPosX - mouseX).toFloat()
pokemonInfoWidget.rotationY = (((pokemonInfoWidget.rotationY + dragOffsetY) % 360 + 360) % 360)
@@ -411,6 +452,11 @@ class PokedexGUI private constructor(
}
override fun tick() {
val page = currentPage
if (page != null && page !is MainDexPokedexPage) {
page.tick(this)
return
}
if (::pokemonInfoWidget.isInitialized && !pokemonInfoWidget.isSuppressed) pokemonInfoWidget.tick()
}
@@ -682,6 +728,95 @@ class PokedexGUI private constructor(
updateTabInfoElement()
}
private fun renderPageNavigation(context: GuiGraphics, x: Int, y: Int, mouseX: Int, mouseY: Int) {
val centerX = x + (BASE_WIDTH / 2)
val title = lang(currentPage?.titleKey ?: "ui.pokedex.page.pokedex").bold()
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = title,
x = centerX,
y = y + 14,
shadow = true,
centered = true
)
if (pages.size <= 1) return
val leftX = centerX - PAGE_SELECTOR_GAP - PAGE_ARROW_WIDTH
val rightX = centerX + PAGE_SELECTOR_GAP
val arrowY = y + 13
val leftHovered = isInsidePageArrow(mouseX, mouseY, leftX, arrowY)
val rightHovered = isInsidePageArrow(mouseX, mouseY, rightX, arrowY)
blitk(
matrixStack = context.pose(),
texture = pageArrowLeftIcon,
x = leftX,
y = arrowY,
width = PAGE_ARROW_WIDTH,
height = PAGE_ARROW_HEIGHT,
vOffset = if (leftHovered) PAGE_ARROW_HEIGHT else 0,
textureHeight = PAGE_ARROW_HEIGHT * 2
)
blitk(
matrixStack = context.pose(),
texture = pageArrowRightIcon,
x = rightX,
y = arrowY,
width = PAGE_ARROW_WIDTH,
height = PAGE_ARROW_HEIGHT,
vOffset = if (rightHovered) PAGE_ARROW_HEIGHT else 0,
textureHeight = PAGE_ARROW_HEIGHT * 2
)
}
private fun handlePageNavigationClick(mouseX: Double, mouseY: Double): Boolean {
if (pages.size <= 1) return false
val x = (width - BASE_WIDTH) / 2
val y = (height - BASE_HEIGHT) / 2
val centerX = x + (BASE_WIDTH / 2)
val leftX = centerX - PAGE_SELECTOR_GAP - PAGE_ARROW_WIDTH
val rightX = centerX + PAGE_SELECTOR_GAP
val arrowY = y + 13
if (isInsidePageArrow(mouseX, mouseY, leftX, arrowY)) {
cyclePage(next = false)
return true
}
if (isInsidePageArrow(mouseX, mouseY, rightX, arrowY)) {
cyclePage(next = true)
return true
}
return false
}
private fun cyclePage(next: Boolean) {
if (pages.size <= 1) return
selectedPageIndex = if (next) {
(selectedPageIndex + 1) % pages.size
} else {
(selectedPageIndex - 1 + pages.size) % pages.size
}
canDragRender = false
isDragging = false
setFocused(null)
currentPage?.onSelected(this)
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
private fun isInsidePageArrow(mouseX: Number, mouseY: Number, x: Int, y: Int): Boolean {
val mX = mouseX.toDouble()
val mY = mouseY.toDouble()
return mX >= x && mX <= (x + PAGE_ARROW_WIDTH) && mY >= y && mY <= (y + PAGE_ARROW_HEIGHT)
}
fun canSelectTab(tabIndex: Int): Boolean {
val selectedForm = this.selectedForm ?: return false
val selectedEntry = this.selectedEntry ?: return false
@@ -0,0 +1,16 @@
/*
* 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.client.gui.pokedex.page
import net.minecraft.resources.ResourceLocation
class MainDexPokedexPage(
override val id: ResourceLocation,
override val titleKey: String
) : PokedexGuiPage
@@ -0,0 +1,139 @@
/*
* 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.client.gui.pokedex.page
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUI
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.mojang.blaze3d.platform.NativeImage
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.renderer.texture.DynamicTexture
import net.minecraft.resources.ResourceLocation
import java.io.File
import kotlin.math.min
class PhotoGalleryPokedexPage(
override val id: ResourceLocation,
override val titleKey: String
) : PokedexGuiPage {
companion object {
private const val GRID_COLUMNS = 4
private const val GRID_ROWS = 3
private const val THUMB_WIDTH = 68
private const val THUMB_HEIGHT = 36
private const val THUMB_GAP = 4
private const val MAX_THUMBS = GRID_COLUMNS * GRID_ROWS
}
private val textures = mutableMapOf<String, Pair<ResourceLocation, DynamicTexture>>()
private var screenshots: List<File> = emptyList()
override fun onSelected(gui: PokedexGUI) {
refreshScreenshots()
}
override fun onClose(gui: PokedexGUI) {
textures.values.forEach { (_, texture) -> texture.close() }
textures.clear()
}
override fun render(gui: PokedexGUI, context: GuiGraphics, x: Int, y: Int, mouseX: Int, mouseY: Int, delta: Float) {
val countText = lang("ui.pokedex.gallery.count", screenshots.size).bold()
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = countText,
x = x + 173,
y = y + 31,
shadow = true,
centered = true
)
if (screenshots.isEmpty()) {
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.pokedex.gallery.empty").bold(),
x = x + 173,
y = y + 92,
shadow = true,
centered = true
)
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.pokedex.gallery.empty_hint"),
x = x + 173,
y = y + 103,
shadow = true,
centered = true
)
return
}
val startX = x + 28
val startY = y + 43
val visible = screenshots.take(MAX_THUMBS)
visible.forEachIndexed { index, file ->
val col = index % GRID_COLUMNS
val row = index / GRID_COLUMNS
val thumbX = startX + (col * (THUMB_WIDTH + THUMB_GAP))
val thumbY = startY + (row * (THUMB_HEIGHT + THUMB_GAP))
context.fill(thumbX - 1, thumbY - 1, thumbX + THUMB_WIDTH + 1, thumbY + THUMB_HEIGHT + 1, -14536682)
val texture = getTexture(file) ?: return@forEachIndexed
blitk(
matrixStack = context.pose(),
texture = texture,
x = thumbX,
y = thumbY,
width = THUMB_WIDTH,
height = THUMB_HEIGHT
)
}
}
private fun refreshScreenshots() {
val screenshotDirectory = File(Minecraft.getInstance().gameDirectory, "screenshots/cobblemon")
screenshots = screenshotDirectory
.listFiles { file -> file.isFile && file.extension.equals("png", ignoreCase = true) }
?.sortedByDescending(File::lastModified)
?: emptyList()
val activeKeys = screenshots.map(::textureKey).toSet()
val stale = textures.keys.filterNot { it in activeKeys }
stale.forEach { key ->
textures.remove(key)?.second?.close()
}
}
private fun getTexture(file: File): ResourceLocation? {
val key = textureKey(file)
textures[key]?.let { return it.first }
return runCatching {
file.inputStream().use { stream ->
val nativeImage = NativeImage.read(stream)
val texture = DynamicTexture(nativeImage)
val location = cobblemonResource("pokedex/gallery/${key.hashCode().toUInt().toString(16)}")
Minecraft.getInstance().textureManager.register(location, texture)
textures[key] = location to texture
location
}
}.getOrNull()
}
private fun textureKey(file: File): String = "${file.absolutePath}:${file.lastModified()}:${min(file.length(), Int.MAX_VALUE.toLong())}"
}
@@ -0,0 +1,27 @@
/*
* 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.client.gui.pokedex.page
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUI
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.resources.ResourceLocation
interface PokedexGuiPage {
val id: ResourceLocation
val titleKey: String
fun onSelected(gui: PokedexGUI) {}
fun onClose(gui: PokedexGUI) {}
fun render(gui: PokedexGUI, context: GuiGraphics, x: Int, y: Int, mouseX: Int, mouseY: Int, delta: Float) {}
fun mouseClicked(gui: PokedexGUI, mouseX: Double, mouseY: Double, button: Int): Boolean = false
fun mouseReleased(gui: PokedexGUI, mouseX: Double, mouseY: Double, button: Int): Boolean = false
fun mouseDragged(gui: PokedexGUI, mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean = false
fun tick(gui: PokedexGUI) {}
}
@@ -0,0 +1,16 @@
/*
* 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.client.gui.pokedex.page
data class PokedexPageDefinition(
val pageType: String = "",
val titleKey: String = "ui.pokedex.page.pokedex",
val order: Int = 0,
val enabled: Boolean = true
)
@@ -0,0 +1,96 @@
/*
* 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.client.gui.pokedex.page
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
object PokedexPages : JsonDataRegistry<PokedexPageDefinition> {
override val id = cobblemonResource("pokedex_pages")
override val type = PackType.CLIENT_RESOURCES
override val observable = SimpleObservable<PokedexPages>()
override val gson = GsonBuilder()
.disableHtmlEscaping()
.setPrettyPrinting()
.create()
override val typeToken: TypeToken<PokedexPageDefinition> = TypeToken.get(PokedexPageDefinition::class.java)
override val resourcePath = "pokedex/pages"
private val definitions = linkedMapOf<ResourceLocation, PokedexPageDefinition>()
private val pageFactories = mutableMapOf<ResourceLocation, (ResourceLocation, PokedexPageDefinition) -> PokedexGuiPage>()
init {
registerPageType(cobblemonResource("main_dex")) { pageId, def -> MainDexPokedexPage(pageId, def.titleKey) }
registerPageType(cobblemonResource("photo_gallery")) { pageId, def -> PhotoGalleryPokedexPage(pageId, def.titleKey) }
}
override fun sync(player: ServerPlayer) {}
override fun reload(data: Map<ResourceLocation, PokedexPageDefinition>) {
definitions.clear()
data.forEach { (identifier, page) ->
if (page.enabled) {
definitions[identifier] = page
}
}
observable.emit(this)
}
fun registerPageType(
typeId: ResourceLocation,
factory: (ResourceLocation, PokedexPageDefinition) -> PokedexGuiPage
) {
pageFactories[typeId] = factory
}
fun resolvePages(): List<PokedexGuiPage> {
val pages = definitions
.entries
.sortedWith(compareBy<Map.Entry<ResourceLocation, PokedexPageDefinition>> { it.value.order }.thenBy { it.key.toString() })
.mapNotNull { (id, definition) ->
val typeId = parseType(definition) ?: return@mapNotNull null
val factory = pageFactories[typeId]
if (factory == null) {
Cobblemon.LOGGER.warn("Skipped Pokedex page {} - unknown page type {}", id, definition.pageType)
null
} else {
factory(id, definition)
}
}
.toMutableList()
if (pages.isEmpty()) {
pages.add(MainDexPokedexPage(cobblemonResource("fallback_main_dex"), "ui.pokedex.page.pokedex"))
pages.add(PhotoGalleryPokedexPage(cobblemonResource("fallback_photo_gallery"), "ui.pokedex.page.photo_gallery"))
return pages
}
if (pages.none { it is MainDexPokedexPage }) {
pages.add(0, MainDexPokedexPage(cobblemonResource("fallback_main_dex"), "ui.pokedex.page.pokedex"))
}
return pages
}
private fun parseType(definition: PokedexPageDefinition): ResourceLocation? {
if (definition.pageType.isBlank()) return null
return runCatching { ResourceLocation.parse(definition.pageType) }
.onFailure {
Cobblemon.LOGGER.warn("Skipped Pokedex page definition - invalid pageType {}", definition.pageType)
}
.getOrNull()
}
}
@@ -434,8 +434,9 @@ class PokedexScannerRenderer {
fun onRenderOverlay(graphics: GuiGraphics, tickCounter: DeltaTracker) {
if ((CobblemonClient.pokedexUsageContext.scanningGuiOpen || CobblemonClient.pokedexUsageContext.transitionIntervals > 0) && Minecraft.getInstance().options.cameraType.isFirstPerson) {
CobblemonClient.pokedexUsageContext.cacheScannerFrame()
val tickDelta = tickCounter.realtimeDeltaTicks
renderScanOverlay(graphics, tickDelta)
}
}
}
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.pokedex.scanner
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.PokedexLearnedInformation
@@ -21,13 +22,24 @@ import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.StartScannin
import kotlin.math.max
import kotlin.math.min
import kotlin.random.Random
import com.mojang.blaze3d.pipeline.MainTarget
import com.mojang.blaze3d.pipeline.RenderTarget
import com.mojang.blaze3d.platform.GlStateManager
import net.minecraft.client.DeltaTracker
import net.minecraft.client.Minecraft
import net.minecraft.client.Screenshot
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.player.LocalPlayer
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import org.lwjgl.opengl.GL30C
import org.lwjgl.opengl.GL40
import java.nio.file.Files
import java.nio.file.Path
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import kotlin.math.exp
import kotlin.math.ln
@@ -67,6 +79,8 @@ class PokedexUsageContext {
var type: PokedexType = PokedexType.RED
var availableInfoFrames: MutableList<Boolean?> = mutableListOf(null, null, null, null)
val renderer: PokedexScannerRenderer = PokedexScannerRenderer()
private var previousAttackInput = false
private var pictureBuffer: RenderTarget? = null
fun stopUsing(ticksInUse: Int, speciesId: ResourceLocation? = null) {
if (ticksInUse < OPEN_SCANNER_BUFFER_TICKS) {
@@ -146,6 +160,39 @@ class PokedexUsageContext {
}
}
fun handleAttackInput(isHeld: Boolean) {
if (isHeld && !previousAttackInput && scanningGuiOpen) {
takeScanPicture()
}
attackKeyHeld(isHeld)
previousAttackInput = isHeld
}
fun cacheScannerFrame() {
if (!scanningGuiOpen) return
val minecraft = Minecraft.getInstance()
val mainTarget = minecraft.mainRenderTarget
val buffer = ensurePictureBuffer(mainTarget.width, mainTarget.height)
GlStateManager._glBindFramebuffer(GL30C.GL_READ_FRAMEBUFFER, mainTarget.frameBufferId)
GlStateManager._glBindFramebuffer(GL30C.GL_DRAW_FRAMEBUFFER, buffer.frameBufferId)
GL40.glBlitFramebuffer(
0,
0,
mainTarget.width,
mainTarget.height,
0,
0,
buffer.width,
buffer.height,
GL30C.GL_COLOR_BUFFER_BIT or GL30C.GL_DEPTH_BUFFER_BIT or GL30C.GL_STENCIL_BUFFER_BIT,
GL30C.GL_NEAREST
)
GlStateManager._glBindFramebuffer(GL30C.GL_FRAMEBUFFER, mainTarget.frameBufferId)
}
fun tryScanPokemon(user: LocalPlayer) {
val targetScannableEntity = PokemonScanner.findScannableEntity(user, zoomLevel.toInt())
if (targetScannableEntity != null) {
@@ -210,6 +257,7 @@ class PokedexUsageContext {
zoomLevel = 0F
zoomModifier = 1F
focusIntervals = 0F
previousAttackInput = false
resetFocusedPokemonState()
if (resetAnimationStates) {
@@ -240,4 +288,56 @@ class PokedexUsageContext {
fun playSound(soundEvent: SoundEvent) {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(soundEvent, 1.0F))
}
}
private fun ensurePictureBuffer(width: Int, height: Int): RenderTarget {
val existing = pictureBuffer
if (existing == null) {
val created = MainTarget(width, height)
pictureBuffer = created
return created
}
if (existing.width != width || existing.height != height) {
existing.resize(width, height, Minecraft.ON_OSX)
}
return existing
}
private fun takeScanPicture() {
val minecraft = Minecraft.getInstance()
val buffer = pictureBuffer ?: return
val image = Screenshot.takeScreenshot(buffer)
try {
val outputPath = nextPhotoPath(minecraft.gameDirectory.toPath())
Files.write(outputPath, image.asByteArray())
minecraft.player?.displayClientMessage(
Component.literal("Saved Pokedex scan photo: ${outputPath.fileName}"),
true
)
} catch (e: Exception) {
Cobblemon.LOGGER.error("Failed to save Pokedex scan photo", e)
minecraft.player?.displayClientMessage(
Component.literal("Failed to save Pokedex scan photo"),
true
)
} finally {
image.close()
}
}
private fun nextPhotoPath(gameDirectory: Path): Path {
val directory = gameDirectory.resolve("screenshots").resolve("cobblemon")
Files.createDirectories(directory)
val timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss"))
var suffix = 0
while (true) {
val suffixString = if (suffix == 0) "" else "_$suffix"
val candidate = directory.resolve("pokedex_scan_${timestamp}${suffixString}.png")
if (!Files.exists(candidate)) return candidate
suffix++
}
}
}
@@ -9553,6 +9553,12 @@
"cobblemon.snapshots.warning.dont_show_again": "Don't Show Again",
"cobblemon.snapshots.warning.dont_show_again_tip": "This setting may be ignored on newer Cobblemon snapshots (e.g. 1.7.0 vs 1.8.0)",
"cobblemon.ui.pokedex.page.pokedex": "Pokedex",
"cobblemon.ui.pokedex.page.photo_gallery": "Photo Gallery",
"cobblemon.ui.pokedex.gallery.count": "Photos: %s",
"cobblemon.ui.pokedex.gallery.empty": "No Pokedex photos yet.",
"cobblemon.ui.pokedex.gallery.empty_hint": "Use scan mode and click to snap.",
"stat.cobblemon.battles_fled": "Pokémon Battles Fled",
"stat.cobblemon.battles_lost": "Pokémon Battles Lost",
"stat.cobblemon.battles_total": "Pokémon Total Battles",
@@ -0,0 +1,6 @@
{
"pageType": "cobblemon:photo_gallery",
"titleKey": "ui.pokedex.page.photo_gallery",
"order": 2,
"enabled": true
}
@@ -0,0 +1,6 @@
{
"pageType": "cobblemon:main_dex",
"titleKey": "ui.pokedex.page.pokedex",
"order": 0,
"enabled": true
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

@@ -143,7 +143,7 @@ class CobblemonFabricClient: ClientModInitializer, CobblemonClientImplementation
pokedexUsageContext.scanningGuiOpen
) {
val keyAttack = client.options.keyAttack
pokedexUsageContext.attackKeyHeld(keyAttack.isDown)
pokedexUsageContext.handleAttackInput(keyAttack.isDown)
}
}
})
@@ -201,4 +201,4 @@ class CobblemonFabricClient: ClientModInitializer, CobblemonClientImplementation
override fun <T : Entity> registerEntityRenderer(type: EntityType<out T>, factory: EntityRendererProvider<T>) {
EntityRendererRegistry.register(type, factory)
}
}
}
@@ -232,7 +232,7 @@ object CobblemonNeoForgeClient : CobblemonClientImplementation {
pokedexUsageContext.scanningGuiOpen
) {
val keyAttack = client.options.keyAttack
pokedexUsageContext.attackKeyHeld(keyAttack.isDown)
pokedexUsageContext.handleAttackInput(keyAttack.isDown)
}
}
}
@@ -240,4 +240,4 @@ object CobblemonNeoForgeClient : CobblemonClientImplementation {
internal fun registerResourceReloader(reloader: PreparableReloadListener) {
(Minecraft.getInstance().resourceManager as ReloadableResourceManager).registerReloadListener(reloader)
}
}
}