Merge branch 'feat/animated-party-portraits' into 'main'

Add support for animated portraits in the party overlay

See merge request cable-mc/cobblemon!1302
This commit is contained in:
Polymeta
2025-02-23 09:26:59 +00:00
9 changed files with 245 additions and 11 deletions
@@ -8,22 +8,25 @@
package com.cobblemon.mod.common.client.gui
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.gui.drawPosablePortrait
import com.cobblemon.mod.common.api.text.darkGray
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.gui.portrait.FullyAnimatedPortraitDrawer
import com.cobblemon.mod.common.client.gui.portrait.SelectedAnimatedPortraitDrawer
import com.cobblemon.mod.common.client.gui.portrait.InanimatePortraitDrawer
import com.cobblemon.mod.common.client.gui.portrait.PortraitStyle
import com.cobblemon.mod.common.client.gui.toast.CobblemonToast
import com.cobblemon.mod.common.client.keybind.boundKey
import com.cobblemon.mod.common.client.keybind.keybinds.HidePartyBinding
import com.cobblemon.mod.common.client.keybind.keybinds.SummaryBinding
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.getDepletableRedGreen
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.client.render.renderScaledGuiItemIcon
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.util.cobblemonResource
@@ -55,7 +58,10 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
private val genderIconMale = cobblemonResource("textures/gui/party/party_gender_male.png")
private val genderIconFemale = cobblemonResource("textures/gui/party/party_gender_female.png")
private val portraitBackground = cobblemonResource("textures/gui/party/party_slot_portrait_background.png")
val state = FloatingState()
private val inanimatePortraitDrawer = InanimatePortraitDrawer()
private val selectedAnimatedPortraitDrawer = SelectedAnimatedPortraitDrawer()
private val fullyAnimatedPortraitDrawer = FullyAnimatedPortraitDrawer()
}
private val screenExemptions: List<Class<out Screen>> = listOf(
@@ -84,6 +90,7 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
override fun render(context: GuiGraphics, tickCounter: DeltaTracker) {
val partialDeltaTicks = tickCounter.realtimeDeltaTicks
val minecraft = Minecraft.getInstance()
// Hiding if a Screen is open and not exempt
@@ -153,15 +160,14 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
0.0
)
state.currentAspects = pokemon.aspects
val portraitDrawer = when (Cobblemon.config.partyPortraitAnimations) {
PortraitStyle.NEVER_ANIMATE -> inanimatePortraitDrawer
PortraitStyle.ANIMATE_SELECTED -> selectedAnimatedPortraitDrawer
PortraitStyle.ALWAYS_ANIMATE -> fullyAnimatedPortraitDrawer
}
portraitDrawer.draw(pokemon, matrices, partialDeltaTicks, selectedSlot == index, index)
drawPosablePortrait(
identifier = pokemon.species.resourceIdentifier,
matrixStack = matrices,
partialTicks = 0F, // partialDeltaTicks, //Before you get any funny ideas about party animated pokemon, make sure they each get their own state instead of sharing.
contextScale = pokemon.form.baseScale,
state = state
)
matrices.popPose()
context.disableScissor()
}
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.portrait
import com.cobblemon.mod.common.api.gui.drawPosablePortrait
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.pokemon.Pokemon
import com.mojang.blaze3d.vertex.PoseStack
import java.util.UUID
/**
* Base logic for a [PortraitDrawer] that animates a portrait based on conditions.
*/
abstract class AnimatedPortraitDrawer : PortraitDrawer {
private val state = FloatingState()
private val stateAtIndex = hashMapOf<Int, Pair<UUID, FloatingState>>()
override fun draw(pokemon: Pokemon, poseStack: PoseStack, partialTicks: Float, isSelected: Boolean, index: Int) {
drawPosablePortrait(
identifier = pokemon.species.resourceIdentifier,
matrixStack = poseStack,
partialTicks = this.ticksFor(pokemon, isSelected, partialTicks),
contextScale = pokemon.form.baseScale,
state = this.stateFor(pokemon, isSelected, index)
)
}
protected abstract fun shouldAnimate(pokemon: Pokemon, isSelected: Boolean): Boolean
protected open fun ticksFor(pokemon: Pokemon, isSelected: Boolean, partialTicks: Float): Float {
return if (this.shouldAnimate(pokemon, isSelected)) partialTicks else 0F
}
protected open fun stateFor(pokemon: Pokemon, isSelected: Boolean, index: Int): FloatingState {
if (!this.shouldAnimate(pokemon, isSelected)) {
return this.state
}
val state = this.stateAtIndex.getOrPut(index) { pokemon.uuid to this.createState(pokemon) }
if (state.first != pokemon.uuid) {
val newState = this.createState(pokemon)
this.stateAtIndex[index] = pokemon.uuid to newState
return newState
}
return state.second
}
protected open fun createState(pokemon: Pokemon): FloatingState {
val state = FloatingState()
state.currentAspects = pokemon.aspects
return state
}
}
@@ -0,0 +1,25 @@
/*
* 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.portrait
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.client.GraphicsStatus
/**
* Draws portraits if the Pokemon is not fainted.
*
* Meant for client with [GraphicsStatus.FABULOUS].
*/
class FullyAnimatedPortraitDrawer : AnimatedPortraitDrawer() {
override fun shouldAnimate(pokemon: Pokemon, isSelected: Boolean): Boolean {
return !pokemon.isFainted()
}
}
@@ -0,0 +1,37 @@
/*
* 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.portrait
import com.cobblemon.mod.common.api.gui.drawPosablePortrait
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.pokemon.Pokemon
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.client.GraphicsStatus
/**
* The simplest implementation, the Pokemon is drawn staying still.
*
* Meant for client with [GraphicsStatus.FAST].
*/
class InanimatePortraitDrawer : PortraitDrawer {
private val state = FloatingState()
override fun draw(pokemon: Pokemon, poseStack: PoseStack, partialTicks: Float, isSelected: Boolean, index: Int) {
state.currentAspects = pokemon.aspects
drawPosablePortrait(
identifier = pokemon.species.resourceIdentifier,
matrixStack = poseStack,
partialTicks = 0F,
contextScale = pokemon.form.baseScale,
state = this.state
)
}
}
@@ -0,0 +1,38 @@
/*
* 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.portrait
import com.cobblemon.mod.common.pokemon.Pokemon
import com.mojang.blaze3d.vertex.PoseStack
/**
* Responsible for drawing a Pokemon portrait.
*
* Keep in mind any scaling and handling of the [PoseStack] must be done on the implementations using this system.
*/
fun interface PortraitDrawer {
/**
* Draws the portrait.
*
* @param pokemon The [Pokemon] being drawn.
* @param poseStack The [PoseStack] being used.
* @param partialTicks The partial ticks of the screen using this.
* @param isSelected If the [pokemon] is considered in a selected state for example hovered or the current party member.
* @param index The index of the [pokemon] in the screen using this.
*/
fun draw(
pokemon: Pokemon,
poseStack: PoseStack,
partialTicks: Float,
isSelected: Boolean,
index: Int
)
}
@@ -0,0 +1,29 @@
/*
* 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.portrait
import com.mojang.serialization.Codec
import net.minecraft.util.StringRepresentable
enum class PortraitStyle : StringRepresentable {
NEVER_ANIMATE,
ANIMATE_SELECTED,
ALWAYS_ANIMATE;
override fun getSerializedName(): String = this.name
companion object {
@JvmStatic
val CODEC: Codec<PortraitStyle> = StringRepresentable.fromEnum(PortraitStyle::values)
}
}
@@ -0,0 +1,25 @@
/*
* 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.portrait
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.client.GraphicsStatus
/**
* Draws portraits if the Pokemon is not fainted and selected.
*
* Meant for client with [GraphicsStatus.FANCY].
*/
class SelectedAnimatedPortraitDrawer : AnimatedPortraitDrawer() {
override fun shouldAnimate(pokemon: Pokemon, isSelected: Boolean): Boolean {
return !pokemon.isFainted() && isSelected
}
}
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.config
import com.cobblemon.mod.common.api.drop.ItemDropMethod
import com.cobblemon.mod.common.api.pokeball.catching.calculators.CaptureCalculator
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.client.gui.portrait.PortraitStyle
import com.cobblemon.mod.common.config.constraint.IntConstraint
import com.cobblemon.mod.common.pokeball.catching.calculators.CobblemonCaptureCalculator
import com.cobblemon.mod.common.util.adapters.CaptureCalculatorAdapter
@@ -255,6 +256,9 @@ class CobblemonConfig {
@CobblemonConfigField(Category.Interface, lang = "summary_pokemon_follow_cursor")
var summaryPokemonFollowCursor = true
@CobblemonConfigField(Category.Interface, lang = "party_portrait_animations")
var partyPortraitAnimations = PortraitStyle.NEVER_ANIMATE
@CobblemonConfigField(Category.Debug, lang = "enable_debug_keys")
var enableDebugKeys = false
@@ -1715,6 +1715,13 @@
"cobblemon.config.ui.hide_unimplemented_pokemon_in_the_pokedex.tooltip": "Whether to hide unimplemented Pokémon from the Pokédex.",
"cobblemon.config.ui.summary_pokemon_follow_cursor": "Make Pokémon Follow Cursor",
"cobblemon.config.ui.summary_pokemon_follow_cursor.tooltip": "Whether or not Pokémon should follow the mouse cursor with their heads in the Summary screen.",
"cobblemon.config.ui.party_portrait_animations": "Party Portrait Animations",
"cobblemon.config.ui.party_portrait_animations.never_animate": "Never Animate",
"cobblemon.config.ui.party_portrait_animations.never_animate.tooltip": "No portraits animate",
"cobblemon.config.ui.party_portrait_animations.animate_selected": "Animate Selected",
"cobblemon.config.ui.party_portrait_animations.animate_selected.tooltip": "Selected Pokémon animates unless fainted",
"cobblemon.config.ui.party_portrait_animations.always_animate": "Always animate",
"cobblemon.config.ui.party_portrait_animations.always_animate.tooltip": "Entire party animates unless fainted",
"cobblemon.config.ui.enable_debug_keys": "Enable Debug Keys",
"cobblemon.config.ui.enable_debug_keys.tooltip": "Enables additional commands and chat logging to assist with implementing Pokémon (see the wiki for a list of commands).",