forked from mirrors/cobblemon
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94154776eb | ||
|
|
b76e75efcc |
@@ -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
|
||||
|
||||
+16
@@ -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
|
||||
+139
@@ -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())}"
|
||||
}
|
||||
+27
@@ -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) {}
|
||||
}
|
||||
+16
@@ -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
|
||||
)
|
||||
+96
@@ -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()
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+101
-1
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user