Merge branch 'selecting-starters-in-style' into 'main'

The Cooler Starter Selection GUI

See merge request cable-mc/cobblemon!2164
This commit is contained in:
Polymeta
2026-02-26 11:58:31 +00:00
52 changed files with 658 additions and 301 deletions
+1
View File
@@ -18,6 +18,7 @@
- Restructured Botany Pots integration folder structure
- Decrease default pastured mon wander radius to 32.
- Factor in the weight of pokemon when calculating pushing forces between hitboxes.
- Remade the Starter Selection Screen with new assets
### Fixes
- Fixes https://gitlab.com/cable-mc/cobblemon/-/issues/1943
@@ -47,6 +47,7 @@ fun drawProfilePokemon(
a: Float = 1F,
headYaw: Float = 0f,
headPitch: Float = 0f,
blockLight: Int = 13
) = drawProfilePokemon(
species = renderablePokemon.species.resourceIdentifier,
matrixStack = matrixStack,
@@ -63,6 +64,7 @@ fun drawProfilePokemon(
a = a,
headYaw = headYaw,
headPitch = headPitch,
blockLight = blockLight
)
fun drawProfilePokemon(
@@ -82,6 +84,7 @@ fun drawProfilePokemon(
a: Float = 1F,
headYaw: Float = 0f,
headPitch: Float = 0f,
blockLight: Int = 13
) {
RenderSystem.applyModelViewMatrix()
matrixStack.scale(scale, scale, -scale)
@@ -138,7 +141,7 @@ fun drawProfilePokemon(
val light1 = Vector3f(-1F, 1F, 1.0F)
val light2 = Vector3f(1.3F, -1F, 1.0F)
RenderSystem.setShaderLights(light1, light2)
val packedLight = LightTexture.pack(11, 7)
val packedLight = LightTexture.pack(blockLight, 0)
val colour = toHex(r, g, b, a)
model.withLayerContext(bufferSource, state, VaryingModelRepository.getLayers(species, state)) {
@@ -0,0 +1,153 @@
/*
* 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
import com.cobblemon.mod.common.api.gui.blitk
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.AbstractWidget
import net.minecraft.client.gui.components.events.GuiEventListener
import net.minecraft.client.gui.narration.NarrationElementOutput
import net.minecraft.network.chat.CommonComponents
import net.minecraft.resources.ResourceLocation
import kotlin.math.floor
open class VariableObjectSelectionList(x: Int, y: Int, width: Int, height: Int): AbstractWidget(x, y, width, height, CommonComponents.EMPTY) {
val children = mutableListOf<Entry>()
var listHeight = 0
var scrollAmount = 0.0
fun updateScroll(offset: Double) {
setScroll(scrollAmount + offset)
}
fun getMaxScroll(): Int {
return (listHeight - height).coerceAtLeast(0)
}
fun setScroll(scroll: Double) {
scrollAmount = scroll.coerceIn(0.0, getMaxScroll().toDouble())
}
fun removeEntry(index: Int) {
listHeight -= children[index].height
children.removeAt(index)
updatePositions()
}
fun removeEntry(entry: Entry) {
listHeight -= entry.height
children.remove(entry)
updatePositions()
}
fun removeLastEntry() {
listHeight -= children.last().height
children.removeLast()
}
fun updatePositions() {
var stackHeight = 0
children.forEach {
it.scrolledY = stackHeight
stackHeight += it.height
}
updateScroll(0.0)
}
fun addEntry(entry: Entry) {
children.add(entry)
entry.scrolledY = listHeight
listHeight += entry.height
}
override fun renderWidget(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
var stackHeight = -scrollAmount.toInt()
guiGraphics.enableScissor(x, y, x + width, y + height)
children.forEachIndexed { index, entry ->
if (stackHeight in 1..<height || stackHeight + entry.height in 1..<height) {
entry.render(guiGraphics, index, x, y + stackHeight, mouseX, mouseY, partialTick)
}
stackHeight += entry.height
}
guiGraphics.disableScissor()
}
fun getEntryAtPosition(mouseX: Int, mouseY: Int): Entry? {
if (mouseX in x..(x + width) && mouseY in y..(y + height)) {
var stackHeight = -scrollAmount
children.forEach {
if (mouseY.toDouble() in (y + stackHeight)..(y + stackHeight + it.height)) {
return it
}
stackHeight += it.height
}
}
return null
}
fun ensureVisible(index: Int) {
val entry = children[index]
if (entry.scrolledY - scrollAmount < 0) {
setScroll(entry.scrolledY.toDouble())
} else if (entry.scrolledY + entry.height > height) {
setScroll(entry.scrolledY.toDouble() + entry.height)
}
}
override fun mouseScrolled(mouseX: Double, mouseY: Double, horizontalAmount: Double, verticalAmount: Double): Boolean {
updateScroll(-verticalAmount * (listHeight / children.size / 2))
return true
}
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
val entry = getEntryAtPosition(pMouseX.toInt(), pMouseY.toInt())
if (entry != null) {
return entry.mouseClicked(pMouseX, pMouseY, pButton)
}
return false
}
override fun mouseReleased(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
return children.any {
it.mouseReleased(pMouseX, pMouseY, pButton)
} || super.mouseReleased(pMouseX, pMouseY, pButton)
}
fun getRowTop(entry: Entry): Int {
var stackHeight = -scrollAmount.toInt()
children.forEach {
if (it == entry) {
return y + stackHeight
}
stackHeight += it.height
}
return y
}
override fun defaultButtonNarrationText(pNarrationElementOutput: NarrationElementOutput) {}
override fun updateWidgetNarration(narrationElementOutput: NarrationElementOutput) {}
abstract inner class Entry(var height: Int): GuiEventListener {
var scrolledY: Int = 0
private var widgetFocused = false
override fun setFocused(focused: Boolean) {
this.widgetFocused = focused
}
override fun isFocused(): Boolean = widgetFocused
abstract fun render(guiGraphics: GuiGraphics, index: Int, x: Int, y: Int, mouseX: Int, mouseY: Int, partialTick: Float)
}
inner class EmptySeparator(height: Int): Entry(height) {
override fun render(guiGraphics: GuiGraphics, index: Int, x: Int, y: Int, mouseX: Int, mouseY: Int, partialTick: Float) {}
}
}
@@ -0,0 +1,100 @@
/*
* 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
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.AbstractWidget
import net.minecraft.client.gui.narration.NarrationElementOutput
import net.minecraft.client.sounds.SoundManager
import net.minecraft.network.chat.CommonComponents
import net.minecraft.resources.ResourceLocation
class VariableObjectSelectionScrollbar(var parent: VariableObjectSelectionList, x: Int, y: Int, width: Int, height: Int): AbstractWidget(x, y, width, height, CommonComponents.EMPTY) {
companion object {
private val scrollerResource: ResourceLocation = ResourceLocation("minecraft", "textures/gui/sprites/widget/scroller.png")
private val scrollerBackgroundResource: ResourceLocation = ResourceLocation("minecraft", "textures/gui/sprites/widget/scroller_background.png")
private const val SCROLLER_HEIGHT = 32
}
private var scrolling = false
private var scrollHoldOffset = 0.0
fun scrollbarVisible(): Boolean = parent.getMaxScroll() > 0
override fun renderWidget(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
if (scrollbarVisible()) {
val scrollPosition = (parent.scrollAmount / parent.getMaxScroll()) * (height - SCROLLER_HEIGHT)
blitk(
matrixStack = guiGraphics.pose(),
texture = scrollerBackgroundResource,
x = x,
y = y,
width = width,
height = height,
)
blitk(
matrixStack = guiGraphics.pose(),
texture = scrollerResource,
x = x,
y = y + scrollPosition,
width = width,
height = SCROLLER_HEIGHT,
textureWidth = 6,
textureHeight = SCROLLER_HEIGHT
)
}
}
override fun mouseScrolled(mouseX: Double, mouseY: Double, scrollX: Double, scrollY: Double): Boolean {
return parent.mouseScrolled(mouseX, mouseY, scrollX, scrollY)
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (mouseX.toInt() in x..(x + width) && mouseY.toInt() in y..(y + height)) {
if (button == 0) {
var scrollPosition = (parent.scrollAmount / parent.getMaxScroll()) * (height - SCROLLER_HEIGHT)
if (mouseY !in (y + scrollPosition)..(y + scrollPosition + SCROLLER_HEIGHT)) {
val scrollRatio =
((mouseY - y - SCROLLER_HEIGHT / 2) / (height - SCROLLER_HEIGHT)).coerceIn(0.0, 1.0)
parent.setScroll(scrollRatio * parent.getMaxScroll())
scrollPosition = (parent.scrollAmount / parent.getMaxScroll()) * (height - SCROLLER_HEIGHT)
}
scrollHoldOffset = mouseY - y - scrollPosition
scrolling = true
return true
}
}
return false
}
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, dragX: Double, dragY: Double): Boolean {
if (button == 0 && scrolling) {
val scrollRatio = ((mouseY - y - scrollHoldOffset) / (height - SCROLLER_HEIGHT)).coerceIn(0.0,1.0)
parent.setScroll(scrollRatio * parent.getMaxScroll())
return true
}
return false
}
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (button == 0 && scrolling) {
scrolling = false
return true
}
return false
}
override fun playDownSound(handler: SoundManager) {}
override fun updateWidgetNarration(narrationElementOutput: NarrationElementOutput) {}
}
@@ -375,7 +375,8 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
matrixStack = matrices,
partialTicks = delta,
rotation = Quaternionf().fromEulerXYZDegrees(rotationVector),
state = state
state = state,
blockLight = 15
)
matrices.popPose()
@@ -15,18 +15,19 @@ import com.cobblemon.mod.common.api.gui.ColourLibrary
import com.cobblemon.mod.common.api.gui.MultiLineLabelK
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.api.types.ElementalType
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.ExitButton
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.VariableObjectSelectionScrollbar
import com.cobblemon.mod.common.client.gui.startselection.widgets.CategoryList
import com.cobblemon.mod.common.client.gui.startselection.widgets.ExitButton
import com.cobblemon.mod.common.client.gui.startselection.widgets.preview.ArrowButton
import com.cobblemon.mod.common.client.gui.startselection.widgets.preview.SelectionButton
import com.cobblemon.mod.common.client.gui.startselection.widgets.SelectionButton
import com.cobblemon.mod.common.client.gui.startselection.widgets.preview.StarterRoundabout
import com.cobblemon.mod.common.client.gui.summary.Summary.Companion.PARTY
import com.cobblemon.mod.common.client.gui.summary.widgets.ModelWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.type.DualTypeWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.type.SingleTypeWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.type.TypeWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.config.starter.RenderableStarterCategory
import com.cobblemon.mod.common.net.messages.server.SelectStarterPacket
@@ -34,13 +35,14 @@ import com.cobblemon.mod.common.pokemon.RenderablePokemon
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.math.toRGB
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.toasts.Toast
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import java.io.FileNotFoundException
/**
* Starterselection Screen Thingy
@@ -49,32 +51,31 @@ import net.minecraft.network.chat.Component
* @since 2022-06-18
*/
class StarterSelectionScreen(private val categories: List<RenderableStarterCategory>): Screen("cobblemon.ui.starter.title".asTranslated()), CobblemonRenderable {
companion object {
// Size of UI at scale 1
private const val BASE_WIDTH = 200
private const val BASE_HEIGHT = 175
private const val BASE_WIDTH = 239
private const val BASE_HEIGHT = 197
// Resources
private val base = cobblemonResource("textures/gui/starterselection/starterselection_base.png")
private val baseUnderlay = cobblemonResource("textures/gui/starterselection/starterselection_base_underlay.png")
private val baseFrame = cobblemonResource("textures/gui/starterselection/starterselection_base_frame.png")
private val baseResource = cobblemonResource("textures/gui/starterselection/base.png")
private val backgroundResource = cobblemonResource("textures/gui/starterselection/background.png")
private val backgroundBallResource = cobblemonResource("textures/gui/starterselection/background_poke_ball.png")
// Type Backgrounds
private val singleTypeBackground = cobblemonResource("textures/gui/starterselection/starterselection_type_slot1.png")
private val doubleTypeBackground = cobblemonResource("textures/gui/starterselection/starterselection_type_slot2.png")
private val platformResource = cobblemonResource("textures/gui/starterselection/platform_base.png")
private val platformShadow = cobblemonResource("textures/gui/pokedex/platform_shadow.png")
private val typeSpacerResource = cobblemonResource("textures/gui/starterselection/type_spacer_single.png")
private val ballResource = cobblemonResource("textures/item/poke_balls/poke_ball.png")
}
private var currentSelection = 0
private lateinit var currentCategory: RenderableStarterCategory
var currentSelection = 0
lateinit var currentCategory: RenderableStarterCategory
private lateinit var modelWidget: ModelWidget
private lateinit var currentPokemon: RenderablePokemon
private lateinit var typeWidget: TypeWidget
private lateinit var starterRoundaboutCenter: StarterRoundabout
private lateinit var starterRoundaboutLeft: StarterRoundabout
private lateinit var starterRoundaboutRight: StarterRoundabout
override fun renderBlurredBackground(delta: Float) { }
var ticksElapsed = 0
var currentBallBackgroundFrame = 0
override fun renderBlurredBackground(delta: Float) {}
override fun renderMenuBackground(context: GuiGraphics) {}
@@ -95,55 +96,24 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
return
}
addRenderableWidget(
CategoryList(
paneWidth = 71, paneHeight = BASE_HEIGHT - 11,
topOffset = 6,
entryHeight = 20, entryWidth = 57,
categories = categories,
listX = x - 2, listY = y + 8,
starterSelectionScreen = this
)
)
val rightButton = ArrowButton(
pX = x + 183, pY = y + 151,
pWidth = 9, pHeight = 14,
right = true
) {
right()
}
val leftButton = ArrowButton(
pX = x + 72, pY = y + 151,
pWidth = 9, pHeight = 14,
right = false
) {
left()
}
addRenderableWidget(rightButton)
addRenderableWidget(leftButton)
currentCategory = categories.first()
currentPokemon = currentCategory.pokemon[currentSelection]
with(currentPokemon) {
modelWidget = ModelWidget(
pX = x + 85, pY = y + 50,
pWidth = 102, pHeight = 100,
pX = x + 6, pY = y + 17,
pWidth = 118, pHeight = 100,
pokemon = this,
baseScale = 2.0f,
baseScale = 2.7f,
playCryOnClick = true,
offsetY = -12.0
)
typeWidget = typeWidget(this, x, y)
}
addRenderableWidget(modelWidget)
val selectionButton = SelectionButton(
pX = x + 106, pY = y + 124,
pX = x + 13, pY = y + 180,
pWidth = SelectionButton.BUTTON_WIDTH, pHeight = SelectionButton.BUTTON_HEIGHT
) {
CobblemonNetwork.sendToServer(
@@ -152,182 +122,214 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
selected = currentSelection
)
)
Minecraft.getInstance().setScreen(null)
playSound(CobblemonSounds.GUI_CLICK)
onClose()
}
addRenderableWidget(selectionButton)
starterRoundaboutCenter = StarterRoundabout(
pX = x + 119, pY = height / 2 + 84,
pWidth = StarterRoundabout.MODEL_WIDTH, pHeight = StarterRoundabout.MODEL_HEIGHT,
pokemon = currentPokemon,
rotationVector = this.modelWidget.rotationVector
)
starterRoundaboutLeft = StarterRoundabout(
pX = x + 89, pY = height / 2 + 84,
pWidth = StarterRoundabout.MODEL_WIDTH, pHeight = StarterRoundabout.MODEL_HEIGHT,
pokemon = currentCategory.pokemon[leftOfCurrentSelection()],
clickAction = { _, _ -> this.left() },
rotationVector = this.modelWidget.rotationVector
)
starterRoundaboutRight = StarterRoundabout(
pX = x + 149, pY = height / 2 + 84,
pWidth = StarterRoundabout.MODEL_WIDTH, pHeight = StarterRoundabout.MODEL_HEIGHT,
pokemon = currentCategory.pokemon[rightOfCurrentSelection()],
clickAction = { _, _ -> this.right() },
rotationVector = this.modelWidget.rotationVector
)
addRenderableWidget(starterRoundaboutLeft)
addRenderableWidget(starterRoundaboutCenter)
addRenderableWidget(starterRoundaboutRight)
// Add Exit Button
addRenderableWidget(
ExitButton(
pX = x + 181, pY = y + 2,
pWidth = 16, pHeight = 12,
pXTexStart = 0, pYTexStart = 0, pYDiffText = 0
) {
Minecraft.getInstance().setScreen(null)
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
ExitButton(pX = x + 210, pY = y + 181) {
playSound(CobblemonSounds.GUI_CLICK)
onClose()
}
)
val categoryList = CategoryList(
paneWidth = 97, paneHeight = 145,
categories = categories,
listX = x + 134, listY = y + 27,
starterSelectionScreen = this
)
addRenderableWidget(categoryList)
addRenderableWidget(VariableObjectSelectionScrollbar(
parent = categoryList,
x = x + 231,
y = y + 27,
width = 3,
height = 145
))
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val matrices = context.pose()
val x = (width - BASE_WIDTH) / 2
val y = (height - BASE_HEIGHT) / 2
// Render Underlay
//Background
blitk(
matrixStack = matrices,
texture = baseUnderlay,
x = x, y = y,
width = BASE_WIDTH, height = BASE_HEIGHT
texture = backgroundResource,
x = x + 6,
y = y + 17,
width = 118,
height = 100
)
// Render Base
blitk(
matrixStack = matrices,
texture = base,
x = x, y = y,
width = BASE_WIDTH, height = BASE_HEIGHT
texture = backgroundBallResource,
x = x + 10.5,
y = y + 12.5,
width = 109,
height = 109,
textureHeight = 1744,
vOffset = currentBallBackgroundFrame * 109
)
// Render Frame
val (r, g, b) = currentPokemon.form.primaryType.hue.toRGB()
//Platform
blitk(
matrixStack = matrices,
texture = baseFrame,
red = r,
green = g,
blue = b,
x = x, y = y,
width = BASE_WIDTH, height = BASE_HEIGHT
texture = platformResource,
x = x + 8.5,
y = y + 88,
width = 113,
height = 32
)
// Render Text
val typePlatform = getPlatformResource(currentPokemon.form.primaryType)
if (typePlatform != null) {
blitk(
matrixStack = matrices,
texture = typePlatform,
x = x + 8.5,
y = y + 82,
width = 113,
height = 30
)
}
blitk(
matrixStack = matrices,
texture = platformShadow,
x = (x + 43) / 0.5F,
y = (y + 93.5) / 0.5F,
width = 90,
height = 20,
scale = 0.5F
)
//Base
blitk(
matrixStack = matrices,
texture = baseResource,
x = x,
y = y,
width = BASE_WIDTH,
height = BASE_HEIGHT
)
//Type Icon
TypeIcon(
x = x + 65,
y = y + 120,
type = currentPokemon.form.primaryType,
secondaryType = currentPokemon.form.secondaryType,
centeredX = true
).render(context)
if (currentPokemon.form.secondaryType == null) {
blitk(
matrixStack = matrices,
texture = typeSpacerResource,
x = x + 47,
y = y + 123,
width = 36,
height = 12
)
}
//Name
blitk(
matrixStack = matrices,
texture = ballResource,
x = (x + 4) / 0.5F,
y = (y + 3) / 0.5F,
width = 16,
height = 16,
scale = 0.5F
)
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = currentPokemon.species.translatedName.bold(),
x = x + 14,
y = y + 2,
shadow = true
)
// Pokédex Number
// Add preceding zeroes if Pokédex number is less than 4 digits
var dexNo = currentPokemon.species.nationalPokedexNumber.toString()
while (dexNo.length < 4) {
dexNo = "0$dexNo"
}
dexNo = "#$dexNo"
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = dexNo.text().bold(),
x = x + 79,
y = y + 1,
shadow = true
)
//"Title"
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.starter.title").bold(),
x = x + 125, y = y + 3F,
centered = true,
scale = 1.4F,
maxCharacterWidth = 120,
shadow = true
x = x + 172,
y = y + 11,
shadow = true,
centered = true
)
// Render Name
val pokemonName = currentPokemon.species.translatedName
val scale = 0.8F
drawScaledText(
context = context,
text = pokemonName,
centered = true,
scale = scale,
maxCharacterWidth = 50,
x = x + 94,
y = y + 19.5,
shadow = false
)
//Description Text
val smallTextScale = 0.5F
// Render Description
val scale2 = 0.60F
matrices.pushPose()
matrices.scale(scale2, scale2, 1F)
// TODO use all pokedex lines across multiple clickable pages in this screen
matrices.scale(smallTextScale, smallTextScale, 1F)
MultiLineLabelK.create(
component = currentPokemon.form.pokedex.first().asTranslated(),
width = 127,
maxLines = 4
width = 114 / smallTextScale,
maxLines = 5
).renderLeftAligned(
context = context,
x = (x + 119) / scale2 + 4, y = (y + 18) / scale2 + 4.0,
ySpacing = (8.0 / scale2) - 1.25,
colour = ColourLibrary.WHITE, shadow = false
x = (x + 8) / smallTextScale,
y = (y + 143) / smallTextScale,
ySpacing = 5.5 / smallTextScale,
colour = ColourLibrary.WHITE,
shadow = true
)
matrices.popPose()
// Render the type background
blitk(
matrixStack = matrices,
texture = currentPokemon.form.secondaryType?.let { doubleTypeBackground } ?: singleTypeBackground,
x = (currentPokemon.form.secondaryType?.let { x + 76.75 } ?: (x + 85.25)), y = y + 29.4,
width = currentPokemon.form.secondaryType?.let { 35.25 } ?: 19, height = 19.25
)
// Render the type widget
typeWidget.render(context, mouseX, mouseY, delta)
// Render the rest
super.render(context, mouseX, mouseY, delta)
}
fun changeCategory(category: RenderableStarterCategory) {
fun getPlatformResource(type: ElementalType): ResourceLocation? {
return try {
cobblemonResource("textures/gui/starterselection/starter_platform_base_${type.showdownId}.png")
} catch (error: FileNotFoundException) {
null
}
}
fun selectPokemon(category: RenderableStarterCategory, slot: Int) {
currentCategory = category
currentSelection = 0
updateSelection()
}
private fun right() {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
currentSelection = rightOfCurrentSelection()
updateSelection()
}
private fun rightOfCurrentSelection() : Int = if (currentSelection + 1 <= currentCategory.pokemon.size - 1) currentSelection + 1 else 0
private fun left() {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
currentSelection = leftOfCurrentSelection()
updateSelection()
}
private fun leftOfCurrentSelection() : Int = if (currentSelection - 1 == -1) currentCategory.pokemon.size - 1 else currentSelection - 1
private fun updateSelection() {
currentSelection = slot
currentPokemon = currentCategory.pokemon[currentSelection].also {
modelWidget.pokemon = it
typeWidget = typeWidget(it, (width - BASE_WIDTH) / 2, (height - BASE_HEIGHT) / 2)
}
starterRoundaboutLeft.pokemon = currentCategory.pokemon[leftOfCurrentSelection()]
starterRoundaboutCenter.pokemon = currentPokemon
starterRoundaboutRight.pokemon = currentCategory.pokemon[rightOfCurrentSelection()]
}
private fun typeWidget(pokemon: RenderablePokemon, x: Int, y: Int) : TypeWidget {
return pokemon.form.secondaryType?.let {
DualTypeWidget(
pX = x + 77, pY = y + 30,
pWidth = 18, pHeight = 18,
pMessage = Component.literal("What?"),
mainType = pokemon.form.primaryType, secondaryType = it
)
} ?: SingleTypeWidget(
pX = x + 85, pY = y + 30,
pWidth = 18, pHeight = 18,
type = pokemon.form.primaryType,
renderText = false
)
override fun tick() {
ticksElapsed++
val delay = 3
if (ticksElapsed % delay == 0) currentBallBackgroundFrame++
if (currentBallBackgroundFrame == 16) currentBallBackgroundFrame = 0
}
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
@@ -340,4 +342,8 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
}
override fun isPauseScreen() = true
fun playSound(soundEvent: SoundEvent) {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(soundEvent, 1.0F))
}
}
@@ -8,126 +8,212 @@
package com.cobblemon.mod.common.client.gui.startselection.widgets
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.PokemonGUIAnimationStyle
import com.cobblemon.mod.common.client.gui.VariableObjectSelectionList
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.startselection.StarterSelectionScreen
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.config.starter.RenderableStarterCategory
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.ObjectSelectionList
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
import org.joml.Quaternionf
import org.joml.Vector3f
class CategoryList(
private val paneWidth: Int,
private val paneHeight: Int,
topOffset: Int,
private val entryWidth: Int,
entryHeight: Int,
private val categories: List<RenderableStarterCategory>,
val listX: Int,
val listY: Int,
private val minecraft: Minecraft = Minecraft.getInstance(),
private val starterSelectionScreen: StarterSelectionScreen
) : ObjectSelectionList<CategoryList.Category>(
minecraft,
) : VariableObjectSelectionList(
listX,
listY,
paneWidth,
paneHeight,
topOffset,
entryHeight
paneHeight
), CobblemonRenderable {
companion object {
private const val CATEGORY_BUTTON_WIDTH = 51.5f
private const val CATEGORY_BUTTON_HEIGHT = 16f
private const val ENTRY_X_OFFSET = 10f
private val categoryResource = cobblemonResource("textures/gui/starterselection/starterselection_slot.png")
private val headerResource = cobblemonResource("textures/gui/starterselection/selection_container_header.png")
private val footerResource = cobblemonResource("textures/gui/starterselection/selection_container_footer.png")
private val edgeResource = cobblemonResource("textures/gui/starterselection/selection_container_edge.png")
private val containerResource = cobblemonResource("textures/gui/starterselection/selection_container.png")
private val buttonResource = cobblemonResource("textures/gui/starterselection/starter_selection.png")
private val buttonActiveResource = cobblemonResource("textures/gui/starterselection/starter_selection_active.png")
const val MARGIN = 4
const val HEADER_HEIGHT = 7
const val BUTTON_SEPERATION = 3
const val CONTAINER_BUTTON_SIZE = 25
const val CONTAINER_WIDTH = 89
}
init {
this.x = listX
this.y = listY
this.correctSize()
createEntries().forEach { addEntry(it) }
//this.setRenderBackground(false)
addEntry(EmptySeparator(MARGIN))
createEntries().forEach {
addEntry(it)
addEntry(EmptySeparator(MARGIN))
}
}
private fun createEntries() = categories.map {
Category(it)
private fun createEntries() = categories.map { category ->
val rows = (category.pokemon.size - 1) / 3 + 1
val height = CONTAINER_BUTTON_SIZE * rows + BUTTON_SEPERATION * (rows + 1) + HEADER_HEIGHT + 3
Category(category, height)
}
override fun renderListBackground(context: GuiGraphics) {}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
super.renderWidget(context, mouseX, mouseY, delta)
correctSize()
}
private fun correctSize() {
this.setRectangle(this.paneWidth, this.paneHeight, this.listX, this.listY)
}
private fun scale(n: Int): Int = (this.minecraft.window.guiScale * n).toInt()
override fun getRowWidth() = this.entryWidth
override fun getScrollbarPosition(): Int {
return this.listX + this.width - 5
}
inner class Category(private val category: RenderableStarterCategory) : Entry<Category>() {
inner class Category(private val category: RenderableStarterCategory, height: Int) : Entry(height) {
override fun render(
context: GuiGraphics,
guiGraphics: GuiGraphics,
index: Int,
y: Int,
x: Int,
entryWidth: Int,
entryHeight: Int,
y: Int,
mouseX: Int,
mouseY: Int,
hovered: Boolean,
tickDelta: Float
partialTick: Float
) {
val matrices = context.pose()
val isHovered = mouseX >= x && mouseY >= y && mouseX < x + entryWidth && mouseY < y + (entryHeight - 1)
if (isHovered) {
blitk(
matrixStack = matrices,
x = x + 2.5f, y = y,
texture = categoryResource,
width = CATEGORY_BUTTON_WIDTH, height = CATEGORY_BUTTON_HEIGHT,
red = 0.75f, green = 0.75f, blue = 0.75f
)
} else
blitk(
matrixStack = matrices,
x = x + 2.5f, y = y,
texture = categoryResource,
width = CATEGORY_BUTTON_WIDTH, height = CATEGORY_BUTTON_HEIGHT
)
drawScaledText(
context = context,
text = category.displayNameText,
x = x + 28,
y = y + 4.5F,
scale = 1F,
maxCharacterWidth = 50,
shadow = true,
centered = true
val rows = (category.pokemon.size - 1) / 3 + 1
val matrices = guiGraphics.pose()
//Header
blitk(
matrixStack = matrices,
texture = headerResource,
x = x + MARGIN,
y = y,
width = CONTAINER_WIDTH,
height = HEADER_HEIGHT
)
val smallTextScale = 0.5F
drawScaledText(
context = guiGraphics,
text = category.displayNameText.bold(),
x = x + MARGIN + CONTAINER_WIDTH / 2,
y = y + 2,
shadow = true,
centered = true,
scale = smallTextScale
)
//Container
var stackHeight = HEADER_HEIGHT
blitk(
matrixStack = matrices,
texture = edgeResource,
x = x + MARGIN,
y = y + stackHeight,
width = CONTAINER_WIDTH,
height = 1
)
stackHeight += 1
val containerInnerHeight = CONTAINER_BUTTON_SIZE * rows + BUTTON_SEPERATION * (rows + 1)
blitk(
matrixStack = matrices,
texture = containerResource,
x = x + MARGIN,
y = y + stackHeight,
width = CONTAINER_WIDTH,
height = containerInnerHeight
)
stackHeight += containerInnerHeight
blitk(
matrixStack = matrices,
texture = edgeResource,
x = x + MARGIN,
y = y + stackHeight,
width = CONTAINER_WIDTH,
height = 1
)
stackHeight += 1
//Footer
blitk(
matrixStack = matrices,
texture = footerResource,
x = x + MARGIN,
y = y + stackHeight,
width = CONTAINER_WIDTH,
height = 1
)
//Buttons
for (index in 0..<category.pokemon.size) {
val row = index / 3
val column = index % 3
val startX = x + MARGIN + 5 + column * (CONTAINER_BUTTON_SIZE + 2)
val startY = y + HEADER_HEIGHT + 1 + BUTTON_SEPERATION + row * (BUTTON_SEPERATION + CONTAINER_BUTTON_SIZE)
val active = starterSelectionScreen.currentCategory == category && starterSelectionScreen.currentSelection == index
val hovered = getEntryAtPosition(mouseX, mouseY) == this && mouseX in startX..(startX + CONTAINER_BUTTON_SIZE) && mouseY in startY..(startY + CONTAINER_BUTTON_SIZE)
blitk(
matrixStack = matrices,
texture = if (active) buttonActiveResource else buttonResource,
x = startX,
y = startY,
width = CONTAINER_BUTTON_SIZE,
height = CONTAINER_BUTTON_SIZE,
textureHeight = CONTAINER_BUTTON_SIZE * 2,
vOffset = if (hovered) CONTAINER_BUTTON_SIZE else 0
)
matrices.pushPose()
matrices.translate(startX + (CONTAINER_BUTTON_SIZE / 2.0), startY.toDouble() - 6, 0.0)
matrices.scale(3.5F, 3.5F, 1F)
val state = FloatingState()
drawProfilePokemon(
species = category.pokemon[index].species.resourceIdentifier,
matrixStack = matrices,
rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(13F, 35F, 0F)),
state = state,
scale = 4.5F,
partialTicks = 0F
)
matrices.popPose()
}
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
starterSelectionScreen.changeCategory(category = category)
minecraft.soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
return true
val entryY = getRowTop(this)
val hoveredButton = getButtonHovered(x, entryY, mouseX, mouseY)
if (hoveredButton != -1) {
starterSelectionScreen.selectPokemon(category, hoveredButton)
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
return true
}
return false
}
override fun getNarration(): Component {
return Component.literal("Yes")
private fun getButtonHovered(x: Int, y: Int, mouseX: Double, mouseY: Double): Int {
for (index in 0..<category.pokemon.size) {
val row = index / 3
val column = index % 3
val startX = x + MARGIN + 5 + column * (CONTAINER_BUTTON_SIZE + 2)
val startY = y + HEADER_HEIGHT + 1 + BUTTON_SEPERATION + row * (BUTTON_SEPERATION + CONTAINER_BUTTON_SIZE)
if (getEntryAtPosition(mouseX.toInt(), mouseY.toInt()) == this && mouseX.toInt() in startX..(startX + CONTAINER_BUTTON_SIZE) && mouseY.toInt() in startY..(startY + CONTAINER_BUTTON_SIZE)) {
return index
}
}
return -1
}
}
}
@@ -6,11 +6,13 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.startselection.widgets.preview
package com.cobblemon.mod.common.client.gui.startselection.widgets
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.ColourLibrary
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.CobblemonRenderable
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
@@ -26,45 +28,39 @@ class SelectionButton(
pX: Int, pY: Int,
pWidth: Int, pHeight: Int,
onPress: OnPress
): Button(pX, pY, pWidth, pHeight, Component.literal("SelectionButton"), onPress, DEFAULT_NARRATION), CobblemonRenderable {
): Button(pX, pY, pWidth, pHeight, Component.literal("SelectionButton"), onPress, DEFAULT_NARRATION),
CobblemonRenderable {
companion object {
private val buttonTexture = cobblemonResource("textures/gui/starterselection/starterselection_button.png")
const val BUTTON_WIDTH = 56
const val BUTTON_HEIGHT = 12
private const val SCALE = 0.7f
private val buttonResource = cobblemonResource("textures/gui/starterselection/choose_button.png")
const val BUTTON_WIDTH = 106
const val BUTTON_HEIGHT = 14
const val TEXT_HEIGHT = 10
}
override fun playDownSound(soundManager: SoundManager) {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
override fun playDownSound(soundManager: SoundManager) {}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val matrices = context.pose()
if (isHovered)
blitk(
matrixStack = matrices,
texture = buttonTexture,
x = x + 0.6, y = y + 0.6,
width = BUTTON_WIDTH - 0.25, height = BUTTON_HEIGHT - 0.25,
red = 0.75f, green = 0.75f, blue = 0.75f
)
else
blitk(
matrixStack = matrices,
texture = buttonTexture,
x = x + 0.6, y = y + 0.6,
width = BUTTON_WIDTH - 0.25, height = BUTTON_HEIGHT - 0.25
)
blitk(
matrixStack = matrices,
texture = buttonResource,
x = x,
y = y,
width = BUTTON_WIDTH,
height = BUTTON_HEIGHT,
textureHeight = BUTTON_HEIGHT * 2,
vOffset = if (isHovered) BUTTON_HEIGHT else 0
)
drawScaledText(
context = context,
text = lang("ui.starter.choosebutton"),
x = x + BUTTON_WIDTH / 2, y = y + BUTTON_HEIGHT / 2 - 2.4,
colour = ColourLibrary.WHITE,
centered = true,
maxCharacterWidth = 68,
scale = SCALE,
shadow = true
font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.starter.choosebutton").bold(),
x = x + BUTTON_WIDTH / 2,
y = y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2,
shadow = true,
centered = true
)
}
}
@@ -31,6 +31,7 @@ class ModelWidget(
var offsetY: Double = 0.0,
val playCryOnClick: Boolean = false,
val shouldFollowCursor: Boolean = false,
val blockLight: Int = 13
): SoundlessWidget(pX, pY, pWidth, pHeight, Component.literal("Summary - ModelWidget")) {
companion object {
@@ -115,6 +116,7 @@ class ModelWidget(
partialTicks = partialTicks,
headYaw = currentYawAndPitch.first,
headPitch = currentYawAndPitch.second,
blockLight = blockLight
)
heldItemRenderer.renderOnModel(
@@ -126,6 +126,14 @@ class StarterConfig {
PokemonProperties.parse("Fuecoco level=10"),
PokemonProperties.parse("Quaxly level=10"),
)
),
StarterCategory(
name = "Special",
displayName = "cobblemon.starterselection.category.special",
pokemon = mutableListOf(
PokemonProperties.parse("Pikachu level=10"),
PokemonProperties.parse("Eevee level=10")
)
)
)
}
@@ -3403,6 +3403,7 @@
"cobblemon.starterselection.category.galar": "Galar",
"cobblemon.starterselection.category.hisui_bias": "Hisui",
"cobblemon.starterselection.category.paldea": "Paldea",
"cobblemon.starterselection.category.special": "Special",
"cobblemon.stat.hp.name": "HP",
"cobblemon.stat.attack.name": "Attack",
Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 946 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 853 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 731 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 811 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 885 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 768 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 B