Major refactor of animation system

This commit is contained in:
Hiroku
2024-06-06 11:22:06 +00:00
committed by Apion
parent 5e74af8a9e
commit 58e82f1bf6
865 changed files with 11796 additions and 12749 deletions
+23
View File
@@ -0,0 +1,23 @@
### Additions
- Pokémon are now animated when seen in any GUI that isn't the party GUI.
- Quirk animations can now occur for Pokémon that are shoulder mounted.
### Changes
- Improved the performance of display cases that contain Pokémon photos.
### Fixes
- Fixed some Pokémon photos in display cases flashing if the Pokémon has gender differences.
- Fixed Bidoof and Jigglypuff sleep animations stopping after some time, causing them to be 'asleep' while T-posing menacingly.
- Fixed entities not changing poses unless you're looking at them.
### Developer
- Rebuilt large swaths of the model animation code to simplify it.
- Renamed a bunch of things from %Poseable% to %Posable% because spelling.
- Renamed StatelessAnimation to PoseAnimation.
- Renamed StatefulAnimation to ActiveAnimation.
- Documented the animation system.
### Data Pack & Resource Pack Creators
- Added MoLang compatibility in the isVisible property for transformed parts.
- Added q.has_aspect('some_aspect') function to animations, posers, and entity particle effects.
- Added support for conditional pose animations.
@@ -8,13 +8,11 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityModel;
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Bone;
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext;
import net.minecraft.client.model.ModelPart;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.Entity;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@@ -13,7 +13,7 @@ import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.client.net.PlayerInteractOptionsHandler
import com.cobblemon.mod.common.client.net.SetClientPlayerDataHandler
import com.cobblemon.mod.common.client.net.animation.PlayPoseableAnimationHandler
import com.cobblemon.mod.common.client.net.animation.PlayPosableAnimationHandler
import com.cobblemon.mod.common.client.net.battle.*
import com.cobblemon.mod.common.client.net.callback.move.OpenMoveCallbackHandler
import com.cobblemon.mod.common.client.net.callback.party.OpenPartyCallbackHandler
@@ -59,21 +59,11 @@ import com.cobblemon.mod.common.client.net.trade.TradeOfferNotificationHandler
import com.cobblemon.mod.common.client.net.trade.TradeStartedHandler
import com.cobblemon.mod.common.client.net.trade.TradeUpdatedHandler
import com.cobblemon.mod.common.net.messages.client.PlayerInteractOptionsPacket
import com.cobblemon.mod.common.net.messages.client.animation.PlayPoseableAnimationPacket
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
import com.cobblemon.mod.common.net.messages.client.battle.*
import com.cobblemon.mod.common.net.messages.client.callback.OpenMoveCallbackPacket
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyCallbackPacket
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyMoveCallbackPacket
import com.cobblemon.mod.common.net.messages.client.data.AbilityRegistrySyncPacket
import com.cobblemon.mod.common.net.messages.client.data.BerryRegistrySyncPacket
import com.cobblemon.mod.common.net.messages.client.data.GlobalSpeciesFeatureSyncPacket
import com.cobblemon.mod.common.net.messages.client.data.MovesRegistrySyncPacket
import com.cobblemon.mod.common.net.messages.client.data.NPCRegistrySyncPacket
import com.cobblemon.mod.common.net.messages.client.data.PropertiesCompletionRegistrySyncPacket
import com.cobblemon.mod.common.net.messages.client.data.SpeciesFeatureAssignmentSyncPacket
import com.cobblemon.mod.common.net.messages.client.data.SpeciesRegistrySyncPacket
import com.cobblemon.mod.common.net.messages.client.data.StandardSpeciesFeatureSyncPacket
import com.cobblemon.mod.common.net.messages.client.data.UnlockReloadPacket
import com.cobblemon.mod.common.net.messages.client.data.*
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueClosedPacket
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueOpenedPacket
@@ -352,7 +342,7 @@ object CobblemonNetwork : NetworkManager {
this.createClientBound(PokemonUnpasturedPacket.ID, PokemonUnpasturedPacket::decode, PokemonUnpasturedHandler)
// Behaviours
this.createClientBound(PlayPoseableAnimationPacket.ID, PlayPoseableAnimationPacket::decode, PlayPoseableAnimationHandler)
this.createClientBound(PlayPosableAnimationPacket.ID, PlayPosableAnimationPacket::decode, PlayPosableAnimationHandler)
// Move select packets
this.createClientBound(OpenMoveCallbackPacket.ID, OpenMoveCallbackPacket::decode, OpenMoveCallbackHandler)
@@ -22,7 +22,6 @@ import com.cobblemon.mod.common.api.battles.model.actor.FleeableBattleActor
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.BattleFledEvent
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.api.text.red
@@ -32,6 +31,7 @@ import com.cobblemon.mod.common.battles.BattleCaptureAction
import com.cobblemon.mod.common.battles.BattleFormat
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.battles.BattleSide
import com.cobblemon.mod.common.battles.ForfeitActionResponse
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.battles.dispatch.BattleDispatch
import com.cobblemon.mod.common.battles.dispatch.DispatchResult
@@ -40,7 +40,6 @@ import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
import com.cobblemon.mod.common.battles.interpreter.ContextManager
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.battles.ForfeitActionResponse
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.battle.BattleEndPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleMessagePacket
@@ -49,13 +48,12 @@ import com.cobblemon.mod.common.pokemon.evolution.progress.LastBattleCriticalHit
import com.cobblemon.mod.common.pokemon.evolution.requirements.DefeatRequirement
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.withQueryValue
import java.io.File
import java.util.UUID
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ConcurrentLinkedDeque
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.text.Text
import java.util.concurrent.ConcurrentLinkedDeque
/**
* Individual battle instance
@@ -72,7 +70,7 @@ open class PokemonBattle(
/** Whether logging will be silenced for this battle. */
var mute = true
val struct = this.asMoLangValue()
val runtime = MoLangRuntime().also { it.environment.structs["query"] = struct }
val runtime = MoLangRuntime().also { it.environment.query = struct }
val onEndHandlers: MutableList<(PokemonBattle) -> Unit> = mutableListOf()
@@ -514,7 +512,6 @@ open class PokemonBattle(
queryStruct.addFunction("pvn") { DoubleValue(isPvN) }
queryStruct.addFunction("pvw") { DoubleValue(isPvW) }
queryStruct.addFunction("has_rule") { params -> DoubleValue(params.getString(0) in format.ruleSet) }
return queryStruct
}
}
@@ -20,7 +20,6 @@ import com.cobblemon.mod.common.api.dialogue.input.ActiveInput
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.scheduling.ServerRealTimeTaskTracker
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueOpenedPacket
@@ -42,7 +41,7 @@ class ActiveDialogue(var playerEntity: ServerPlayerEntity, var dialogueReference
var activeInput = ActiveInput(this, currentPage.input)
init {
runtime.environment.getQueryStruct().addFunctions(
runtime.environment.query.addFunctions(
mapOf(
"dialogue" to java.util.function.Function { _ -> toMoLangStruct() },
"player" to java.util.function.Function { _ -> playerStruct }
@@ -10,8 +10,7 @@ package com.cobblemon.mod.common.api.gui
import com.cobblemon.mod.common.api.text.font
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay.Companion.PORTRAIT_DIAMETER
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
import com.cobblemon.mod.common.entity.PoseType
@@ -24,18 +23,17 @@ import net.minecraft.client.render.DiffuseLighting
import net.minecraft.client.render.GameRenderer
import net.minecraft.client.render.LightmapTextureManager
import net.minecraft.client.render.OverlayTexture
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.Tessellator
import net.minecraft.client.render.VertexFormat
import net.minecraft.client.render.VertexFormats
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.entity.Entity
import net.minecraft.text.MutableText
import net.minecraft.text.OrderedText
import net.minecraft.text.Text
import net.minecraft.util.Identifier
import net.minecraft.util.math.RotationAxis
import org.joml.Matrix4f
import org.joml.Quaternionf
import org.joml.Vector3f
@JvmOverloads
@@ -188,15 +186,15 @@ fun drawString(
}
@JvmOverloads
fun <T : Entity, M : PoseableEntityModel<T>> drawPoseablePortrait(
fun drawPosablePortrait(
identifier: Identifier,
aspects: Set<String>,
matrixStack: MatrixStack,
scale: Float = 13F,
contextScale: Float = 1F,
reversed: Boolean = false,
state: PoseableEntityState<T>? = null,
repository: VaryingModelRepository<T, M>,
state: PosableState,
repository: VaryingModelRepository<*>,
partialTicks: Float,
limbSwing: Float = 0F,
limbSwingAmount: Float = 0F,
@@ -205,30 +203,29 @@ fun <T : Entity, M : PoseableEntityModel<T>> drawPoseablePortrait(
headPitch: Float = 0F
) {
val model = repository.getPoser(identifier, aspects)
val texture = repository.getTexture(identifier, aspects, state?.animationSeconds ?: 0F)
state.currentAspects = aspects
state.currentModel = model
val texture = repository.getTexture(identifier, aspects, state.animationSeconds)
val context = RenderContext()
model.context = context
repository.getTextureNoSubstitute(identifier, aspects, 0f).let { context.put(RenderContext.TEXTURE, it) }
context.put(RenderContext.SCALE, contextScale)
context.put(RenderContext.SPECIES, identifier)
context.put(RenderContext.ASPECTS, aspects)
context.put(RenderContext.POSABLE_STATE, state)
val renderType = model.getLayer(texture)
val renderType = RenderLayer.getEntityCutout(texture)
RenderSystem.applyModelViewMatrix()
val quaternion1 = RotationAxis.POSITIVE_Y.rotationDegrees(-32F * if (reversed) -1F else 1F)
val quaternion2 = RotationAxis.POSITIVE_X.rotationDegrees(5F)
if (state == null) {
model.setupAnimStateless(setOf(PoseType.PORTRAIT, PoseType.PROFILE))
} else {
val originalPose = state.currentPose
model.getPose(PoseType.PORTRAIT)?.let { state.setPose(it.poseName) }
state.timeEnteredPose = 0F
state.updatePartialTicks(partialTicks)
model.setupAnimStateful(null, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch)
originalPose?.let { state.setPose(it) }
}
val originalPose = state.currentPose
state.setPoseToFirstSuitable(PoseType.PORTRAIT)
state.updatePartialTicks(partialTicks)
model.applyAnimations(null, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch)
originalPose?.let { state.setPose(it) }
matrixStack.push()
matrixStack.translate(0.0, PORTRAIT_DIAMETER.toDouble() + 2.0, 0.0)
@@ -259,12 +256,12 @@ fun <T : Entity, M : PoseableEntityModel<T>> drawPoseablePortrait(
DiffuseLighting.enableGuiDepthLighting()
}
fun <E : Entity, M : PoseableEntityModel<E>> drawProfile(
repository: VaryingModelRepository<E, M>,
fun drawProfile(
repository: VaryingModelRepository<*>,
resourceIdentifier: Identifier,
aspects: Set<String>,
matrixStack: MatrixStack,
state: PoseableEntityState<E>,
state: PosableState,
partialTicks: Float,
scale: Float = 20F
) {
@@ -272,19 +269,23 @@ fun <E : Entity, M : PoseableEntityModel<E>> drawProfile(
val texture = repository.getTexture(resourceIdentifier, aspects, state.animationSeconds)
val context = RenderContext()
model.context = context
repository.getTextureNoSubstitute(resourceIdentifier, aspects, 0f).let { context.put(RenderContext.TEXTURE, it) }
context.put(RenderContext.SCALE, 1F)
context.put(RenderContext.SPECIES, resourceIdentifier)
context.put(RenderContext.ASPECTS, aspects)
context.put(RenderContext.POSABLE_STATE, state)
state.currentAspects = aspects
state.currentModel = model
val renderType = model.getLayer(texture)
val renderType = RenderLayer.getEntityCutout(texture)//model.getLayer(texture)
RenderSystem.applyModelViewMatrix()
matrixStack.scale(scale, scale, -scale)
model.getPose(PoseType.PROFILE)?.let { state.setPose(it.poseName) }
state.timeEnteredPose = 0F
state.setPoseToFirstSuitable(PoseType.PORTRAIT)
state.updatePartialTicks(partialTicks)
model.setupAnimStateful(null, state, 0F, 0F, 0F, 0F, 0F)
model.applyAnimations(null, state, 0F, 0F, 0F, 0F, 0F)
matrixStack.translate(model.profileTranslation.x, model.profileTranslation.y, model.profileTranslation.z - 4.0)
matrixStack.scale(model.profileScale, model.profileScale, 1 / model.profileScale)
// matrixStack.multiply(rotation)
@@ -93,7 +93,9 @@ object MoLangFunctions {
},
"run_script" to java.util.function.Function { params ->
val runtime = MoLangRuntime()
runtime.environment.structs.putAll(params.environment.structs)
runtime.environment.query = params.environment.query
runtime.environment.variable = params.environment.variable
runtime.environment.context = params.environment.context
val script = params.getString(0).asIdentifierDefaultingNamespace()
CobblemonScripts.run(script, runtime) ?: DoubleValue(0)
}
@@ -132,7 +134,7 @@ object MoLangFunctions {
}
map.put("environment") {
val environment = MoLangEnvironment()
environment.structs["query"] = player.asMoLangValue()
environment.query = player.asMoLangValue()
environment
}
map
@@ -195,7 +197,9 @@ object MoLangFunctions {
val script = params.getString(0).asIdentifierDefaultingNamespace()
val environment = params.environment
val runtime = MoLangRuntime()
runtime.environment.structs.putAll(environment.structs)
runtime.environment.query = environment.query
runtime.environment.variable = environment.variable
runtime.environment.context = environment.context
CobblemonScripts.run(script, runtime) ?: DoubleValue(0)
}
map.put("environment") { _ -> npc.runtime.environment }
@@ -275,10 +279,8 @@ object MoLangFunctions {
return this
}
fun MoLangEnvironment.getQueryStruct(name: String = "query") = structs.getOrPut(name) { QueryStruct(hashMapOf()) } as QueryStruct
fun MoLangRuntime.setup(): MoLangRuntime {
environment.getQueryStruct().addStandardFunctions()
environment.query.addStandardFunctions()
return this
}
@@ -14,7 +14,7 @@ import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.EntityProvider
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.scheduling.delayedFuture
import com.cobblemon.mod.common.net.messages.client.animation.PlayPoseableAnimationPacket
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
import com.cobblemon.mod.common.util.asExpressionLike
import java.util.concurrent.CompletableFuture
import net.minecraft.server.world.ServerWorld
@@ -45,7 +45,7 @@ class AnimationActionEffectKeyframe : ConditionalActionEffectKeyframe(), EntityC
for (entity in entities) {
val world = entity.world as ServerWorld
val players = world.getPlayers { it.distanceTo(entity) <= visibilityRange }
val pkt = PlayPoseableAnimationPacket(entity.id, animation = animation, expressions = expressions)
val pkt = PlayPosableAnimationPacket(entity.id, animation = animation, expressions = expressions)
players.forEach { it.sendPacket(pkt) }
}
@@ -12,7 +12,6 @@ import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import net.minecraft.entity.Entity
@@ -26,7 +25,7 @@ interface EntityConditionalActionEffectKeyframe {
val entityCondition: ExpressionLike
fun test(context: ActionEffectContext, entity: Entity, isUser: Boolean): Boolean {
// TODO this should use the entity's own query struct
context.runtime.environment.getQueryStruct().addFunction("entity") {
context.runtime.environment.query.addFunction("entity") {
QueryStruct(hashMapOf())
.addFunction("uuid") { StringValue(entity.uuidAsString) }
.addFunction("is_user") { DoubleValue(isUser) }
@@ -13,7 +13,7 @@ import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.EntityProvider
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.scheduling.delayedFuture
import com.cobblemon.mod.common.entity.Poseable
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
@@ -44,7 +44,7 @@ class EntityParticlesActionEffectKeyframe : ConditionalActionEffectKeyframe(), E
effect
}?.asIdentifierDefaultingNamespace() ?: return skip()
entities.filter { it is Poseable }.forEach { entity ->
entities.filter { it is PosableEntity }.forEach { entity ->
val packet = SpawnSnowstormEntityParticlePacket(effectIdentifier, entity.id, locator)
val players = (entity.world as ServerWorld).getPlayers { it.distanceTo(entity) <= visibilityRange }
packet.sendToPlayers(players)
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.drop.DropEntry
import com.cobblemon.mod.common.api.drop.ItemDropMethod
import com.cobblemon.mod.common.api.entity.EntityDimensionsAdapter
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.adapters.MoveTemplateAdapter
import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffect
@@ -91,6 +92,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
.registerTypeAdapter(SleepDepth::class.java, SleepDepth.adapter)
.registerTypeAdapter(DropEntry::class.java, DropEntryAdapter)
.registerTypeAdapter(NbtCompound::class.java, NbtCompoundAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Item::class.java).type, ItemLikeConditionAdapter)
@@ -13,8 +13,6 @@ import com.cobblemon.mod.common.util.runOnServer
@Deprecated("Use afterOnServer or afterOnClient; ambiguous side is not good for your health")
@JvmOverloads
fun after(ticks: Int = 0, seconds: Float = 0F, serverThread: Boolean = false, action: () -> Unit) {
val scheduler = if (serverThread) ServerTaskTracker else ClientTaskTracker
scheduler.after(seconds = seconds + ticks / 20F, action)
@@ -103,7 +103,7 @@ class SteadyParticleEmitterRate(
// The emitting rates are all per second, but this runs every tick. Presents some difficulties.
val max = runtime.resolveDouble(maximum).toInt()
val variables = runtime.environment.structs["variable"] as VariableStruct
val variables = runtime.environment.variable
/*
* The strategy is that per tick it might be calculated to be 1.2 particles. We can't spawn a fifth
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.api.snowstorm
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.net.Decodable
import com.cobblemon.mod.common.api.net.Encodable
@@ -100,7 +99,7 @@ class ParticleEvent(
sourceAlive = storm.sourceAlive,
sourceVisible = storm.sourceVisible,
onDespawn = {},
runtime = MoLangRuntime().setup().also { it.environment.structs["query"] = storm.runtime.environment.getQueryStruct() },
runtime = MoLangRuntime().setup().also { it.environment.query = storm.runtime.environment.query },
entity = storm.entity
)
@@ -17,11 +17,11 @@ import com.cobblemon.mod.common.entity.npc.NPCEntity
* @author Hiroku
* @since October 8th, 2023
*/
class NPCSpawnAction(ctx: SpawningContext, override val detail: NPCSpawnDetail) : SpawnAction<NPCEntity>(ctx, detail) {
override fun run(): NPCEntity {
class NPCSpawnAction(ctx: SpawningContext, override val detail: NPCSpawnDetail) : SpawnAction<EntitySpawnResult>(ctx, detail) {
override fun run(): EntitySpawnResult {
val npc = NPCEntity(ctx.world)
npc.npc = detail.npcClass
npc.appliedAspects.addAll(detail.aspects)
return npc
return EntitySpawnResult(listOf(npc))
}
}
@@ -13,7 +13,6 @@ import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asDimensionTypeMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asWorldMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
@@ -12,7 +12,6 @@ import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import net.minecraft.util.Identifier
import java.util.concurrent.CompletableFuture
@@ -32,8 +31,8 @@ interface ActionEffectInstruction : InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
preActionEffect(battle)
val runtime = MoLangRuntime()
battle.addQueryFunctions(runtime.environment.getQueryStruct())
runtime.environment.getQueryStruct().addStandardFunctions()
battle.addQueryFunctions(runtime.environment.query)
runtime.environment.query.addStandardFunctions()
addMolangQueries(runtime)
runActionEffect(battle, runtime)
postActionEffect(battle)
@@ -44,8 +43,7 @@ interface ActionEffectInstruction : InterpreterInstruction {
fun postActionEffect(battle: PokemonBattle)
fun addMolangQueries(runtime: MoLangRuntime) {
runtime.environment.getQueryStruct()
.addFunction("instruction_id") { StringValue(id.toString()) }
runtime.environment.query.addFunction("instruction_id") { StringValue(id.toString()) }
}
}
@@ -9,11 +9,8 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.pokemon.status.Statuses
@@ -22,14 +19,11 @@ import com.cobblemon.mod.common.battles.dispatch.ActionEffectInstruction
import com.cobblemon.mod.common.battles.dispatch.CauserInstruction
import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.text.Text
import net.minecraft.util.Identifier
import java.util.concurrent.CompletableFuture
import net.minecraft.text.Text
/**
* Format: |-activate|POKEMON|EFFECT
@@ -11,23 +11,17 @@ package com.cobblemon.mod.common.battles.interpreter.instructions
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.TargetsProvider
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.dispatch.ActionEffectInstruction
import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.util.Identifier
import java.util.concurrent.CompletableFuture
/**
@@ -12,21 +12,17 @@ import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.ShowdownInterpreter.lastCauser
import com.cobblemon.mod.common.battles.dispatch.ActionEffectInstruction
import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.net.messages.client.animation.PlayPoseableAnimationPacket
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleHealthChangePacket
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
import com.cobblemon.mod.common.pokemon.evolution.progress.DamageTakenEvolutionProgress
@@ -37,7 +33,6 @@ import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import java.util.concurrent.CompletableFuture
import net.minecraft.text.Text
import net.minecraft.util.Identifier
/**
* Format: |-damage|POKEMON|HP STATUS
@@ -133,7 +128,7 @@ class DamageInstruction(
val pokemonEntity = battlePokemon.entity
//Play recoil animation if the pokemon recoiling isnt dead
if (!causedFaint && pokemonEntity != null) {
val pkt = PlayPoseableAnimationPacket(pokemonEntity.id, setOf("recoil"), emptySet())
val pkt = PlayPosableAnimationPacket(pokemonEntity.id, setOf("recoil"), emptySet())
pkt.sendToPlayersAround(
x = pokemonEntity.x,
y = pokemonEntity.y,
@@ -15,7 +15,6 @@ import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.interpreter.Effect
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.TargetsProvider
@@ -89,7 +88,7 @@ class MoveInstruction(
userPokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
targetPokemon?.effectedPokemon?.entity?.let { TargetsProvider(it) }?.let(providers::add)
val runtime = MoLangRuntime().also {
battle.addQueryFunctions(it.environment.getQueryStruct()).addStandardFunctions()
battle.addQueryFunctions(it.environment.query).addStandardFunctions()
}
actionEffect ?: return@dispatch GO
@@ -102,7 +101,7 @@ class MoveInstruction(
val subsequentInstructions = instructionSet.findInstructionsCausedBy(this)
val missedTargets = subsequentInstructions.filterIsInstance<MissInstruction>().mapNotNull { it.target }
runtime.environment.getQueryStruct().addFunction("missed") { params ->
runtime.environment.query.addFunction("missed") { params ->
if (params.params.size == 0) {
return@addFunction DoubleValue(missedTargets.isNotEmpty())
} else {
@@ -112,7 +111,7 @@ class MoveInstruction(
}
val hurtTargets = subsequentInstructions.filterIsInstance<DamageInstruction>().mapNotNull { it.expectedTarget }
runtime.environment.getQueryStruct().addFunction("hurt") { params ->
runtime.environment.query.addFunction("hurt") { params ->
if (params.params.size == 0) {
return@addFunction DoubleValue(hurtTargets.isNotEmpty())
} else {
@@ -121,8 +120,8 @@ class MoveInstruction(
}
}
runtime.environment.getQueryStruct().addFunction("move") { move.struct }
runtime.environment.getQueryStruct().addFunction("instruction_id") { StringValue(cobblemonResource("move").toString()) }
runtime.environment.query.addFunction("move") { move.struct }
runtime.environment.query.addFunction("instruction_id") { StringValue(cobblemonResource("move").toString()) }
this.future = actionEffect.run(context)
holds = context.holds // Reference so future things can check on this action effect's holds
@@ -9,14 +9,13 @@
package com.cobblemon.mod.common.block.chest
import com.cobblemon.mod.common.api.scheduling.SchedulingTracker
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import net.minecraft.entity.Entity
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
class GildedState : PoseableEntityState<Entity>() {
class GildedState : PosableState() {
override fun getEntity() = null
init {
setPose("CLOSED")
setPose("closed")
}
override fun updatePartialTicks(partialTicks: Float) {
@@ -18,9 +18,7 @@ import net.minecraft.block.entity.LootableContainerBlockEntity
import net.minecraft.block.entity.ViewerCountManager
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.inventory.DoubleInventory
import net.minecraft.inventory.Inventories
import net.minecraft.inventory.Inventory
import net.minecraft.inventory.SidedInventory
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NbtCompound
@@ -35,7 +33,7 @@ import net.minecraft.world.World
class GildedChestBlockEntity(pos: BlockPos, state: BlockState, val type: Type = Type.RED) : LootableContainerBlockEntity(CobblemonBlockEntities.GILDED_CHEST, pos, state), SidedInventory {
var inventoryContents: DefaultedList<ItemStack> = DefaultedList.ofSize(NUM_SLOTS, ItemStack.EMPTY)
val poseableState: GildedState = GildedState()
val posableState: GildedState = GildedState()
private val stateManager: ViewerCountManager = object : ViewerCountManager() {
override fun onContainerOpen(world: World, pos: BlockPos, state: BlockState) {
@@ -129,12 +127,12 @@ class GildedChestBlockEntity(pos: BlockPos, state: BlockState, val type: Type =
override fun onSyncedBlockEvent(type: Int, data: Int): Boolean {
if (type == 1) {
val isNowOpen = data > 0
val wasOpen = poseableState.currentPose == "OPEN"
val model = poseableState.currentModel ?: return true
val wasOpen = posableState.currentPose == "open"
val model = posableState.currentModel ?: return true
if (isNowOpen && !wasOpen) {
model.moveToPose(null, poseableState, model.getPose("OPEN")!!)
model.moveToPose(posableState, model.poses["open"]!!)
} else if (!isNowOpen && wasOpen) {
model.moveToPose(null, poseableState, model.getPose("CLOSED")!!)
model.moveToPose(posableState, model.poses["closed"]!!)
}
return true
}
@@ -13,7 +13,6 @@ import com.bedrockk.molang.runtime.MoParams
import com.bedrockk.molang.runtime.value.MoValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import net.minecraft.client.MinecraftClient
@@ -42,7 +41,7 @@ object ClientMoLangFunctions {
)
fun MoLangRuntime.setupClient(): MoLangRuntime {
environment.getQueryStruct().addFunctions(clientFunctions)
environment.query.addFunctions(clientFunctions)
return this
}
}
@@ -80,6 +80,14 @@ object CobblemonClient {
/** If true then we won't bother them anymore about choosing a starter even if it's a thing they can do. */
var checkedStarterScreen = false
var requests = ClientPlayerActionRequests()
var posableModelRepositories = listOf(
PokemonModelRepository,
PokeBallModelRepository,
NPCModelRepository,
FossilModelRepository,
BlockEntityModelRepository,
GenericBedrockEntityModelRepository
)
val overlay: PartyOverlay by lazy { PartyOverlay() }
@@ -275,22 +283,22 @@ object CobblemonClient {
this.implementation.registerEntityRenderer(CobblemonEntities.GENERIC_BEDROCK_ENTITY, ::GenericBedrockRenderer)
LOGGER.info("Registering PokeRod Bobber renderer")
this.implementation.registerEntityRenderer(CobblemonEntities.POKE_BOBBER) { ctx -> PokeBobberEntityRenderer(ctx) }
LOGGER.info("Registering NPC renderer")
this.implementation.registerEntityRenderer(CobblemonEntities.NPC, ::NPCRenderer)
}
fun reloadCodedAssets(resourceManager: ResourceManager) {
LOGGER.info("Loading assets...")
// Particles come first because animations need them.
BedrockParticleEffectRepository.loadEffects(resourceManager)
// Animations come next because models need them.
BedrockAnimationRepository.loadAnimations(
resourceManager = resourceManager,
directories = PokemonModelRepository.animationDirectories + PokeBallModelRepository.animationDirectories + FossilModelRepository.animationDirectories + BlockEntityModelRepository.animationDirectories + GenericBedrockEntityModelRepository.animationDirectories + NPCModelRepository.animationDirectories
directories = posableModelRepositories.flatMap { it.animationDirectories }
)
PokemonModelRepository.reload(resourceManager)
PokeBallModelRepository.reload(resourceManager)
NPCModelRepository.reload(resourceManager)
posableModelRepositories.forEach { it.reload(resourceManager) }
BerryModelRepository.reload(resourceManager)
FossilModelRepository.reload(resourceManager)
BlockEntityModelRepository.reload(resourceManager)
GenericBedrockEntityModelRepository.reload(resourceManager)
MiscModelRepository.reload(resourceManager)
LOGGER.info("Loaded assets")
}
@@ -11,7 +11,7 @@ package com.cobblemon.mod.common.client.battle
import com.cobblemon.mod.common.api.reactive.SettableObservable
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.scheduling.ClientTaskTracker
import com.cobblemon.mod.common.client.render.pokeball.PokeBallPoseableState
import com.cobblemon.mod.common.client.render.pokeball.PokeBallPosableState
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.pokeball.PokeBall
@@ -21,7 +21,7 @@ import com.cobblemon.mod.common.pokeball.PokeBall
* @author Hiroku
* @since July 2nd, 2022
*/
class ClientBallDisplay(val pokeBall: PokeBall, val aspects: Set<String>) : PokeBallPoseableState() {
class ClientBallDisplay(val pokeBall: PokeBall, val aspects: Set<String>) : PokeBallPosableState() {
override val stateEmitter = SettableObservable(EmptyPokeBallEntity.CaptureState.FALL)
override val shakeEmitter = SimpleObservable<Unit>()
override val schedulingTracker = ClientTaskTracker
@@ -11,7 +11,7 @@ package com.cobblemon.mod.common.client.battle
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonFloatingState
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.pokemon.status.PersistentStatus
@@ -52,6 +52,6 @@ class ClientBattlePokemon(
val gender: Gender
get() = properties.gender ?: Gender.GENDERLESS
var state = PokemonFloatingState()
var state = FloatingState()
}
@@ -8,21 +8,17 @@
package com.cobblemon.mod.common.client.entity
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.entity.EntitySideDelegate
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.reactive.SettableObservable
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.scheduling.SchedulingTracker
import com.cobblemon.mod.common.client.render.pokeball.PokeBallPoseableState
import com.cobblemon.mod.common.client.render.pokeball.PokeBallPosableState
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity.CaptureState
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity.CaptureState.NOT
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.entity.data.TrackedData
class EmptyPokeBallClientDelegate : PokeBallPoseableState(), EntitySideDelegate<EmptyPokeBallEntity> {
class EmptyPokeBallClientDelegate : PokeBallPosableState(), EntitySideDelegate<EmptyPokeBallEntity> {
override val stateEmitter: SettableObservable<CaptureState> = SettableObservable(NOT)
override val shakeEmitter = SimpleObservable<Unit>()
@@ -40,12 +36,11 @@ class EmptyPokeBallClientDelegate : PokeBallPoseableState(), EntitySideDelegate<
this.currentEntity = entity
age = entity.age
initSubscriptions()
this.runtime.environment.getQueryStruct().addFunctions(getEntity().struct.functions)
this.runtime.environment.query.addFunctions(getEntity().struct.functions)
}
override fun tick(entity: EmptyPokeBallEntity) {
super.tick(entity)
updateLocatorPosition(entity.pos)
incrementAge(entity)
}
@@ -54,6 +49,7 @@ class EmptyPokeBallClientDelegate : PokeBallPoseableState(), EntitySideDelegate<
when (data) {
EmptyPokeBallEntity.CAPTURE_STATE -> stateEmitter.set(currentEntity.captureState)
EmptyPokeBallEntity.SHAKE -> shakeEmitter.emit(Unit)
EmptyPokeBallEntity.ASPECTS -> currentAspects = currentEntity.aspects
}
}
}
@@ -9,12 +9,12 @@
package com.cobblemon.mod.common.client.entity
import com.cobblemon.mod.common.api.entity.EntitySideDelegate
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.GenericBedrockEntityModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.entity.generic.GenericBedrockEntity
class GenericBedrockClientDelegate : EntitySideDelegate<GenericBedrockEntity>, PoseableEntityState<GenericBedrockEntity>() {
class GenericBedrockClientDelegate : EntitySideDelegate<GenericBedrockEntity>, PosableState() {
lateinit var currentEntity: GenericBedrockEntity
override val schedulingTracker
get() = getEntity().schedulingTracker
@@ -25,17 +25,13 @@ class GenericBedrockClientDelegate : EntitySideDelegate<GenericBedrockEntity>, P
this.currentEntity = entity
this.age = entity.age
this.currentModel = GenericBedrockEntityModelRepository.getPoser(entity.category, entity.aspects)
val model = currentModel!!
model.context.put(RenderContext.ENTITY, entity)
currentModel!!.updateLocators(this)
currentModel!!.updateLocators(entity, this)
updateLocatorPosition(entity.pos)
val currentPoseType = entity.getCurrentPoseType()
val pose = this.currentModel!!.poses.values.firstOrNull { currentPoseType in it.poseTypes && (it.condition == null || it.condition.invoke(entity)) }
if (pose != null) {
doLater { setPose(pose.poseName) }
}
// Doing this awful thing because otherwise evolution particle won't start until the client looks at it. Which sucks slightly more than this.
val pose = this.currentModel!!.getFirstSuitablePose(this, currentPoseType)
doLater { setPose(pose.poseName) }
}
override fun tick(entity: GenericBedrockEntity) {
@@ -9,10 +9,11 @@
package com.cobblemon.mod.common.client.entity
import com.cobblemon.mod.common.api.entity.NPCSideDelegate
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.entity.npc.NPCEntity
import net.minecraft.entity.data.TrackedData
class NPCClientDelegate : PoseableEntityState<NPCEntity>(), NPCSideDelegate {
class NPCClientDelegate : PosableState(), NPCSideDelegate {
lateinit var npcEntity: NPCEntity
override val schedulingTracker
get() = npcEntity.schedulingTracker
@@ -23,10 +24,16 @@ class NPCClientDelegate : PoseableEntityState<NPCEntity>(), NPCSideDelegate {
override fun tick(entity: NPCEntity) {
super.tick(entity)
updateLocatorPosition(entity.pos)
incrementAge(entity)
}
override fun onTrackedDataSet(data: TrackedData<*>) {
super.onTrackedDataSet(data)
if (data == NPCEntity.ASPECTS) {
currentAspects = getEntity().dataTracker.get(NPCEntity.ASPECTS)
}
}
override fun getEntity() = npcEntity
override fun updatePartialTicks(partialTicks: Float) {
@@ -8,13 +8,12 @@
package com.cobblemon.mod.common.client.entity
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.entity.PokemonSideDelegate
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.scheduling.SchedulingTracker
import com.cobblemon.mod.common.api.scheduling.afterOnClient
@@ -23,10 +22,9 @@ import com.cobblemon.mod.common.client.ClientMoLangFunctions
import com.cobblemon.mod.common.client.particle.BedrockParticleEffectRepository
import com.cobblemon.mod.common.client.particle.ParticleStorm
import com.cobblemon.mod.common.client.render.MatrixWrapper
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.animation.ActiveAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PrimaryAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.StatefulAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPoseableModel
import com.cobblemon.mod.common.client.render.models.blockbench.repository.PokemonModelRepository
import com.cobblemon.mod.common.client.render.pokemon.PokemonRenderer.Companion.ease
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -43,7 +41,7 @@ import net.minecraft.util.Hand
import net.minecraft.util.Identifier
import net.minecraft.util.math.Vec3d
class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideDelegate {
class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
companion object {
const val BEAM_SHRINK_TIME = 0.4F
const val BEAM_EXTEND_TIME = 0.2F
@@ -80,7 +78,7 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
val secondsSinceBallThrown: Float
get() = (System.currentTimeMillis() - ballStartTime) / 1000F
private var cryAnimation: StatefulAnimation<PokemonEntity, *>? = null
private var cryAnimation: ActiveAnimation? = null
override fun onTrackedDataSet(data: TrackedData<*>) {
super.onTrackedDataSet(data)
@@ -89,12 +87,14 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
val identifier = Identifier(currentEntity.dataTracker.get(PokemonEntity.SPECIES))
currentPose = null
currentEntity.pokemon.species = PokemonSpecies.getByIdentifier(identifier)!! // TODO exception handling
// force a model update - handles edge case where the PoseableEntityState's tracked PoseableEntityModel isn't updated until the LivingEntityRenderer render is run
// force a model update - handles edge case where the PosableState's tracked PosableModel isn't updated until the LivingEntityRenderer render is run
currentModel = PokemonModelRepository.getPoser(identifier, currentEntity.aspects)
} else if (data == PokemonEntity.ASPECTS) {
currentAspects = currentEntity.dataTracker.get(PokemonEntity.ASPECTS)
} else if (data == PokemonEntity.DYING_EFFECTS_STARTED) {
val isDying = currentEntity.dataTracker.get(PokemonEntity.DYING_EFFECTS_STARTED)
if (isDying) {
val model = (currentModel ?: return) as PokemonPoseableModel
val model = currentModel ?: return
val animation = try {
model.getAnimation(this, "faint", runtime)
} catch (e: Exception) {
@@ -149,30 +149,30 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
playedSendOutSound = true
}
currentEntity.ownerUuid?.let {
client.world?.playSound(client.player, soundPos.x, soundPos.y, soundPos.z, SoundEvent.of(CobblemonSounds.POKE_BALL_SEND_OUT.id), SoundCategory.PLAYERS, 0.6f, 1f)
playedSendOutSound = true
/// create end rod particles in a 0.1 radius around the soundPos with a count of 50 and a random velocity of 0.1
sendOutPosition?.let{
val newPos = it.add(sendOutOffset)
val ballType = currentEntity.pokemon.caughtBall.name.path.toLowerCase().replace("_","")
val mode = if(currentEntity.isBattling) "battle" else "casual"
val sendflash = BedrockParticleEffectRepository.getEffect(cobblemonResource("${ballType}/${mode}/sendflash"))
sendflash?.let { effect ->
val wrapper = MatrixWrapper()
val matrix = MatrixStack()
matrix.translate(newPos.x, newPos.y, newPos.z)
wrapper.updateMatrix(matrix.peek().positionMatrix)
val world = MinecraftClient.getInstance().world ?: return@let
ParticleStorm(effect, wrapper, world).spawn()
val ballsparks = BedrockParticleEffectRepository.getEffect(cobblemonResource("${ballType}/${mode}/ballsparks"))
val ballsendsparkle = BedrockParticleEffectRepository.getEffect(cobblemonResource("${ballType}/${mode}/ballsendsparkle"))
// using afterOnClient because it's such a small timeframe that it's unlikely the entity has been removed & we'd like the precision
afterOnClient(seconds = 0.01667f) {
ballsparks?.let { effect -> ParticleStorm(effect, wrapper, world).spawn() }
ballsendsparkle?.let { effect -> ParticleStorm(effect, wrapper, world).spawn() }
currentEntity.after(seconds = 0.4f) {
val ballsparkle = BedrockParticleEffectRepository.getEffect(cobblemonResource("${ballType}/ballsparkle"))
ballsparkle?.let { effect -> ParticleStorm(effect, wrapper, world).spawn() }
client.world?.playSound(client.player, soundPos.x, soundPos.y, soundPos.z, SoundEvent.of(CobblemonSounds.POKE_BALL_SEND_OUT.id), SoundCategory.PLAYERS, 0.6f, 1f)
playedSendOutSound = true
/// create end rod particles in a 0.1 radius around the soundPos with a count of 50 and a random velocity of 0.1
sendOutPosition?.let{
val newPos = it.add(sendOutOffset)
val ballType = currentEntity.pokemon.caughtBall.name.path.toLowerCase().replace("_","")
val mode = if(currentEntity.isBattling) "battle" else "casual"
val sendflash = BedrockParticleEffectRepository.getEffect(cobblemonResource("${ballType}/${mode}/sendflash"))
sendflash?.let { effect ->
val wrapper = MatrixWrapper()
val matrix = MatrixStack()
matrix.translate(newPos.x, newPos.y, newPos.z)
wrapper.updateMatrix(matrix.peek().positionMatrix)
val world = MinecraftClient.getInstance().world ?: return@let
ParticleStorm(effect, wrapper, world).spawn()
val ballsparks = BedrockParticleEffectRepository.getEffect(cobblemonResource("${ballType}/${mode}/ballsparks"))
val ballsendsparkle = BedrockParticleEffectRepository.getEffect(cobblemonResource("${ballType}/${mode}/ballsendsparkle"))
// using afterOnClient because it's such a small timeframe that it's unlikely the entity has been removed & we'd like the precision
afterOnClient(seconds = 0.01667f) {
ballsparks?.let { effect -> ParticleStorm(effect, wrapper, world).spawn() }
ballsendsparkle?.let { effect -> ParticleStorm(effect, wrapper, world).spawn() }
currentEntity.after(seconds = 0.4f) {
val ballsparkle = BedrockParticleEffectRepository.getEffect(cobblemonResource("${ballType}/ballsparkle"))
ballsparkle?.let { effect -> ParticleStorm(effect, wrapper, world).spawn() }
}
}
}
@@ -255,14 +255,14 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
super.addToStruct(struct)
struct.addFunctions(functions.functions)
struct.addFunctions(ClientMoLangFunctions.clientFunctions)
runtime.environment.structs["query"] = struct
runtime.environment.query = struct
}
override fun initialize(entity: PokemonEntity) {
this.currentEntity = entity
this.age = entity.age
this.runtime.environment.getQueryStruct().addFunctions(mapOf(
this.runtime.environment.query.addFunctions(mapOf(
"in_battle" to java.util.function.Function {
return@Function DoubleValue(currentEntity.isBattling)
},
@@ -288,7 +288,6 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
}
override fun tick(entity: PokemonEntity) {
updateLocatorPosition(entity.pos)
incrementAge(entity)
}
@@ -298,9 +297,9 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
override fun handleStatus(status: Byte) {
if (status == 10.toByte()) {
val model = (currentModel ?: return) as PokemonPoseableModel
val animation = model.getEatAnimation(currentEntity, this) ?: return
statefulAnimations.add(animation)
val model = (currentModel ?: return)
val animation = model.getEatAnimation(this) ?: return
activeAnimations.add(animation)
}
}
@@ -310,18 +309,16 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
fun cry() {
val model = currentModel ?: return
if (model is PokemonPoseableModel) {
if (cryAnimation != null && (cryAnimation in statefulAnimations || cryAnimation == primaryAnimation)) {
return
}
val animation = model.cryAnimation(currentEntity, this) ?: return
if (animation is PrimaryAnimation) {
addPrimaryAnimation(animation)
} else {
statefulAnimations.add(animation)
}
cryAnimation = animation
if (cryAnimation != null && (cryAnimation in activeAnimations || cryAnimation == primaryAnimation)) {
return
}
val animation = model.cryAnimation(this) ?: return
if (animation is PrimaryAnimation) {
addPrimaryAnimation(animation)
} else {
activeAnimations.add(animation)
}
cryAnimation = animation
}
}
@@ -10,7 +10,7 @@ package com.cobblemon.mod.common.client.gui
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.gui.drawPoseablePortrait
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
@@ -23,6 +23,7 @@ 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.models.blockbench.repository.PokemonModelRepository
import com.cobblemon.mod.common.client.render.renderScaledGuiItemIcon
import com.cobblemon.mod.common.pokemon.Gender
@@ -53,6 +54,7 @@ class PartyOverlay : InGameHud(MinecraftClient.getInstance(), MinecraftClient.ge
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 screenExemptions: List<Class<out Screen>> = listOf(
@@ -149,13 +151,14 @@ class PartyOverlay : InGameHud(MinecraftClient.getInstance(), MinecraftClient.ge
0.0
)
drawPoseablePortrait(
drawPosablePortrait(
identifier = pokemon.species.resourceIdentifier,
aspects = pokemon.aspects,
matrixStack = matrices,
partialTicks = partialDeltaTicks,
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,
repository = PokemonModelRepository
repository = PokemonModelRepository,
state = state
)
matrices.pop()
context.disableScissor()
@@ -9,17 +9,17 @@
package com.cobblemon.mod.common.client.gui
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.PokemonModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.RenderablePokemon
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.client.MinecraftClient
import net.minecraft.client.render.DiffuseLighting
import net.minecraft.client.render.LightmapTextureManager
import net.minecraft.client.render.OverlayTexture
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.util.Identifier
import org.joml.Quaternionf
@@ -29,7 +29,7 @@ fun drawProfilePokemon(
renderablePokemon: RenderablePokemon,
matrixStack: MatrixStack,
rotation: Quaternionf,
state: PoseableEntityState<PokemonEntity>?,
state: PosableState,
partialTicks: Float,
scale: Float = 20F
) = drawProfilePokemon(
@@ -47,32 +47,33 @@ fun drawProfilePokemon(
aspects: Set<String>,
matrixStack: MatrixStack,
rotation: Quaternionf,
state: PoseableEntityState<PokemonEntity>?,
state: PosableState,
partialTicks: Float,
scale: Float = 20F
) {
val model = PokemonModelRepository.getPoser(species, aspects)
val texture = PokemonModelRepository.getTexture(species, aspects, state?.animationSeconds ?: 0F)
val texture = PokemonModelRepository.getTexture(species, aspects, state.animationSeconds)
val context = RenderContext()
PokemonModelRepository.getTextureNoSubstitute(species, aspects, 0f).let { it -> context.put(RenderContext.TEXTURE, it) }
model.context = context
PokemonModelRepository.getTextureNoSubstitute(species, aspects, 0f).let { context.put(RenderContext.TEXTURE, it) }
context.put(RenderContext.SCALE, PokemonSpecies.getByIdentifier(species)!!.getForm(aspects).baseScale)
context.put(RenderContext.SPECIES, species)
context.put(RenderContext.ASPECTS, aspects)
context.put(RenderContext.RENDER_STATE, RenderContext.RenderState.PROFILE)
context.put(RenderContext.POSABLE_STATE, state)
val renderType = model.getLayer(texture)
state.currentModel = model
state.currentAspects = aspects
val renderType = RenderLayer.getEntityCutout(texture)
RenderSystem.applyModelViewMatrix()
matrixStack.scale(scale, scale, -scale)
if (state != null) {
model.getPose(PoseType.PROFILE)?.let { state.setPose(it.poseName) }
state.timeEnteredPose = 0F
state.updatePartialTicks(partialTicks)
model.setupAnimStateful(null, state, 0F, 0F, 0F, 0F, 0F)
} else {
model.setupAnimStateless(PoseType.PROFILE)
}
state.setPoseToFirstSuitable(PoseType.PROFILE)
state.updatePartialTicks(partialTicks)
model.applyAnimations(null, state, 0F, 0F, 0F, 0F, 0F)
matrixStack.translate(model.profileTranslation.x, model.profileTranslation.y, model.profileTranslation.z - 4.0)
matrixStack.scale(model.profileScale, model.profileScale, 1 / model.profileScale)
@@ -9,7 +9,7 @@
package com.cobblemon.mod.common.client.gui.battle
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.gui.drawPoseablePortrait
import com.cobblemon.mod.common.api.gui.drawPosablePortrait
import com.cobblemon.mod.common.api.scheduling.Schedulable
import com.cobblemon.mod.common.api.scheduling.SchedulingTracker
import com.cobblemon.mod.common.api.text.bold
@@ -23,13 +23,13 @@ import com.cobblemon.mod.common.client.keybind.boundKey
import com.cobblemon.mod.common.client.keybind.keybinds.PartySendBinding
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.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.PokeBallModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.PokemonModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.sineFunction
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.pokemon.status.PersistentStatus
@@ -47,6 +47,7 @@ import net.minecraft.client.gui.screen.ChatScreen
import net.minecraft.client.render.DiffuseLighting
import net.minecraft.client.render.LightmapTextureManager
import net.minecraft.client.render.OverlayTexture
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.MutableText
import net.minecraft.util.math.MathHelper.ceil
@@ -134,7 +135,6 @@ class BattleOverlay : InGameHud(MinecraftClient.getInstance(), MinecraftClient.g
}
}
fun drawTile(context: DrawContext, tickDelta: Float, activeBattlePokemon: ActiveClientBattlePokemon, left: Boolean, rank: Int) {
val mc = MinecraftClient.getInstance()
@@ -197,7 +197,7 @@ class BattleOverlay : InGameHud(MinecraftClient.getInstance(), MinecraftClient.g
displayName: MutableText,
gender: Gender,
status: PersistentStatus?,
state: PoseableEntityState<PokemonEntity>?,
state: PosableState,
colour: Triple<Float, Float, Float>?,
opacity: Float,
ballState: ClientBallDisplay? = null,
@@ -239,7 +239,7 @@ class BattleOverlay : InGameHud(MinecraftClient.getInstance(), MinecraftClient.g
)
} else {
matrixStack.push()
drawPoseablePortrait(
drawPosablePortrait(
identifier = species.resourceIdentifier,
aspects = aspects,
matrixStack = matrixStack,
@@ -394,18 +394,19 @@ class BattleOverlay : InGameHud(MinecraftClient.getInstance(), MinecraftClient.g
partialTicks: Float,
reversed: Boolean = false
) {
val context = RenderContext()
val model = PokeBallModelRepository.getPoser(state.pokeBall.name, state.aspects)
val texture = PokeBallModelRepository.getTexture(state.pokeBall.name, state.aspects, state.animationSeconds)
val renderType = model.getLayer(texture)
val renderType = RenderLayer.getEntityCutout(texture)//model.getLayer(texture)
RenderSystem.applyModelViewMatrix()
val quaternion1 = RotationAxis.POSITIVE_Y.rotationDegrees(-32F * if (reversed) -1F else 1F)
val quaternion2 = RotationAxis.POSITIVE_X.rotationDegrees(5F)
model.getPose(PoseType.PORTRAIT)?.let { state.setPose(it.poseName) }
state.timeEnteredPose = 0F
state.currentModel = model
state.setPoseToFirstSuitable(PoseType.PORTRAIT)
state.updatePartialTicks(partialTicks)
model.setupAnimStateful(null, state, 0F, 0F, 0F, 0F, 0F)
model.applyAnimations(null, state, 0F, 0F, 0F, 0F, 0F)
matrixStack.scale(scale, scale, -scale)
matrixStack.translate(0.0, 5.5, -4.0)
@@ -424,7 +425,7 @@ class BattleOverlay : InGameHud(MinecraftClient.getInstance(), MinecraftClient.g
val immediate = MinecraftClient.getInstance().bufferBuilders.entityVertexConsumers
val buffer = immediate.getBuffer(renderType)
val packedLight = LightmapTextureManager.pack(11, 7)
model.render(matrixStack, buffer, packedLight, OverlayTexture.DEFAULT_UV, 1F, 1F, 1F, 1F)
model.render(context, matrixStack, buffer, packedLight, OverlayTexture.DEFAULT_UV, 1F, 1F, 1F, 1F)
immediate.draw()
@@ -14,6 +14,7 @@ import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.battle.SingleActionRequest
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.battleLang
import net.minecraft.client.MinecraftClient
@@ -22,8 +23,6 @@ import net.minecraft.client.gui.Selectable
import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder
import net.minecraft.client.sound.PositionedSoundInstance
import net.minecraft.client.sound.SoundManager
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.math.MathHelper.ceil
class BattleSwitchPokemonSelection(
battleGUI: BattleGUI,
@@ -54,6 +53,8 @@ class BattleSwitchPokemonSelection(
val pokemon: Pokemon,
val showdownPokemon: ShowdownPokemon
) {
val state = FloatingState()
fun isHovered(mouseX: Double, mouseY: Double) = mouseX in x..(x + SWITCH_TILE_WIDTH) && mouseY in (y..(y + SWITCH_TILE_HEIGHT))
fun render(context: DrawContext, mouseX: Double, mouseY: Double, deltaTicks: Float) {
val healthRatioSplits = showdownPokemon.condition.split(" ")[0].split("/")
@@ -77,7 +78,7 @@ class BattleSwitchPokemonSelection(
maxHealth = maxHp,
health = hp.toFloat(),
isFlatHealth = true,
state = null,
state = state,
colour = null,
opacity = selection.opacity,
partialTicks = deltaTicks
@@ -14,7 +14,6 @@ import com.cobblemon.mod.common.api.dialogue.ArtificialDialogueFaceProvider
import com.cobblemon.mod.common.api.dialogue.PlayerDialogueFaceProvider
import com.cobblemon.mod.common.api.dialogue.ReferenceDialogueFaceProvider
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
@@ -24,7 +23,7 @@ import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialogueNameWidget
import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialogueOptionWidget
import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialogueTextInputWidget
import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialogueTimerWidget
import com.cobblemon.mod.common.entity.Poseable
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.net.messages.client.dialogue.dto.DialogueDTO
import com.cobblemon.mod.common.net.messages.client.dialogue.dto.DialogueInputDTO
import com.cobblemon.mod.common.net.messages.server.dialogue.EscapeDialoguePacket
@@ -46,7 +45,7 @@ class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTra
is ReferenceDialogueFaceProvider -> {
key to DialogueRenderableSpeaker(
name = name,
face = ReferenceRenderableFace(MinecraftClient.getInstance().world?.getEntityById(face.entityId) as? Poseable ?: return@mapNotNull null)
face = ReferenceRenderableFace(MinecraftClient.getInstance().world?.getEntityById(face.entityId) as? PosableEntity ?: return@mapNotNull null)
)
}
else -> key to DialogueRenderableSpeaker(name, null)
@@ -108,7 +107,7 @@ class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTra
super.init()
runtime.environment
.getQueryStruct()
.query
.addFunctions(
dialogueMolangFunctions
.flatMap { it(this@DialogueScreen).entries }
@@ -9,16 +9,15 @@
package com.cobblemon.mod.common.client.gui.dialogue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.gui.drawPoseablePortrait
import com.cobblemon.mod.common.api.gui.drawPosablePortrait
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.client.entity.NPCClientDelegate
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.npc.NPCFloatingState
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonFloatingState
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.NPCModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.PokemonModelRepository
import com.cobblemon.mod.common.entity.Poseable
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.entity.npc.NPCEntity
import java.util.UUID
import kotlin.math.atan
@@ -73,12 +72,13 @@ class PlayerRenderableFace(val playerId: UUID) : RenderableFace {
}
}
class ReferenceRenderableFace(val entity: Poseable): RenderableFace {
val state = entity.delegate as PoseableEntityState<*>
class ReferenceRenderableFace(val entity: PosableEntity): RenderableFace {
val state = entity.delegate as PosableState
override fun render(drawContext: DrawContext, partialTicks: Float) {
val state = this.state
if (state is PokemonClientDelegate) {
drawPoseablePortrait(
state.currentAspects = state.currentEntity.pokemon.aspects
drawPosablePortrait(
identifier = state.currentEntity.pokemon.species.resourceIdentifier,
aspects = state.currentEntity.pokemon.aspects,
repository = PokemonModelRepository,
@@ -88,11 +88,11 @@ class ReferenceRenderableFace(val entity: Poseable): RenderableFace {
partialTicks = 0F // It's already being rendered potentially so we don't need to tick the state.
)
} else if (state is NPCClientDelegate) {
entity as NPCEntity
state.currentAspects = entity.aspects
val limbSwing = entity.limbAnimator.getPos(partialTicks)
val limbSwingAmount = entity.limbAnimator.getSpeed(partialTicks)
drawPoseablePortrait(
drawPosablePortrait(
identifier = state.npcEntity.npc.resourceIdentifier,
aspects = state.npcEntity.aspects,
repository = NPCModelRepository,
@@ -108,26 +108,21 @@ class ReferenceRenderableFace(val entity: Poseable): RenderableFace {
}
class ArtificialRenderableFace(
modelType: String,
val modelType: String,
val identifier: Identifier,
val aspects: Set<String>
): RenderableFace {
val state: PoseableEntityState<*> = if (modelType == "pokemon") {
PokemonFloatingState()
} else if (modelType == "npc") {
NPCFloatingState()
} else {
throw IllegalArgumentException("Unknown model type: $modelType")
}
val state = FloatingState()
override fun render(drawContext: DrawContext, partialTicks: Float) {
val state = this.state
if (state is PokemonFloatingState) {
state.currentAspects = aspects
if (modelType == "pokemon") {
val species = PokemonSpecies.getByIdentifier(identifier) ?: run {
Cobblemon.LOGGER.error("Unable to find species for $identifier for a dialogue face. Defaulting to first species.")
PokemonSpecies.species.first()
}
drawPoseablePortrait(
drawPosablePortrait(
identifier = species.resourceIdentifier,
aspects = aspects,
matrixStack = drawContext.matrices,
@@ -136,8 +131,8 @@ class ArtificialRenderableFace(
repository = PokemonModelRepository,
partialTicks = partialTicks
)
} else if (state is NPCFloatingState) {
drawPoseablePortrait(
} else if (modelType == "npc") {
drawPosablePortrait(
identifier = identifier,
aspects = aspects,
matrixStack = drawContext.matrices,
@@ -17,7 +17,7 @@ import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.summary.widgets.PartySlotWidget
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.pokemon.PokemonFloatingState
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.client.render.renderScaledGuiItemIcon
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.cobblemonResource
@@ -53,7 +53,7 @@ class PartySlotButton(
const val SCALE = 0.5F
}
val state = PokemonFloatingState()
val state = FloatingState()
private val renderablePokemon = pokemon.asRenderablePokemon().also { it.aspects = aspects }
@@ -10,14 +10,13 @@ package com.cobblemon.mod.common.client.gui.npc.widgets
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.gui.drawProfile
import com.cobblemon.mod.common.client.render.models.blockbench.npc.NPCFloatingState
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.NPCModelRepository
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.gui.Drawable
import net.minecraft.client.gui.Element
import net.minecraft.util.Identifier
import net.minecraft.util.math.RotationAxis
class NPCRenderWidget(
val x: Int,
@@ -25,7 +24,7 @@ class NPCRenderWidget(
var identifier: Identifier,
val aspects: MutableSet<String>
) : Drawable, Element {
val state = NPCFloatingState()
val state = FloatingState()
companion object {
val profileBackgroundResource = cobblemonResource("textures/gui/npc/profile_background.png")
const val WIDTH = 66
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.pc.StorageSlot
import com.cobblemon.mod.common.client.gui.summary.widgets.PartySlotWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.client.render.renderScaledGuiItemIcon
import com.cobblemon.mod.common.net.messages.client.pasture.OpenPasturePacket
import com.cobblemon.mod.common.net.messages.server.pasture.UnpasturePokemonPacket
@@ -151,6 +152,7 @@ class PasturePokemonScrollList(
class PastureSlot(val pokemon: OpenPasturePacket.PasturePokemonDataDTO, private val parent: PastureWidget) : Entry<PastureSlot>() {
val client: MinecraftClient = MinecraftClient.getInstance()
val state = FloatingState()
fun isOwned() = client.player?.uuid == pokemon.playerId
fun canUnpasture() = isOwned() || parent.pasturePCGUIConfiguration.permissions.canUnpastureOthers
@@ -204,7 +206,7 @@ class PasturePokemonScrollList(
aspects = pokemon.aspects,
matrixStack = matrixStack,
rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(13F, 35F, 0F)),
state = null,
state = state,
partialTicks = partialTicks,
scale = 4.5F
)
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.pasture.PasturePCGUIConfiguration
import com.cobblemon.mod.common.client.render.drawScaledText
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.pokemon.Pokemon
@@ -31,6 +32,7 @@ open class StorageSlot(
private val parent: StorageWidget,
onPress: PressAction
) : ButtonWidget(x, y, SIZE, SIZE, Text.literal("StorageSlot"), onPress, DEFAULT_NARRATION_SUPPLIER) {
val state = FloatingState()
companion object {
const val SIZE = 25
@@ -70,7 +72,7 @@ open class StorageSlot(
renderablePokemon = pokemon.asRenderablePokemon(),
matrixStack = matrices,
rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(13F, 35F, 0F)),
state = null,
state = state,
partialTicks = partialTicks,
scale = 4.5F
)
@@ -10,10 +10,10 @@ package com.cobblemon.mod.common.client.gui.startselection.widgets.preview
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.pokemon.RenderablePokemon
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.Text
import org.joml.Quaternionf
import org.joml.Vector3f
@@ -33,7 +33,7 @@ class StarterRoundabout(
private val clickAction: (mouseX: Double, mouseY: Double) -> Unit = { _, _ -> },
private val rotationVector: Vector3f
): SoundlessWidget(pX, pY, pWidth, pHeight, Text.literal("StarterRoundabout")) {
val state = FloatingState()
companion object {
const val MODEL_WIDTH = 30
const val MODEL_HEIGHT = 30
@@ -69,7 +69,7 @@ class StarterRoundabout(
renderablePokemon = pokemon,
matrixStack = matrices,
rotation = Quaternionf().fromEulerXYZDegrees(rotationVector),
state = null,
state = state,
scale = 18F,
partialTicks = delta
)
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.summary.SummaryButton
import com.cobblemon.mod.common.client.gui.summary.widgets.common.SummaryScrollList
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
@@ -62,6 +63,7 @@ class EvolutionSelectScreen(
class EvolveSlot(private val pokemon: Pokemon, private val evolution: EvolutionDisplay) : Entry<EvolveSlot>() {
val client: MinecraftClient = MinecraftClient.getInstance()
val state = FloatingState()
val form: FormData = evolution.species.getForm(evolution.aspects)
val selectButton: SummaryButton = SummaryButton(
buttonX = 0F,
@@ -139,7 +141,7 @@ class EvolutionSelectScreen(
aspects = this.evolution.aspects,
matrixStack = matrices,
rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(13F, 35F, 0F)),
state = null,
state = state,
scale = 6F,
partialTicks = partialTicks
)
@@ -9,10 +9,9 @@
package com.cobblemon.mod.common.client.gui.summary.widgets
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonFloatingState
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.pokemon.RenderablePokemon
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.DrawContext
import net.minecraft.text.Text
import org.joml.Quaternionf
@@ -31,7 +30,7 @@ class ModelWidget(
var render = true
}
var state = PokemonFloatingState()
var state = FloatingState()
val rotVec = Vector3f(13F, rotationY, 0F)
override fun renderButton(context: DrawContext, pMouseX: Int, pMouseY: Int, partialTicks: Float) {
@@ -15,6 +15,7 @@ import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.summary.Summary
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.pokemon.Pokemon
@@ -36,7 +37,7 @@ class PartySlotWidget(
private val index: Int,
private val isClientPartyMember: Boolean,
) : SoundlessWidget(pX.toInt(), pY.toInt(), WIDTH, HEIGHT, Text.literal("PartyMember")) {
val state = FloatingState()
companion object {
const val WIDTH = 46
const val HEIGHT = 27
@@ -156,7 +157,7 @@ class PartySlotWidget(
aspects = slotPokemon.aspects.toSet(),
matrixStack = matrices,
rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(13F, 35F, 0F)),
state = null,
state = state,
scale = 4.5F,
partialTicks = delta
)
@@ -10,11 +10,10 @@ package com.cobblemon.mod.common.client.gui.trade
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonFloatingState
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.pokemon.RenderablePokemon
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.Text
import org.joml.Quaternionf
import org.joml.Vector3f
@@ -27,7 +26,7 @@ class ModelWidget(
var rotationY: Float = 35F,
var offsetY: Double = 0.0
): SoundlessWidget(pX, pY, pWidth, pHeight, Text.literal("Trade - ModelWidget")) {
var state = PokemonFloatingState()
var state = FloatingState()
private var rotVec = Vector3f(13F, rotationY, 0F)
override fun renderButton(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.client.gui.trade
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.client.render.renderScaledGuiItemIcon
import com.cobblemon.mod.common.net.messages.client.trade.TradeStartedPacket
import com.cobblemon.mod.common.pokemon.Gender
@@ -20,7 +21,6 @@ import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.sound.SoundManager
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.Text
import org.joml.Quaternionf
import org.joml.Vector3f
@@ -32,7 +32,7 @@ open class PartySlot(
private val isOpposing: Boolean = false,
onPress: PressAction
) : ButtonWidget(x, y, SIZE, SIZE, Text.literal("PartySlot"), onPress, DEFAULT_NARRATION_SUPPLIER) {
val state = FloatingState()
companion object {
const val SIZE = 25
@@ -75,7 +75,7 @@ open class PartySlot(
renderablePokemon = pokemon.asRenderablePokemon(),
matrixStack = matrices,
rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(13F, 35F, 0F)),
state = null,
state = state,
scale = 4.5F,
partialTicks = delta
)
@@ -27,7 +27,7 @@ object DebugKeybindings {
TranslateDownKeybinding(),
PrintModelSettingsKeybinding()
)
class ScaleUpKeybinding(): CobblemonKeyBinding(
class ScaleUpKeybinding: CobblemonKeyBinding(
"key.cobblemon.scaleportraitup",
InputUtil.Type.KEYSYM,
InputUtil.GLFW_KEY_EQUAL,
@@ -42,7 +42,7 @@ object DebugKeybindings {
}
}
class ScaleDownKeybinding(): CobblemonKeyBinding(
class ScaleDownKeybinding: CobblemonKeyBinding(
"key.cobblemon.scaleportraitdown",
InputUtil.Type.KEYSYM,
InputUtil.GLFW_KEY_MINUS,
@@ -73,7 +73,7 @@ object DebugKeybindings {
}
}
class TranslateDownKeybinding(): CobblemonKeyBinding(
class TranslateDownKeybinding: CobblemonKeyBinding(
"key.cobblemon.translateportraitdown",
InputUtil.Type.KEYSYM,
InputUtil.GLFW_KEY_K,
@@ -89,7 +89,7 @@ object DebugKeybindings {
}
}
class TranslateLeftKeybinding(): CobblemonKeyBinding(
class TranslateLeftKeybinding: CobblemonKeyBinding(
"key.cobblemon.translateportraitleft",
InputUtil.Type.KEYSYM,
InputUtil.GLFW_KEY_J,
@@ -105,7 +105,7 @@ object DebugKeybindings {
}
}
class TranslateRightKeybinding(): CobblemonKeyBinding(
class TranslateRightKeybinding: CobblemonKeyBinding(
"key.cobblemon.translateportraitright",
InputUtil.Type.KEYSYM,
InputUtil.GLFW_KEY_L,
@@ -120,7 +120,7 @@ object DebugKeybindings {
}
}
class PrintModelSettingsKeybinding() : CobblemonKeyBinding(
class PrintModelSettingsKeybinding : CobblemonKeyBinding(
"key.cobblemon.printmodelsettings",
InputUtil.Type.KEYSYM,
InputUtil.GLFW_KEY_PERIOD,
@@ -9,21 +9,20 @@
package com.cobblemon.mod.common.client.net.animation
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.entity.Poseable
import com.cobblemon.mod.common.net.messages.client.animation.PlayPoseableAnimationPacket
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.resolve
import net.minecraft.client.MinecraftClient
object PlayPoseableAnimationHandler : ClientNetworkPacketHandler<PlayPoseableAnimationPacket> {
override fun handle(packet: PlayPoseableAnimationPacket, client: MinecraftClient) {
object PlayPosableAnimationHandler : ClientNetworkPacketHandler<PlayPosableAnimationPacket> {
override fun handle(packet: PlayPosableAnimationPacket, client: MinecraftClient) {
val world = client.world ?: return
val entity = world.getEntityById(packet.entityId) ?: return
if (entity is Poseable) {
if (entity is PosableEntity) {
val delegate = entity.delegate
if (delegate is PoseableEntityState<*>) {
if (delegate is PosableState) {
for (expr in packet.expressions) {
delegate.runtime.resolve(expr.asExpressionLike())
}
@@ -13,7 +13,7 @@ import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.battle.ClientBattlePokemon
import com.cobblemon.mod.common.client.battle.animations.MoveTileOffscreenAnimation
import com.cobblemon.mod.common.client.battle.animations.SwapAndMoveTileOnscreenAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonFloatingState
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.net.messages.client.battle.BattleSwitchPokemonPacket
import net.minecraft.client.MinecraftClient
@@ -42,7 +42,7 @@ object BattleSwitchPokemonHandler : ClientNetworkPacketHandler<BattleSwitchPokem
statChanges = statChanges
).also {
it.actor = actor
it.state = PokemonFloatingState()
it.state = FloatingState()
}
}
)
@@ -9,8 +9,8 @@
package com.cobblemon.mod.common.client.net.effect
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.entity.Poseable
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.resolve
@@ -20,8 +20,8 @@ object RunPosableMoLangHandler : ClientNetworkPacketHandler<RunPosableMoLangPack
override fun handle(packet: RunPosableMoLangPacket, client: MinecraftClient) {
val world = client.world ?: return
val entity = world.getEntityById(packet.entityId) ?: return
if (entity is Poseable) {
val state = entity.delegate as? PoseableEntityState<*> ?: return
if (entity is PosableEntity) {
val state = entity.delegate as? PosableState ?: return
for (expression in packet.expressions.map { it.asExpressionLike() }) {
state.runtime.resolve(expression)
}
@@ -9,14 +9,13 @@
package com.cobblemon.mod.common.client.net.effect
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
import com.cobblemon.mod.common.client.particle.BedrockParticleEffectRepository
import com.cobblemon.mod.common.client.particle.ParticleStorm
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.entity.Poseable
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
import net.minecraft.client.MinecraftClient
import net.minecraft.entity.Entity
@@ -25,13 +24,13 @@ object SpawnSnowstormEntityParticleHandler : ClientNetworkPacketHandler<SpawnSno
override fun handle(packet: SpawnSnowstormEntityParticlePacket, client: MinecraftClient) {
val world = MinecraftClient.getInstance().world ?: return
val effect = BedrockParticleEffectRepository.getEffect(packet.effectId) ?: return
val entity = world.getEntityById(packet.entityId) as? Poseable ?: return
val entity = world.getEntityById(packet.entityId) as? PosableEntity ?: return
entity as Entity
val state = entity.delegate as PoseableEntityState<*>
val state = entity.delegate as PosableState
val matrixWrapper = state.locatorStates[packet.locator] ?: state.locatorStates["root"]!!
val particleRuntime = MoLangRuntime().setup().setupClient()
particleRuntime.environment.getQueryStruct().addFunction("entity") { state.runtime.environment.getQueryStruct() }
particleRuntime.environment.query.addFunction("entity") { state.runtime.environment.query }
val storm = ParticleStorm(
effect = effect,
@@ -10,20 +10,17 @@ package com.cobblemon.mod.common.client.particle
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies.species
import com.cobblemon.mod.common.api.snowstorm.BedrockParticleEffect
import com.cobblemon.mod.common.api.snowstorm.ParticleEmitterAction
import com.cobblemon.mod.common.client.render.MatrixWrapper
import com.cobblemon.mod.common.client.render.SnowstormParticle
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.Poseable
import com.cobblemon.mod.common.particle.SnowstormParticleEffect
import com.cobblemon.mod.common.util.math.geometry.transformDirection
import kotlin.random.Random
import net.minecraft.client.MinecraftClient
import net.minecraft.client.particle.NoRenderParticle
import net.minecraft.client.util.ParticleUtil.spawnParticle
import net.minecraft.client.world.ClientWorld
import net.minecraft.entity.Entity
import net.minecraft.util.math.Vec3d
@@ -47,7 +44,7 @@ class ParticleStorm(
): NoRenderParticle(world, matrixWrapper.getOrigin().x, matrixWrapper.getOrigin().y, matrixWrapper.getOrigin().z) {
fun spawn() {
if (entity != null) {
runtime.environment.getQueryStruct()
runtime.environment.query
.addFunction("entity_width") { DoubleValue(entity.boundingBox.xLength) }
.addFunction("entity_height") { DoubleValue(entity.boundingBox.yLength) }
.addFunction("entity_size") { DoubleValue(entity.boundingBox.run { if (xLength > yLength) xLength else yLength }) }
@@ -61,8 +58,8 @@ class ParticleStorm(
val entityScale = pokeEntity?.scaleFactor ?: 1.0F
DoubleValue(baseScale * pokemonScale * entityScale)
}
if (entity is Poseable) {
runtime.environment.getQueryStruct().addFunction("entity") { entity.struct }
if (entity is PosableEntity) {
runtime.environment.query.addFunction("entity") { entity.struct }
}
// TODO replace with a generified call to if (entity is MoLangEntity) entity.applyVariables(env) or w/e
runtime.environment.setSimpleVariable("entity_width", DoubleValue(entity.boundingBox.xLength))
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.client.render
import com.bedrockk.molang.runtime.struct.VariableStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.ModAPI
@@ -61,12 +60,10 @@ class SnowstormParticle(
var texture = storm.effect.particle.texture
var variableStruct = (storm.runtime.environment.structs["variable"] as VariableStruct);
val random1 = variableStruct.map["particle_random_1"]
val random2 = variableStruct.map["particle_random_2"]
val random3 = variableStruct.map["particle_random_3"]
val random4 = variableStruct.map["particle_random_4"]
val random1 = storm.runtime.environment.variable.map["particle_random_1"]
val random2 = storm.runtime.environment.variable.map["particle_random_2"]
val random3 = storm.runtime.environment.variable.map["particle_random_3"]
val random4 = storm.runtime.environment.variable.map["particle_random_4"]
var localX = x - storm.getX()
var localY = y - storm.getY()
@@ -110,10 +107,10 @@ class SnowstormParticle(
}
private fun applyRandoms() {
variableStruct.setDirectly("particle_random_1", random1)
variableStruct.setDirectly("particle_random_2", random2)
variableStruct.setDirectly("particle_random_3", random3)
variableStruct.setDirectly("particle_random_4", random4)
storm.runtime.environment.variable.setDirectly("particle_random_1", random1)
storm.runtime.environment.variable.setDirectly("particle_random_2", random2)
storm.runtime.environment.variable.setDirectly("particle_random_3", random3)
storm.runtime.environment.variable.setDirectly("particle_random_4", random4)
}
init {
@@ -514,8 +511,8 @@ class SnowstormParticle(
private fun setParticleAgeInRuntime() {
variableStruct.setDirectly("particle_age", DoubleValue(age / 20.0))
variableStruct.setDirectly("particle_lifetime", DoubleValue(maxAge / 20.0))
storm.runtime.environment.variable.setDirectly("particle_age", DoubleValue(age / 20.0))
storm.runtime.environment.variable.setDirectly("particle_lifetime", DoubleValue(maxAge / 20.0))
}
override fun getType() = particleTextureSheet
@@ -8,7 +8,7 @@
package com.cobblemon.mod.common.client.render
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Bone
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
@@ -19,7 +19,6 @@ import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.GsonBuilder
import com.google.gson.annotations.SerializedName
import kotlin.math.floor
import net.minecraft.entity.Entity
import net.minecraft.util.Identifier
import org.joml.Vector3f
import org.joml.Vector4f
@@ -30,12 +29,12 @@ import org.joml.Vector4f
* @author Hiroku
* @since May 14th, 2022
*/
class VaryingRenderableResolver<E : Entity, M : PoseableEntityModel<E>>(
class VaryingRenderableResolver<T : PosableModel>(
val name: Identifier,
val variations: MutableList<ModelAssetVariation>
) {
lateinit var repository: VaryingModelRepository<E, M>
val posers = mutableMapOf<Pair<Identifier, Identifier>, M>()
lateinit var repository: VaryingModelRepository<T>
val posers = mutableMapOf<Pair<Identifier, Identifier>, T>()
val models = mutableMapOf<Identifier, Bone>()
fun getResolvedPoser(aspects: Set<String>): Identifier {
@@ -92,7 +91,7 @@ class VaryingRenderableResolver<E : Entity, M : PoseableEntityModel<E>>(
.create()
}
fun initialize(repository: VaryingModelRepository<E, M>) {
fun initialize(repository: VaryingModelRepository<T>) {
this.repository = repository
posers.clear()
getAllModels().forEach { identifier ->
@@ -104,7 +103,7 @@ class VaryingRenderableResolver<E : Entity, M : PoseableEntityModel<E>>(
}
}
fun getPoser(aspects: Set<String>): M {
fun getPoser(aspects: Set<String>): T {
val poserName = getResolvedPoser(aspects)
val poserSupplier = repository.posers[poserName] ?: throw IllegalStateException("No poser found for name: $poserName for $name")
val modelName = getResolvedModel(aspects)
@@ -166,7 +165,7 @@ class ModelAssetVariation(
/**
* Given the animation seconds, returns a texture to use. Only implemented
* by [StaticModelTextureSupplier], [FallbackModelTextureSupplier] and [AnimatedModelTextureSupplier].
* by [StaticModelTextureSupplier] and [AnimatedModelTextureSupplier].
*
* @author Hiroku
* @since February 6th, 2023
@@ -13,12 +13,14 @@ import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.block.DisplayCaseBlock
import com.cobblemon.mod.common.block.entity.DisplayCaseBlockEntity
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.PokemonModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.item.PokeBallItem
import com.cobblemon.mod.common.item.PokemonItem
import net.minecraft.client.MinecraftClient
import net.minecraft.client.render.OverlayTexture
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.VertexConsumer
import net.minecraft.client.render.VertexConsumerProvider
import net.minecraft.client.render.block.entity.BlockEntityRenderer
@@ -36,6 +38,9 @@ import net.minecraft.util.math.RotationAxis
import net.minecraft.world.World
class DisplayCaseRenderer(ctx: BlockEntityRendererFactory.Context) : BlockEntityRenderer<DisplayCaseBlockEntity> {
val context = RenderContext().also {
it.put(RenderContext.RENDER_STATE, RenderContext.RenderState.WORLD)
}
val coinPouchStack: ItemStack by lazy { ItemStack(CobblemonItems.RELIC_COIN_POUCH).also { it.setSubNbt("CustomModelData", NbtInt.of(1)) } }
override fun render(
entity: DisplayCaseBlockEntity,
@@ -100,9 +105,10 @@ class DisplayCaseRenderer(ctx: BlockEntityRendererFactory.Context) : BlockEntity
yRot: Float
) {
val item = stack.item as? PokemonItem ?: return
val pokemon = item.asPokemon(stack) ?: return
val model = PokemonModelRepository.getPoser(pokemon.species.resourceIdentifier, pokemon.aspects)
val renderLayer = model.getLayer(PokemonModelRepository.getTexture(pokemon.species.resourceIdentifier, pokemon.aspects, 0F))
val (species, aspects) = item.getSpeciesAndAspects(stack) ?: return
val model = PokemonModelRepository.getPoser(species.resourceIdentifier, aspects)
val texture = PokemonModelRepository.getTexture(species.resourceIdentifier, aspects, 0F)
val renderLayer = RenderLayer.getEntityCutout(texture)//model.getLayer(texture)
val tint = item.tint(stack)
val vertexConsumer: VertexConsumer = vertexConsumers.getBuffer(renderLayer)
val scale = 0.25f
@@ -113,10 +119,29 @@ class DisplayCaseRenderer(ctx: BlockEntityRendererFactory.Context) : BlockEntity
matrices.scale(scale, scale, scale)
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yRot))
model.setupAnimStateless(PoseType.PROFILE)
val state = FloatingState()
state.currentAspects = aspects
model.context = context
context.put(RenderContext.SCALE, scale)
context.put(RenderContext.SPECIES, species.resourceIdentifier)
context.put(RenderContext.ASPECTS, aspects)
context.put(RenderContext.TEXTURE, texture)
context.put(RenderContext.POSABLE_STATE, state)
state.currentPose = model.getFirstSuitablePose(state, PoseType.PROFILE).poseName
model.withLayerContext(vertexConsumers, null, PokemonModelRepository.getLayers(pokemon.species.resourceIdentifier, pokemon.aspects)) {
model.render(matrices, vertexConsumer, light, OverlayTexture.DEFAULT_UV, tint.x, tint.y, tint.z, tint.w)
model.applyAnimations(
entity = null,
state = state,
limbSwing = 0F,
limbSwingAmount = 0F,
ageInTicks = 0F,
headYaw = 0F,
headPitch = 0F
)
model.withLayerContext(vertexConsumers, state, PokemonModelRepository.getLayers(species.resourceIdentifier, aspects)) {
model.render(context, matrices, vertexConsumer, light, OverlayTexture.DEFAULT_UV, tint.x, tint.y, tint.z, tint.w)
}
matrices.pop()
@@ -10,7 +10,9 @@ package com.cobblemon.mod.common.client.render.block
import com.cobblemon.mod.common.block.entity.GildedChestBlockEntity
import com.cobblemon.mod.common.client.render.models.blockbench.repository.BlockEntityModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import net.minecraft.client.render.OverlayTexture
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.VertexConsumerProvider
import net.minecraft.client.render.block.entity.BlockEntityRenderer
import net.minecraft.client.render.block.entity.BlockEntityRendererFactory
@@ -28,24 +30,31 @@ class GildedChestBlockRenderer(context: BlockEntityRendererFactory.Context) : Bl
overlay: Int
) {
val aspects = emptySet<String>()
val state = entity.poseableState
val state = entity.posableState
state.updatePartialTicks(tickDelta)
val poserId = entity.type.poserId
val model = BlockEntityModelRepository.getPoser(poserId, aspects)
val texture = BlockEntityModelRepository.getTexture(poserId, aspects, state.animationSeconds)
val vertexConsumer = vertexConsumers.getBuffer(model.getLayer(texture))
val vertexConsumer = vertexConsumers.getBuffer(RenderLayer.getEntityCutout(texture))
model.bufferProvider = vertexConsumers
state.currentModel = model
state.currentAspects = aspects
val context = RenderContext()
context.put(RenderContext.RENDER_STATE, RenderContext.RenderState.BLOCK)
context.put(RenderContext.ASPECTS, aspects)
context.put(RenderContext.TEXTURE, texture)
context.put(RenderContext.SPECIES, poserId)
context.put(RenderContext.POSABLE_STATE, state)
matrices.push()
matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(180f))
matrices.translate(-0.5, 0.0, 0.5)
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(entity.cachedState.get(Properties.HORIZONTAL_FACING).asRotation()))
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(180f))
model.setupAnimStateful(
model.applyAnimations(
entity = null,
state = state,
headYaw = 0F,
@@ -54,9 +63,9 @@ class GildedChestBlockRenderer(context: BlockEntityRendererFactory.Context) : Bl
limbSwingAmount = 0F,
ageInTicks = state.animationSeconds * 20
)
model.render(matrices, vertexConsumer, light, overlay, 1.0f, 1.0f, 1.0f, 1.0f)
model.render(context, matrices, vertexConsumer, light, overlay, 1.0f, 1.0f, 1.0f, 1.0f)
model.withLayerContext(vertexConsumers, state, BlockEntityModelRepository.getLayers(poserId, aspects)) {
model.render(matrices, vertexConsumer, light, OverlayTexture.DEFAULT_UV, 1F, 1F, 1F, 1F)
model.render(context, matrices, vertexConsumer, light, OverlayTexture.DEFAULT_UV, 1F, 1F, 1F, 1F)
}
model.setDefault()
matrices.pop()
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.block.entity.RestorationTankBlockEntity
import com.cobblemon.mod.common.block.multiblock.FossilMultiblockStructure
import com.cobblemon.mod.common.client.CobblemonBakingOverrides
import com.cobblemon.mod.common.client.render.models.blockbench.repository.FossilModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunction
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.parabolaFunction
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.rerange
@@ -30,6 +31,11 @@ import net.minecraft.util.math.Direction
import net.minecraft.util.math.RotationAxis
class RestorationTankRenderer(ctx: BlockEntityRendererFactory.Context) : BlockEntityRenderer<RestorationTankBlockEntity> {
val context = RenderContext().also {
it.put(RenderContext.DO_QUIRKS, true)
it.put(RenderContext.RENDER_STATE, RenderContext.RenderState.RESURRECTION_MACHINE)
}
override fun render(
entity: RestorationTankBlockEntity,
tickDelta: Float,
@@ -65,7 +71,9 @@ class RestorationTankRenderer(ctx: BlockEntityRendererFactory.Context) : BlockEn
return
}
if (struct.isRunning() or (struct.hasCreatedPokemon)) renderFetus(entity, tickDelta, matrices, vertexConsumers, light, overlay)
if (struct.isRunning() or (struct.hasCreatedPokemon)) {
renderFetus(entity, tickDelta, matrices, vertexConsumers, light, overlay)
}
matrices.push()
val transparentBuffer = vertexConsumers.getBuffer(RenderLayer.getTranslucent())
@@ -107,6 +115,7 @@ class RestorationTankRenderer(ctx: BlockEntityRendererFactory.Context) : BlockEn
val completionPercentage = (1 - timeRemaining / FossilMultiblockStructure.TIME_TO_TAKE.toFloat()).coerceIn(0F, 1F)
val fossilFetusModel = FossilModelRepository.getPoser(fossil.identifier, aspects)
fossilFetusModel.context = context
val embryo1Scale = EMBRYO_CURVE_1(completionPercentage)
val embryo2Scale = EMBRYO_CURVE_2(completionPercentage)
@@ -125,11 +134,10 @@ class RestorationTankRenderer(ctx: BlockEntityRendererFactory.Context) : BlockEn
val texture = FossilModelRepository.getTexture(identifier, aspects, state.animationSeconds)
if (scale > 0F) {
val vertexConsumer = vertexConsumers.getBuffer(model.getLayer(texture))
val pose = model.poses.values.first()
val vertexConsumer = vertexConsumers.getBuffer(RenderLayer.getEntityCutout(texture))
state.currentModel = model
state.setPose(pose.poseName)
state.timeEnteredPose = 0F
state.currentAspects = aspects
state.setPoseToFirstSuitable()
val scale = if (timeRemaining == 0) {
model.maxScale
@@ -148,10 +156,16 @@ class RestorationTankRenderer(ctx: BlockEntityRendererFactory.Context) : BlockEn
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(90F))
}
model.context = context
context.put(RenderContext.TEXTURE, texture)
context.put(RenderContext.SPECIES, fossil.identifier)
context.put(RenderContext.RENDER_STATE, RenderContext.RenderState.RESURRECTION_MACHINE)
context.put(RenderContext.POSABLE_STATE, state)
matrices.push()
matrices.scale(scale, scale, scale)
matrices.translate(0.0, model.yGrowthPoint.toDouble(), 0.0)
model.setupAnimStateful(
model.applyAnimations(
entity = null,
state = state,
headYaw = 0F,
@@ -160,9 +174,9 @@ class RestorationTankRenderer(ctx: BlockEntityRendererFactory.Context) : BlockEn
limbSwingAmount = 0F,
ageInTicks = state.animationSeconds * 20
)
model.render(matrices, vertexConsumer, light, overlay, 1.0f, 1.0f, 1.0f, 1.0f)
model.render(context, matrices, vertexConsumer, light, overlay, 1.0f, 1.0f, 1.0f, 1.0f)
model.withLayerContext(vertexConsumers, state, FossilModelRepository.getLayers(fossil.identifier, aspects)) {
model.render(matrices, vertexConsumer, light, OverlayTexture.DEFAULT_UV, 1F, 1F, 1F, 1F)
model.render(context, matrices, vertexConsumer, light, OverlayTexture.DEFAULT_UV, 1F, 1F, 1F, 1F)
}
model.setDefault()
matrices.pop()
@@ -9,10 +9,12 @@
package com.cobblemon.mod.common.client.render.generic
import com.cobblemon.mod.common.client.entity.GenericBedrockClientDelegate
import com.cobblemon.mod.common.client.render.models.blockbench.generic.PosableGenericEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.repository.GenericBedrockEntityModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.PokemonModelRepository
import com.cobblemon.mod.common.entity.generic.GenericBedrockEntity
import net.minecraft.client.render.OverlayTexture
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.VertexConsumerProvider
import net.minecraft.client.render.entity.EntityRenderer
import net.minecraft.client.render.entity.EntityRendererFactory
@@ -20,6 +22,7 @@ import net.minecraft.client.util.math.MatrixStack
import net.minecraft.util.math.RotationAxis
class GenericBedrockRenderer(context: EntityRendererFactory.Context) : EntityRenderer<GenericBedrockEntity>(context) {
val model = PosableGenericEntityModel()
override fun getTexture(entity: GenericBedrockEntity) = GenericBedrockEntityModelRepository.getTexture(entity.category, entity.aspects, (entity.delegate as GenericBedrockClientDelegate).animationSeconds)
override fun render(entity: GenericBedrockEntity, yaw: Float, partialTicks: Float, poseStack: MatrixStack, buffer: VertexConsumerProvider, packedLight: Int) {
if (entity.isInvisible) {
@@ -27,17 +30,18 @@ class GenericBedrockRenderer(context: EntityRendererFactory.Context) : EntityRen
}
val model = GenericBedrockEntityModelRepository.getPoser(entity.category, entity.aspects)
this.model.posableModel = model
poseStack.push()
poseStack.scale(1.0F, -1.0F, 1.0F)
poseStack.scale(entity.scale, entity.scale, entity.scale)
poseStack.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yaw))
val vertexConsumer = buffer.getBuffer(model.getLayer(getTexture(entity)))
val vertexConsumer = buffer.getBuffer(RenderLayer.getEntityCutout(getTexture(entity)))
val state = entity.delegate as GenericBedrockClientDelegate
state.updatePartialTicks(partialTicks)
model.setLayerContext(buffer, state, PokemonModelRepository.getLayers(entity.category, entity.aspects))
model.setAngles(entity, 0f, 0f, entity.age + partialTicks, 0F, 0F)
model.render(poseStack, vertexConsumer, packedLight, OverlayTexture.DEFAULT_UV, 1.0f, 1.0f, 1.0f, 1.0f)
this.model.setAngles(entity, 0f, 0f, entity.age + partialTicks, 0F, 0F)
this.model.render(poseStack, vertexConsumer, packedLight, OverlayTexture.DEFAULT_UV, 1.0f, 1.0f, 1.0f, 1.0f)
model.green = 1F
model.blue = 1F
@@ -8,13 +8,16 @@
package com.cobblemon.mod.common.client.render.item
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.PokemonModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.item.PokemonItem
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import net.minecraft.client.render.DiffuseLighting
import net.minecraft.client.render.LightmapTextureManager
import net.minecraft.client.render.OverlayTexture
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.VertexConsumer
import net.minecraft.client.render.VertexConsumerProvider
import net.minecraft.client.render.model.json.ModelTransformationMode
@@ -24,20 +27,35 @@ import org.joml.Quaternionf
import org.joml.Vector3f
class PokemonItemRenderer : CobblemonBuiltinItemRenderer {
val context = RenderContext().also {
it.put(RenderContext.RENDER_STATE, RenderContext.RenderState.PROFILE)
it.put(RenderContext.DO_QUIRKS, false)
}
override fun render(stack: ItemStack, mode: ModelTransformationMode, matrices: MatrixStack, vertexConsumers: VertexConsumerProvider, light: Int, overlay: Int) {
val pokemonItem = stack.item as? PokemonItem ?: return
val (species, aspects) = pokemonItem.getSpeciesAndAspects(stack) ?: return
val state = FloatingState()
matrices.push()
val model = PokemonModelRepository.getPoser(species.resourceIdentifier, aspects)
val renderLayer = model.getLayer(PokemonModelRepository.getTexture(species.resourceIdentifier, aspects, 0F))
model.context = context
context.put(RenderContext.RENDER_STATE, RenderContext.RenderState.PROFILE)
context.put(RenderContext.SPECIES, species.resourceIdentifier)
context.put(RenderContext.ASPECTS, aspects)
context.put(RenderContext.POSABLE_STATE, state)
state.currentModel = model
state.currentAspects = aspects
val renderLayer = RenderLayer.getEntityCutout(PokemonModelRepository.getTexture(species.resourceIdentifier, aspects, 0F))
val transformations = positions[mode]!!
DiffuseLighting.enableGuiDepthLighting()
matrices.scale(transformations.scale.x, transformations.scale.y, transformations.scale.z)
matrices.translate(transformations.translation.x, transformations.translation.y, transformations.translation.z)
model.setupAnimStateless(PoseType.PROFILE)
state.setPoseToFirstSuitable(PoseType.PORTRAIT)
model.applyAnimations(null, state, 0F, 0F, 0F, 0F, 0F)
matrices.translate(model.profileTranslation.x, model.profileTranslation.y, model.profileTranslation.z - 4.0)
matrices.scale(model.profileScale, model.profileScale, 0.15F)
@@ -54,8 +72,8 @@ class PokemonItemRenderer : CobblemonBuiltinItemRenderer {
// x = red, y = green, z = blue, w = alpha
val tint = pokemonItem.tint(stack)
model.withLayerContext(vertexConsumers, null, PokemonModelRepository.getLayers(species.resourceIdentifier, aspects)) {
model.render(matrices, vertexConsumer, packedLight, OverlayTexture.DEFAULT_UV, tint.x, tint.y, tint.z, tint.w)
model.withLayerContext(vertexConsumers, state, PokemonModelRepository.getLayers(species.resourceIdentifier, aspects)) {
model.render(context, matrices, vertexConsumer, packedLight, OverlayTexture.DEFAULT_UV, tint.x, tint.y, tint.z, tint.w)
}
model.setDefault()
@@ -9,8 +9,10 @@
package com.cobblemon.mod.common.client.render.layer
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonFloatingState
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.PokemonModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Pokemon
@@ -19,6 +21,7 @@ import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.isPokemonEntity
import java.util.UUID
import net.minecraft.client.render.OverlayTexture
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.VertexConsumerProvider
import net.minecraft.client.render.entity.LivingEntityRenderer
import net.minecraft.client.render.entity.feature.FeatureRenderer
@@ -34,6 +37,15 @@ import net.minecraft.util.math.RotationAxis
class PokemonOnShoulderRenderer<T : PlayerEntity>(renderLayerParent: FeatureRendererContext<T, PlayerEntityModel<T>>) : FeatureRenderer<T, PlayerEntityModel<T>>(renderLayerParent) {
val context = RenderContext().also {
it.put(RenderContext.RENDER_STATE, RenderContext.RenderState.WORLD)
}
var leftState = FloatingState()
var lastRenderedLeft: ShoulderData? = null
var rightState = FloatingState()
var lastRenderedRight: ShoulderData? = null
override fun render(
matrixStack: MatrixStack,
buffer: VertexConsumerProvider,
@@ -50,6 +62,14 @@ class PokemonOnShoulderRenderer<T : PlayerEntity>(renderLayerParent: FeatureRend
this.render(matrixStack, buffer, packedLight, livingEntity, limbSwing, limbSwingAmount, partialTicks, ageInTicks, netHeadYaw, headPitch, false)
}
fun makeState(model: PosableModel, aspects: Set<String>, leftShoulder: Boolean): FloatingState {
val state = FloatingState()
state.currentModel = model
state.currentAspects = aspects
state.setPoseToFirstSuitable(if (leftShoulder) PoseType.SHOULDER_LEFT else PoseType.SHOULDER_RIGHT)
return state
}
private fun render(
matrixStack: MatrixStack,
buffer: VertexConsumerProvider,
@@ -82,6 +102,10 @@ class PokemonOnShoulderRenderer<T : PlayerEntity>(renderLayerParent: FeatureRend
// Could be null
shoulderData = (if (pLeftShoulder) cache.lastKnownLeft else cache.lastKnownRight) ?: return
}
val model = PokemonModelRepository.getPoser(shoulderData.species.resourceIdentifier, shoulderData.aspects)
context.put(RenderContext.SPECIES, shoulderData.species.resourceIdentifier)
context.put(RenderContext.ASPECTS, shoulderData.aspects)
val scale = shoulderData.form.baseScale * shoulderData.scaleModifier
val width = shoulderData.form.hitbox.width
val offset = width / 2 - 0.7
@@ -100,18 +124,24 @@ class PokemonOnShoulderRenderer<T : PlayerEntity>(renderLayerParent: FeatureRend
matrixStack.scale(scale, scale, scale)
val model = PokemonModelRepository.getPoser(shoulderData.species.resourceIdentifier, shoulderData.aspects)
val state = PokemonFloatingState()
state.updatePartialTicks(ageInTicks + partialTicks)
val vertexConsumer = buffer.getBuffer(model.getLayer(PokemonModelRepository.getTexture(shoulderData.species.resourceIdentifier, shoulderData.aspects, state.animationSeconds)))
val state = if (pLeftShoulder && shoulderData != lastRenderedLeft) {
leftState = makeState(model, shoulderData.aspects, true)
lastRenderedLeft = shoulderData
leftState
} else if (!pLeftShoulder && shoulderData != lastRenderedRight) {
rightState = makeState(model, shoulderData.aspects, false)
lastRenderedRight = shoulderData
rightState
} else {
if (pLeftShoulder) leftState else rightState
}
state.updatePartialTicks(partialTicks)
context.put(RenderContext.POSABLE_STATE, state)
state.currentModel = model
val vertexConsumer = buffer.getBuffer(RenderLayer.getEntityCutout(PokemonModelRepository.getTexture(shoulderData.species.resourceIdentifier, shoulderData.aspects, state.animationSeconds)))
val i = LivingEntityRenderer.getOverlay(livingEntity, 0.0f)
val pose = model.poses.values
.firstOrNull { (if (pLeftShoulder) PoseType.SHOULDER_LEFT else PoseType.SHOULDER_RIGHT) in it.poseTypes }
?: model.poses.values.first()
state.setPose(pose.poseName)
state.timeEnteredPose = 0F
model.setupAnimStateful(
model.applyAnimations(
entity = null,
state = state,
headYaw = netHeadYaw,
@@ -120,9 +150,9 @@ class PokemonOnShoulderRenderer<T : PlayerEntity>(renderLayerParent: FeatureRend
limbSwingAmount = limbSwingAmount,
ageInTicks = livingEntity.age.toFloat()
)
model.render(matrixStack, vertexConsumer, packedLight, i, 1.0f, 1.0f, 1.0f, 1.0f)
model.render(context, matrixStack, vertexConsumer, packedLight, i, 1.0f, 1.0f, 1.0f, 1.0f)
model.withLayerContext(buffer, state, PokemonModelRepository.getLayers(shoulderData.species.resourceIdentifier, shoulderData.aspects)) {
model.render(matrixStack, vertexConsumer, packedLight, OverlayTexture.DEFAULT_UV, 1F, 1F, 1F, 1F)
model.render(context, matrixStack, vertexConsumer, packedLight, OverlayTexture.DEFAULT_UV, 1F, 1F, 1F, 1F)
}
model.setDefault()
matrixStack.pop()
@@ -6,20 +6,20 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon
package com.cobblemon.mod.common.client.render.models.blockbench
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.animation.StatefulAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.StatelessAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.ModelFrame
import net.minecraft.entity.Entity
import com.cobblemon.mod.common.client.render.models.blockbench.animation.ActiveAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PoseAnimation
/**
* Responsible for creating an animation instance from animation reference strings. In the Bedrock format these often
* look like "bedrock(pokemon_name, animation_name)" which searches an animation registry for data.
*
* This is essentially legacy code as MoLang integration in JSON posers is a much more effective framework for custom
* animation generation/referencing.
*/
@Deprecated("Use MoLang expressions in JSON posers instead")
interface AnimationReferenceFactory {
fun <T : Entity> stateless(model: PoseableEntityModel<T>, animString: String): StatelessAnimation<T, ModelFrame>
fun <T : Entity> stateful(model: PoseableEntityModel<T>, animString: String): StatefulAnimation<T, ModelFrame>
fun pose(model: PosableModel, animString: String): PoseAnimation
fun active(model: PosableModel, animString: String): ActiveAnimation
}
@@ -8,14 +8,9 @@
package com.cobblemon.mod.common.client.render.models.blockbench
import com.cobblemon.mod.common.client.render.models.blockbench.animation.StatefulAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.StatelessAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.ActiveAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PoseAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockAnimationRepository
import com.cobblemon.mod.common.client.render.models.blockbench.frame.ModelFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.AnimationReferenceFactory
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.JsonPokemonPoseableModel
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.entity.Entity
/**
* An [AnimationReferenceFactory] that loads Bedrock-format animations from [BedrockAnimationRepository].
@@ -26,12 +21,12 @@ import net.minecraft.entity.Entity
* @since June 28th, 2023
*/
object BedrockAnimationReferenceFactory : AnimationReferenceFactory {
override fun <T : Entity> stateless(model: PoseableEntityModel<T>, animString: String): StatelessAnimation<T, ModelFrame> {
override fun pose(model: PosableModel, animString: String): PoseAnimation {
val split = animString.replace("bedrock(", "").replace(")", "").split(",").map(String::trim)
return model.bedrock(animationGroup = split[0], animation = split[1])
}
override fun <T : Entity> stateful(model: PoseableEntityModel<T>, animString: String, ): StatefulAnimation<T, ModelFrame> {
override fun active(model: PosableModel, animString: String, ): ActiveAnimation {
val split = animString.replace("bedrock(", "").replace(")", "").split(",").map(String::trim)
return model.bedrockStateful(
animationGroup = split[0],
@@ -1,19 +0,0 @@
/*
* 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.render.models.blockbench
import net.minecraft.client.model.ModelPart
/**
* An ear joint which focuses on a specific part and can only be rotated along a specific axis and range of motion.
*
* @author Hiroku
* @since December 5th, 2021
*/
class EarJoint(val modelPart: ModelPart, val axis: Int, val rangeOfMotion: RangeOfMotion)
@@ -8,4 +8,5 @@
package com.cobblemon.mod.common.client.render.models.blockbench
/** A wrapping object so MoLang can return an ObjectValue of this type and have instance checks work. */
class ExcludedLabels(val labels: List<String>)
@@ -6,26 +6,24 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon
package com.cobblemon.mod.common.client.render.models.blockbench
import com.cobblemon.mod.common.api.scheduling.ClientTaskTracker
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
/**
* A [PoseableEntityState] that exists even without an entity or age-based state. This is
* A [PosableState] that exists even without an entity or age-based state. This is
* for when a Pokémon needs to be continuously animated from GUIs and there isn't an entity
* to attach state to. It has no age, and updating the partial ticks will increment rather
* than replace.
*
* What to look out for: Don't run updatePartialTicks multiple times in a single frame. For
* other [PoseableEntityState] this is fine as it just replaces what was there before, but
* other [PosableState] this is fine as it just replaces what was there before, but
* since this is incremental it would actually mess up the animation speeds.
*
* @author Hiroku
* @since May 1st, 2022
*/
class PokemonFloatingState : PoseableEntityState<PokemonEntity>() {
class FloatingState : PosableState() {
override fun getEntity() = null
override val schedulingTracker = ClientTaskTracker
override fun updatePartialTicks(partialTicks: Float) {
@@ -0,0 +1,31 @@
/*
* 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.render.models.blockbench
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Bone
import com.google.gson.InstanceCreator
import java.lang.reflect.Type
/**
* An instance creator for a [PosableModel] that allows for the model to be constructed from a JSON object. This
* is used to maintain a reference to the model so that other type adapters can call back to it.
*
* @author Hiroku
* @since October 18th, 2022
*/
class JsonModelAdapter<T : PosableModel>(private val constructor: (Bone) -> T) : InstanceCreator<T> {
var modelPart: Bone? = null
var model: T? = null
override fun createInstance(type: Type): T {
return constructor(modelPart!!).also {
model = it
it.loadAllNamedChildren(modelPart!!)
}
}
}
@@ -11,12 +11,13 @@ package com.cobblemon.mod.common.client.render.models.blockbench
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
import com.cobblemon.mod.common.client.render.models.blockbench.animation.StatefulAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.StatelessAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.ActiveAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PoseAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.SingleBoneLookAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.client.render.models.blockbench.quirk.SimpleQuirk
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.util.asExpressionLike
@@ -27,14 +28,28 @@ import com.cobblemon.mod.common.util.singularToPluralList
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import net.minecraft.entity.Entity
import net.minecraft.util.math.Vec3d
class JsonPose<T : Entity>(model: PoseableEntityModel<T>, json: JsonObject) {
class JsonPoseTransition(val from: String, val to: String, val animation: ExpressionLike)
/**
* A contained mechanism for extracting the relevant properties from a JSON object and later compose it
* into a [Pose] object.
*
* @author Hiroku
* @since May 28th, 2023
*/
class JsonPose(model: PosableModel, json: JsonObject) {
companion object {
fun registerAnimationFactory(id: String, factory: AnimationReferenceFactory) {
ANIMATION_FACTORIES[id] = factory
}
val ANIMATION_FACTORIES = mutableMapOf<String, AnimationReferenceFactory>()
}
class JsonPoseTransition(val to: String, val animation: ExpressionLike)
val runtime = MoLangRuntime().setup().setupClient().also {
it.environment.getQueryStruct().addFunctions(model.functions.functions)
it.environment.query.addFunctions(model.functions.functions)
}
val condition: ExpressionLike = json.singularToPluralList("condition").get("conditions")?.normalizeToArray()?.map { it.asString }?.asExpressionLike() ?: "true".asExpressionLike()
@@ -51,51 +66,69 @@ class JsonPose<T : Entity>(model: PoseableEntityModel<T>, json: JsonObject) {
val part = model.getPart(partName).createTransformation()
val rotation = it.get("rotation")?.asJsonArray?.let { Vec3d(it[0].asDouble, it[1].asDouble, it[2].asDouble) } ?: Vec3d.ZERO
val position = it.get("position")?.asJsonArray?.let { Vec3d(it[0].asDouble, it[1].asDouble, it[2].asDouble) } ?: Vec3d.ZERO
val isVisible = it.get("isVisible")?.asBoolean ?: true
return@map part.withPosition(position.x, position.y, position.z).withRotationDegrees(rotation.x, rotation.y, rotation.z).withVisibility(isVisible)
val isVisible = it.get("isVisible")?.asString?.asExpressionLike()
return@map part.withPosition(position.x, position.y, position.z).withRotationDegrees(rotation.x, rotation.y, rotation.z).also { if (isVisible != null) it.withVisibility(isVisible) }
}?.toTypedArray() ?: arrayOf()
val idleAnimations = (json.get("animations")?.asJsonArray ?: JsonArray()).asJsonArray.mapNotNull {
val animString = it.asString
if (animString == "look") {
return@mapNotNull if (model is HeadedFrame) {
model.singleBoneLook<T>()
val condition = if (it is JsonObject) {
it.get("condition")?.asString?.asExpressionLike() ?: "true".asExpressionLike()
} else {
"true".asExpressionLike()
}
val animString = if (it is JsonObject) {
it.get("animation")?.asString ?: return@mapNotNull null
} else {
it.asString
}
val animation = if (animString == "look") {
if (model is HeadedFrame) {
model.singleBoneLook()
} else {
object : HeadedFrame {
override val rootPart = model.rootPart
override val head = model.getPartFallback("head_ai", "head")
}.singleBoneLook()
SingleBoneLookAnimation(bone = model.relevantPartsByName["head_ai"] ?: model.relevantPartsByName["head"])
}
} else if (animString.startsWith("bedrock")) {
val split = animString.replace("bedrock(", "").replace(")", "").split(",").map(String::trim)
return@mapNotNull model.bedrock(animationGroup = split[0], animation = split[1])
} else {
try {
val expression = animString.asExpressionLike()
return@mapNotNull runtime.resolveObject(expression).obj as StatelessAnimation<T, *>
runtime.resolveObject(expression).obj as PoseAnimation
} catch (exception: Exception) {
null
val animString = it.asString
val anim = animString.substringBefore("(")
if (ANIMATION_FACTORIES.contains(anim)) {
ANIMATION_FACTORIES[anim]!!.pose(model, animString)
} else {
null
}
}
}
return@mapNotNull null
if (animation == null) {
null
} else {
animation.condition = { it.runtime.resolveBoolean(condition) }
animation
}
}.toTypedArray()
val quirks = (json.get("quirks")?.asJsonArray ?: JsonArray()).map { json ->
if (json is JsonPrimitive) {
return@map json.asString.asExpressionLike().resolveObject(runtime).obj as SimpleQuirk<T>
return@map json.asString.asExpressionLike().resolveObject(runtime).obj as SimpleQuirk
}
json as JsonObject
json.singularToPluralList("animation")
val animations: (state: PoseableEntityState<T>) -> List<StatefulAnimation<T, *>> = { _ ->
(json.get("animations")?.normalizeToArray()?.asJsonArray ?: JsonArray()).map { animJson ->
val animations: (state: PosableState) -> List<ActiveAnimation> = { _ ->
// Animations can be as MoLang expression strings or, legacy, shit like bedrock(something, else)
(json.get("animations")?.normalizeToArray()?.asJsonArray ?: JsonArray()).mapNotNull { animJson ->
try {
val expr = animJson.asString.asExpressionLike()
runtime.resolveObject(expr).obj as StatefulAnimation<T, *>
runtime.resolveObject(expr).obj as ActiveAnimation
} catch (e: Exception) {
val split =
animJson.asString.replace("bedrock(", "").replace(")", "").split(",").map(String::trim)
model.bedrockStateful(animationGroup = split[0], animation = split[1])
val animString = animJson.asString
val anim = animString.substringBefore("(")
return@mapNotNull ANIMATION_FACTORIES[anim]?.active(model, animString)
}
}
}
@@ -121,11 +154,7 @@ class JsonPose<T : Entity>(model: PoseableEntityModel<T>, json: JsonObject) {
map
} ?: mutableMapOf()
val transitions = json.get("transitions")?.takeIf { it is JsonArray }?.asJsonArray?.map {
it as JsonObject
val from = it.get("from").asString
val to = it.get("to").asString
val animation = it.get("animation").asString.asExpressionLike()
JsonPoseTransition(from, to, animation)
val transitions = json.get("transitions")?.takeIf { it is JsonObject }?.asJsonObject?.entrySet()?.map { (key, value) ->
JsonPoseTransition(key, value.asString.asExpressionLike())
} ?: emptyList()
}
@@ -1,125 +0,0 @@
/*
* 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.render.models.blockbench
import com.bedrockk.molang.MoLang
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.client.render.models.blockbench.animation.StatefulAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.ModelFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Bone
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.Poseable
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.asExpressionLike
import com.google.gson.ExclusionStrategy
import com.google.gson.FieldAttributes
import com.google.gson.InstanceCreator
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import java.lang.reflect.Type
import java.util.function.Supplier
import net.minecraft.client.model.ModelPart
import net.minecraft.entity.Entity
abstract class JsonPoseableEntityModel<T : Entity>(override val rootPart: Bone) : PoseableEntityModel<T>() {
override fun registerPoses() {}
object JsonModelExclusion: ExclusionStrategy {
override fun shouldSkipField(f: FieldAttributes): Boolean {
return f.declaringClass.simpleName !in listOf(
"JsonPokemonPoseableModel",
"JsonGenericPoseableModel",
"PoseableEntityModel",
"Pose"
)
}
override fun shouldSkipClass(clazz: Class<*>): Boolean {
return false
}
}
class JsonPoseableModelAdapter<T : Entity>(val constructor: (modelPart: ModelPart) -> JsonPoseableEntityModel<T>) : InstanceCreator<PoseableEntityModel<T>> {
var modelPart: ModelPart? = null
var model: JsonPoseableEntityModel<T>? = null
override fun createInstance(type: Type): JsonPoseableEntityModel<T> {
return constructor(modelPart!!).also {
model = it
it.loadAllNamedChildren(modelPart!!)
}
}
}
class StatefulAnimationAdapter<T : Entity>(val modelFinder: () -> PoseableEntityModel<T>) : JsonDeserializer<Supplier<StatefulAnimation<T, ModelFrame>>> {
override fun deserialize(json: JsonElement, type: Type, ctx: JsonDeserializationContext): Supplier<StatefulAnimation<T, ModelFrame>> {
json as JsonPrimitive
val animString = json.asString
val splits = animString.replace("bedrock(", "").replace(")", "").split(",").map(String::trim)
val file = splits[0]
val animation = splits[1]
return Supplier { modelFinder().bedrockStateful(file, animation) }
}
}
class PoseAdapter<T : Entity>(
val poseConditionReader: (JsonObject) -> List<(T) -> Boolean>,
val modelFinder: () -> PoseableEntityModel<T>
) : JsonDeserializer<Pose<T, ModelFrame>> {
companion object {
val runtime = MoLangRuntime()
}
override fun deserialize(json: JsonElement, type: Type, ctx: JsonDeserializationContext): Pose<T, ModelFrame> {
val model = modelFinder()
val obj = json as JsonObject
val pose = JsonPose(model, obj)
val conditionsList = mutableListOf<(T) -> Boolean>()
val mustBeTouchingWater = json.get("isTouchingWater")?.asBoolean
if (mustBeTouchingWater != null) {
conditionsList.add { mustBeTouchingWater == it.isTouchingWater }
}
conditionsList.addAll(poseConditionReader(json))
if (json.has("condition")) {
val condition = json.get("condition").asString
conditionsList.add {
if (it is Poseable) {
runtime.environment.structs["query"] = it.struct
condition.asExpressionLike().resolveBoolean(runtime)
} else {
false
}
}
}
val poseCondition: (T) -> Boolean = if (conditionsList.isEmpty()) { { true } } else conditionsList.reduce { acc, function -> { acc(it) && function(it) } }
return Pose(
poseName = pose.poseName,
poseTypes = pose.poseTypes.toSet(),
condition = poseCondition,
transformTicks = pose.transformTicks,
idleAnimations = pose.idleAnimations,
transformedParts = pose.transformedParts,
quirks = pose.quirks.toTypedArray()
).also {
it.transitions.putAll(
pose.transitions
.mapNotNull<JsonPose.JsonPoseTransition, Pair<String, (Pose<T, out ModelFrame>, Pose<T, out ModelFrame>) -> StatefulAnimation<T, ModelFrame>>> {
it.to to { _, _ -> it.animation.resolveObject(model.runtime).obj as StatefulAnimation<T, ModelFrame> }
}.toMap()
)
}
}
}
}
@@ -0,0 +1,938 @@
/*
* 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.render.models.blockbench
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.struct.ArrayStruct
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.client.render.ModelLayer
import com.cobblemon.mod.common.client.render.models.blockbench.animation.*
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockActiveAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockAnimationRepository
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockPoseAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.ModelFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Bone
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.client.render.models.blockbench.quirk.ModelQuirk
import com.cobblemon.mod.common.client.render.models.blockbench.quirk.SimpleQuirk
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunction
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.sineFunction
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.generic.GenericBedrockEntity
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.getDoubleOrNull
import com.cobblemon.mod.common.util.getStringOrNull
import com.cobblemon.mod.common.util.plus
import net.minecraft.client.model.ModelPart
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.RenderPhase
import net.minecraft.client.render.VertexConsumer
import net.minecraft.client.render.VertexConsumerProvider
import net.minecraft.client.render.VertexFormat
import net.minecraft.client.render.VertexFormats
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.entity.Entity
import net.minecraft.util.Identifier
import net.minecraft.util.math.RotationAxis
import net.minecraft.util.math.Vec3d
/**
* A model that can be posed and animated using [PoseAnimation]s and [ActiveAnimation]s. This
* requires poses to be registered and should implement any [ModelFrame] interfaces that apply to this
* model. This contains vast quantities of information about quirks, named animation references, locators,
* portrait and profile translation and scaling, and default part positions.
*
* This is a singleton for a specific model. For example, an NPC with an unusually large head would be one
* instance of this even if 100 NPCs are spawned with that model.
*
* Instantiations of a PosableModel can be the result of explicit coded subclasses or from JSON files.
*
* @author Hiroku
* @since December 5th, 2021
*/
open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
@Transient
lateinit var context: RenderContext
/** Whether the renderer that will process this is going to do the weird -1.5 Y offset bullshit that the living entity renderer does. */
@Transient
open var isForLivingEntityRenderer = true
var poses = mutableMapOf<String, Pose>()
/** A way to view the definition of all the different locators that are registered for the model. */
@Transient
lateinit var locatorAccess: LocatorAccess
open var portraitScale = 1F
open var portraitTranslation = Vec3d(0.0, 0.0, 0.0)
open var profileScale = 1F
open var profileTranslation = Vec3d(0.0, 0.0, 0.0)
var red = 1F
var green = 1F
var blue = 1F
var alpha = 1F
/** Named animations that can be referenced in generic named ways. This is different from the [PosableState] that stores the active animations. */
val animations = mutableMapOf<String, ExpressionLike>()
/** The definition of quirks that are possible for the model. This is different from the [PosableState] that stores the active quirk animations. */
val quirks = mutableListOf<ModelQuirk<*>>()
/**
* A list of [ModelPartTransformation] that record the original joint positions, rotations, and scales of the model.
* This allows the original state to be reset between renders.
*/
@Transient
val defaultPositions = mutableListOf<ModelPartTransformation>()
/**
* The named model parts that we expect will get modified. These are tracked so that they can be reset to their
* original positions at the end of each render (reset using [setDefault]).
*/
@Transient
val relevantPartsByName = mutableMapOf<String, ModelPart>()
/** Legacy faint code. */
open fun getFaintAnimation(state: PosableState): ActiveAnimation? = null
/** Legacy eating code. */
open fun getEatAnimation(state: PosableState): ActiveAnimation? = null
/** Legacy cry code. */
@Transient
open val cryAnimation: CryProvider = CryProvider { null }
@Transient
var currentLayers: Iterable<ModelLayer> = listOf()
@Transient
var bufferProvider: VertexConsumerProvider? = null
@Transient
var currentState: PosableState? = null
@Transient
val functions = QueryStruct(hashMapOf())
.addFunction("exclude_labels") { params ->
val labels = params.params.map { it.asString() }
return@addFunction ObjectValue(ExcludedLabels(labels))
}
.addFunction("bedrock_primary") { params ->
val group = params.getString(0)
val animation = params.getString(1)
val anim = bedrockStateful(group, animation)
val excludedLabels = mutableSetOf<String>()
var curve: WaveFunction = { t ->
if (t < 0.1) {
t * 10
} else if (t < 0.9) {
1F
} else {
1F
}
}
for (index in 2 until params.params.size) {
val param = params.get<MoValue>(index)
if (param is ObjectValue<*>) {
val obj = param.obj
if (obj is ExcludedLabels) {
excludedLabels.addAll(obj.labels)
} else {
curve = param.obj as WaveFunction
}
continue
}
val label = params.getString(index) ?: continue
excludedLabels.add(label)
}
return@addFunction ObjectValue(
PrimaryAnimation(
animation = anim,
excludedLabels = excludedLabels,
curve = curve
)
)
}
.addFunction("bedrock_stateful") { params ->
val group = params.getString(0)
val animation = params.getString(1)
val anim = bedrockStateful(group, animation)
return@addFunction ObjectValue(anim)
}
.addFunction("bedrock") { params ->
val group = params.getString(0)
val animation = params.getString(1)
val anim = bedrock(group, animation)
return@addFunction ObjectValue(anim)
}
.addFunction("look") { params ->
val boneName = params.getString(0)
val pitchMultiplier = params.getDoubleOrNull(1) ?: 1F
val yawMultiplier = params.getDoubleOrNull(2) ?: 1F
val maxPitch = params.getDoubleOrNull(3) ?: 70F
val minPitch = params.getDoubleOrNull(4) ?: -45F
val maxYaw = params.getDoubleOrNull(5) ?: 45F
ObjectValue(
SingleBoneLookAnimation(
bone = getPart(boneName),
pitchMultiplier = pitchMultiplier.toFloat(),
yawMultiplier = yawMultiplier.toFloat(),
maxPitch = maxPitch.toFloat(),
minPitch = minPitch.toFloat(),
maxYaw = maxYaw.toFloat()
)
)
}
.addFunction("quadruped_walk") { params ->
val periodMultiplier = params.getDoubleOrNull(0) ?: 0.6662F
val amplitudeMultiplier = params.getDoubleOrNull(1) ?: 1.4F
val leftFrontLeftName = params.getStringOrNull(2) ?: "leg_front_left"
val leftFrontRightName = params.getStringOrNull(3) ?: "leg_front_right"
val leftBackLeftName = params.getStringOrNull(4) ?: "leg_back_left"
val leftBackRightName = params.getStringOrNull(5) ?: "leg_back_right"
ObjectValue(
QuadrupedWalkAnimation(
periodMultiplier = periodMultiplier.toFloat(),
amplitudeMultiplier = amplitudeMultiplier.toFloat(),
legFrontLeft = this.getPart(leftFrontLeftName),
legFrontRight = this.getPart(leftFrontRightName),
legBackLeft = this.getPart(leftBackLeftName),
legBackRight = this.getPart(leftBackRightName)
)
)
}
.addFunction("biped_walk") { params ->
val periodMultiplier = params.getDoubleOrNull(0) ?: 0.6662F
val amplitudeMultiplier = params.getDoubleOrNull(1) ?: 1.4F
val leftLegName = params.getStringOrNull(2) ?: "leg_left"
val rightLegName = params.getStringOrNull(3) ?: "leg_right"
ObjectValue(
BipedWalkAnimation(
periodMultiplier = periodMultiplier.toFloat(),
amplitudeMultiplier = amplitudeMultiplier.toFloat(),
leftLeg = this.getPart(leftLegName),
rightLeg = this.getPart(rightLegName)
)
)
}
.addFunction("bimanual_swing") { params ->
val swingPeriodMultiplier = params.getDoubleOrNull(0) ?: 0.6662F
val amplitudeMultiplier = params.getDoubleOrNull(1) ?: 1F
val leftArmName = params.getStringOrNull(2) ?: "arm_left"
val rightArmName = params.getStringOrNull(3) ?: "arm_right"
ObjectValue(
BimanualSwingAnimation(
swingPeriodMultiplier = swingPeriodMultiplier.toFloat(),
amplitudeMultiplier = amplitudeMultiplier.toFloat(),
leftArm = this.getPart(leftArmName),
rightArm = this.getPart(rightArmName)
)
)
}
.addFunction("sine_wing_flap") { params ->
// verticalShift = -14F.toRadians(), period = 0.9F, amplitude = 0.9F
val amplitude = params.getDoubleOrNull(0) ?: 0.9F
val period = params.getDoubleOrNull(1) ?: 0.9F
val verticalShift = params.getDoubleOrNull(2) ?: 0F
val axis = params.getStringOrNull(3) ?: "y"
val axisIndex = when (axis) {
"x" -> ModelPartTransformation.X_AXIS
"y" -> ModelPartTransformation.Y_AXIS
"z" -> ModelPartTransformation.Z_AXIS
else -> ModelPartTransformation.Y_AXIS
}
val wingLeft = params.getStringOrNull(4) ?: "wing_left"
val wingRight = params.getStringOrNull(5) ?: "wing_right"
ObjectValue(
WingFlapIdleAnimation(
rotation = sineFunction(
verticalShift = verticalShift.toFloat(),
period = period.toFloat(),
amplitude = amplitude.toFloat()
),
axis = axisIndex,
leftWing = this.getPart(wingLeft),
rightWing = this.getPart(wingRight)
)
)
}
.addFunction("bedrock_quirk") { params ->
val animationGroup = params.getString(0)
val animationNames = params.get<MoValue>(1)
?.let { if (it is ArrayStruct) it.map.values.map { it.asString() } else listOf(it.asString()) }
?: listOf()
val minSeconds = params.getDoubleOrNull(2) ?: 8F
val maxSeconds = params.getDoubleOrNull(3) ?: 30F
val loopTimes = params.getDoubleOrNull(4)?.toInt() ?: 1
ObjectValue(
quirk(
secondsBetweenOccurrences = minSeconds.toFloat() to maxSeconds.toFloat(),
condition = { true },
loopTimes = 1..loopTimes,
animation = { bedrockStateful(animationGroup, animationNames.random()) }
)
)
}
.addFunction("bedrock_primary_quirk") { params ->
val animationGroup = params.getString(0)
val animationNames = params.get<MoValue>(1)?.let { if (it is ArrayStruct) it.map.values.map { it.asString() } else listOf(it.asString()) } ?: listOf()
val minSeconds = params.getDoubleOrNull(2) ?: 8F
val maxSeconds = params.getDoubleOrNull(3) ?: 30F
val loopTimes = params.getDoubleOrNull(4)?.toInt() ?: 1
val excludedLabels = mutableSetOf<String>()
var curve: WaveFunction = { t ->
if (t < 0.1) {
t * 10
} else if (t < 0.9) {
1F
} else {
1F
}
}
for (index in 5 until params.params.size) {
val param = params.get<MoValue>(index)
if (param is ObjectValue<*>) {
curve = param.obj as WaveFunction
continue
}
val label = params.getString(index) ?: continue
excludedLabels.add(label)
}
ObjectValue(
quirk(
secondsBetweenOccurrences = minSeconds.toFloat() to maxSeconds.toFloat(),
condition = { true },
loopTimes = 1..loopTimes,
animation = { PrimaryAnimation(bedrockStateful(animationGroup, animationNames.random()), excludedLabels = excludedLabels, curve = curve) }
)
)
}
@Transient
val runtime = MoLangRuntime().setup().setupClient().also { it.environment.query.addFunctions(functions.functions) }
/** Registers the different poses this model is capable of ahead of time. Should use [registerPose] religiously. */
open fun registerPoses() {}
/**
* Generates an active animation by name. This can be legacy-backed cry or faint animations, a prepared builder
* for an animation in the [animations] mapping, a product of MoLang in the name parameter, or a highly specific
* format used in [extractAnimation].
*
* First priority is given to any named animations inside of [Pose], and then to the [animations] mapping, before
* resorting to legacy, MoLang resolution, and finally the [extractAnimation] hail-Mary.
*/
fun getAnimation(state: PosableState, name: String, runtime: MoLangRuntime): ActiveAnimation? {
val poseAnimations = state.currentPose?.let(poses::get)?.namedAnimations ?: mapOf()
val animation = resolveFromAnimationMap(poseAnimations, name, runtime)
?: resolveFromAnimationMap(animations, name, runtime)
?: when (name) {
"cry" -> cryAnimation.invoke(state)
"faint" -> getFaintAnimation(state)
else -> {
try {
name.asExpressionLike().resolveObject(runtime).obj as ActiveAnimation
} catch (exception: Exception) {
extractAnimation(name)
}
}
}
return animation
}
/**
* Animation group : animation name [: primary]
* e.g. "particle_dummy:animation.particle_dummy.dragon_claw_target:primary"
* e.g. "particle_dummy:animation.particle.dummy.stat_up
*/
fun extractAnimation(string: String): ActiveAnimation? {
val group = string.substringBefore(":")
val animationName = string.substringAfter(":").substringBefore(":")
val isPrimary = string.endsWith(":primary")
if (animationName.isNotBlank() && animationName != string) {
val animation = BedrockAnimationRepository.tryGetAnimation(group, animationName) ?: return null
return if (isPrimary) {
PrimaryAnimation(BedrockActiveAnimation(animation))
} else {
BedrockActiveAnimation(animation)
}
} else {
return null
}
}
private fun resolveFromAnimationMap(
map: Map<String, ExpressionLike>,
name: String,
runtime: MoLangRuntime
): ActiveAnimation? {
val animationExpression = map[name] ?: return null
return try {
animationExpression.resolveObject(runtime).obj as ActiveAnimation
} catch (e: Exception) {
Cobblemon.LOGGER.error("Failed to create animation by name $name, most likely something wrong in the MoLang")
e.printStackTrace()
null
}
}
fun withLayerContext(
buffer: VertexConsumerProvider,
state: PosableState,
layers: Iterable<ModelLayer>,
action: () -> Unit
) {
setLayerContext(buffer, state, layers)
action()
resetLayerContext()
}
fun setLayerContext(buffer: VertexConsumerProvider, state: PosableState, layers: Iterable<ModelLayer>) {
currentLayers = layers
bufferProvider = buffer
currentState = state
}
fun resetLayerContext() {
currentLayers = emptyList()
bufferProvider = null
currentState = null
}
/**
* Registers a pose for this model.
*
* @param poseType The type of pose it is, as a [PoseType]
* @param condition The condition for this pose to apply
* @param animations The pose animations to use as idles unless a [ActiveAnimation] prevents it.
* @param transformedParts All the transformed forms of parts of the body that define this pose.
*/
fun registerPose(
poseType: PoseType,
condition: ((PosableState) -> Boolean)? = null,
transformTicks: Int = 10,
namedAnimations: MutableMap<String, ExpressionLike> = mutableMapOf(),
onTransitionedInto: (PosableState) -> Unit = {},
animations: Array<PoseAnimation> = emptyArray(),
transformedParts: Array<ModelPartTransformation> = emptyArray(),
quirks: Array<ModelQuirk<*>> = emptyArray()
): Pose {
return Pose(
poseType.name,
setOf(poseType),
condition,
onTransitionedInto,
transformTicks,
namedAnimations,
animations,
transformedParts,
quirks
).also {
poses[poseType.name] = it
}
}
fun registerPose(
poseName: String,
poseTypes: Set<PoseType>,
condition: ((PosableState) -> Boolean)? = null,
transformTicks: Int = 10,
namedAnimations: MutableMap<String, ExpressionLike> = mutableMapOf(),
onTransitionedInto: (PosableState) -> Unit = {},
animations: Array<PoseAnimation> = emptyArray(),
transformedParts: Array<ModelPartTransformation> = emptyArray(),
quirks: Array<ModelQuirk<*>> = emptyArray()
): Pose {
return Pose(
poseName,
poseTypes,
condition,
onTransitionedInto,
transformTicks,
namedAnimations,
animations,
transformedParts,
quirks
).also {
poses[poseName] = it
}
}
fun registerPose(
poseName: String,
poseType: PoseType,
condition: ((PosableState) -> Boolean)? = null,
transformTicks: Int = 10,
namedAnimations: MutableMap<String, ExpressionLike> = mutableMapOf(),
onTransitionedInto: (PosableState) -> Unit = {},
animations: Array<PoseAnimation> = emptyArray(),
transformedParts: Array<ModelPartTransformation> = emptyArray(),
quirks: Array<ModelQuirk<*>> = emptyArray()
): Pose {
return Pose(
poseName,
setOf(poseType),
condition,
onTransitionedInto,
transformTicks,
namedAnimations,
animations,
transformedParts,
quirks
).also {
poses[poseName] = it
}
}
/** Registers the same configuration for both left and right shoulder poses. */
fun registerShoulderPoses(
transformTicks: Int = 30,
animations: Array<PoseAnimation>,
transformedParts: Array<ModelPartTransformation> = emptyArray()
) {
registerPose(
poseType = PoseType.SHOULDER_LEFT,
transformTicks = transformTicks,
animations = animations,
transformedParts = transformedParts
)
registerPose(
poseType = PoseType.SHOULDER_RIGHT,
transformTicks = transformTicks,
animations = animations,
transformedParts = transformedParts
)
}
fun ModelPart.registerChildWithAllChildren(name: String): ModelPart {
val child = this.getChild(name)!!
registerRelevantPart(name to child)
loadAllNamedChildren(child)
return child
}
/** Builds the [locatorAccess] based on the given root part. */
fun initializeLocatorAccess() {
locatorAccess = LocatorAccess.resolve(rootPart) ?: LocatorAccess(rootPart)
}
fun getPart(name: String) = relevantPartsByName[name]!!
fun loadAllNamedChildren(bone: Bone) {
if (bone is ModelPart) loadAllNamedChildren(bone)
}
fun loadAllNamedChildren(modelPart: ModelPart) {
for ((name, child) in modelPart.children.entries) {
val default = ModelPartTransformation.derive(child)
relevantPartsByName[name] = child
defaultPositions.add(default)
loadAllNamedChildren(child)
}
}
fun registerRelevantPart(name: String, part: ModelPart): ModelPart {
val default = ModelPartTransformation.derive(part)
relevantPartsByName[name] = part
defaultPositions.add(default)
return part
}
fun registerRelevantPart(pairing: Pair<String, ModelPart>) = registerRelevantPart(pairing.first, pairing.second)
/** Renders the model. Assumes rotations have been set. Will simply render the base model and then any extra layers. */
fun render(
context: RenderContext,
stack: MatrixStack,
buffer: VertexConsumer,
packedLight: Int,
packedOverlay: Int,
r: Float,
g: Float,
b: Float,
a: Float
) {
rootPart.render(
context,
stack,
buffer,
packedLight,
packedOverlay,
red * r,
green * g,
blue * b,
alpha * a
)
val provider = bufferProvider
if (provider != null) {
for (layer in currentLayers) {
val texture = layer.texture?.invoke(currentState?.animationSeconds ?: 0F) ?: continue
val renderLayer = getLayer(texture, layer.emissive, layer.translucent)
val consumer = provider.getBuffer(renderLayer)
stack.push()
rootPart.render(
context,
stack,
consumer,
packedLight,
packedOverlay,
layer.tint.x,
layer.tint.y,
layer.tint.z,
layer.tint.w
)
stack.pop()
}
}
}
/** Generates a [RenderLayer] by the power of god and anime. Only possible thanks to 100 access wideners. */
fun makeLayer(texture: Identifier, emissive: Boolean, translucent: Boolean): RenderLayer {
val multiPhaseParameters: RenderLayer.MultiPhaseParameters = RenderLayer.MultiPhaseParameters.builder()
.program(
when {
emissive && translucent -> RenderPhase.ENTITY_TRANSLUCENT_EMISSIVE_PROGRAM
!emissive && translucent -> RenderPhase.ENTITY_TRANSLUCENT_PROGRAM
!emissive && !translucent -> RenderPhase.ENTITY_CUTOUT_PROGRAM
else -> RenderPhase.ENTITY_TRANSLUCENT_EMISSIVE_PROGRAM // This one should be changed to maybe a custom shader? Translucent stuffs with things
}
)
.texture(RenderPhase.Texture(texture, false, false))
.transparency(if (translucent) RenderPhase.TRANSLUCENT_TRANSPARENCY else RenderPhase.NO_TRANSPARENCY)
.cull(RenderPhase.ENABLE_CULLING)
.writeMaskState(RenderPhase.ALL_MASK)
.overlay(RenderPhase.ENABLE_OVERLAY_COLOR)
.build(false)
return RenderLayer.of(
"cobblemon_entity_layer",
VertexFormats.POSITION_COLOR_TEXTURE_OVERLAY_LIGHT_NORMAL,
VertexFormat.DrawMode.QUADS,
256,
true,
translucent,
multiPhaseParameters
)
}
/** Makes a [RenderLayer] in a jank way. Mostly works so that's cool. */
fun getLayer(texture: Identifier, emissive: Boolean, translucent: Boolean): RenderLayer {
return if (!emissive && !translucent) {
RenderLayer.getEntityCutout(texture)
} else if (!emissive) {
RenderLayer.getEntityTranslucent(texture)
} else {
makeLayer(texture, emissive = emissive, translucent = translucent)
}
}
/** Applies the given pose's [ModelPartTransformation]s to the model, if there is a matching pose. */
fun applyPose(state: PosableState, pose: Pose, intensity: Float) = pose.transformedParts.forEach { it.apply(state, intensity) }
/** Gets the first pose of this model that matches the given [PoseType]. */
fun getPose(pose: PoseType) = poses.values.firstOrNull { pose in it.poseTypes }
fun getPose(name: String) = poses[name]
/** Puts the model back to its original location and rotations. */
fun setDefault() = defaultPositions.forEach { it.set() }
/**
* Finds the first of the model's poses that the given state and optional [PoseType] is appropriate for.
* If none exists, return the first pose. Only possible with bad configuration, though.
*/
fun getFirstSuitablePose(state: PosableState, poseType: PoseType?): Pose {
return poses.values.firstOrNull { (poseType == null || poseType in it.poseTypes) && it.isSuitable(state) } ?: poses.values.first()
}
/**
* Validates that the current pose is valid for the state and model. If it isn't, it will attempt
* to find the most desirable pose and begin transitioning to it.
*
* @return the current pose that should be applied during rendering.
*/
fun validatePose(entity: PosableEntity?, state: PosableState): Pose {
val poseName = state.currentPose
val currentPose = poseName?.let(poses::get)
val entityPoseType = if (entity is PosableEntity) entity.getCurrentPoseType() else null
// Is there any reason why we should actually change the pose?
if (entity != null && (poseName == null || currentPose == null || !currentPose.isSuitable(state) || entityPoseType !in currentPose.poseTypes)) {
val desirablePose = getFirstSuitablePose(state, entityPoseType)
// If this if succeeds then it just no longer fits this pose
if (currentPose != null) {
// Don't apply pose correction until the current primary animation is complete.
if (state.primaryAnimation == null) {
moveToPose(state, desirablePose)
return desirablePose
}
} else {
state.setPose(desirablePose.poseName)
return desirablePose
}
} else if (currentPose == null) {
return poses.values.firstOrNull() ?: run {
throw IllegalStateException("Model has no poses: ${this::class.simpleName}")
}
}
return currentPose
}
/**
* Applies animations to the current model. This is the main entry point for rendering the model. There is a lot
* of logic here.
*/
fun applyAnimations(
entity: Entity?,
state: PosableState,
limbSwing: Float,
limbSwingAmount: Float,
ageInTicks: Float,
headYaw: Float,
headPitch: Float
) {
context.put(RenderContext.ENTITY, entity)
setupEntityTypeContext(entity)
state.currentModel = this
// Resets the model's joints back to their default positions.
setDefault()
// Applies any of the state's queued actions.
state.preRender()
// Performs a check that the current pose is correct and returns back which pose we should be applying. Even if
// a change of pose is necessary, if it's going to gradually transition there then we're still going to keep
// applying our current pose until that process is done.
val pose = validatePose(entity as? PosableEntity, state)
// Applies the pose's transformations to the model. This is not the animations.
applyPose(state, pose, 1F)
val primaryAnimation = state.primaryAnimation
// Quirks will run if there is no primary animation running and quirks are enabled for this context.
if (primaryAnimation == null && context.request(RenderContext.DO_QUIRKS) != false) {
// Remove any quirk animations that don't exist in our current pose
state.quirks.keys.filterNot(pose.quirks::contains).forEach(state.quirks::remove)
// Tick all the quirks
pose.quirks.forEach {
it.apply(context, this, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch, 1F)
}
}
if (primaryAnimation != null) {
// The pose intensity is the complement of the primary animation's curve. This is used to blend the primary.
state.poseIntensity = 1 - primaryAnimation.curve((state.animationSeconds - primaryAnimation.started) / primaryAnimation.duration)
// If the primary animation is done after running we're going to clean things up.
if (!primaryAnimation.run(
context,
this,
state,
limbSwing,
limbSwingAmount,
ageInTicks,
headYaw,
headPitch,
1 - state.poseIntensity
)
) {
primaryAnimation.afterAction.accept(Unit)
state.primaryAnimation = null
state.poseIntensity = 1F
}
}
// Run active animations and return back any that are done and can be removed.
val removedActiveAnimations = state.activeAnimations.toList()
.filterNot { it.run(context, this, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch, 1F) }
state.activeAnimations.removeAll(removedActiveAnimations)
// Applies the pose's animations.
state.currentPose?.let(poses::get)
?.apply(context, this, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch)
// Updates the locator positions now that all the animations are in effect. This is the last thing we do!
updateLocators(entity, state)
}
//This is used to set additional entity type specific context
open fun setupEntityTypeContext(entity: Entity?) {}
/**
* Attempts to move the given [state] to the [desirablePose], using transitions if possible. The logic for this
* can be a bit confusing. Returns what the current pose is, just in case it has instantly changed.
*/
fun moveToPose(state: PosableState, desirablePose: Pose): String {
// If we're currently in a pose that doesn't exist on this model, then shit, don't even try doing anything fancy.
val previousPose = state.currentPose?.let(poses::get) ?: run {
state.setPose(desirablePose.poseName)
return desirablePose.poseName
}
val desirablePoseType = desirablePose.poseTypes.first()
// If we're already running a transition animation then leave it be.
if (state.activeAnimations.none { it.isTransition }) {
// Check for a dedicated transition animation.
val transition = previousPose.transitions[desirablePose.poseName]
val animation = if (transition == null && previousPose.transformTicks > 0) {
// If no dedicated transition exists then use a simple interpolator.
PrimaryAnimation(
PoseTransitionAnimation(
beforePose = previousPose,
afterPose = desirablePose,
durationTicks = previousPose.transformTicks
),
curve = { 1F }
)
} else if (transition != null) {
// If we have a dedicated transition, run with that. If it isn't already a PrimaryAnimation then make it one.
var transitionAnimation = transition(previousPose, desirablePose)
if (transitionAnimation !is PrimaryAnimation) {
transitionAnimation = PrimaryAnimation(transitionAnimation, curve = { 1F })
}
transitionAnimation.isTransition = true
transitionAnimation
} else {
state.setPose(poses.values.first {
desirablePoseType in it.poseTypes && (it.condition == null || it.condition.invoke(state))
}.poseName)
return previousPose.poseName
}
// Set the primary animation to the transition. After the animation completes, directly set the pose since
// we're done. The afterAction can occur from render or from tick while off-screen, either will do.
state.addPrimaryAnimation(animation)
animation.afterAction += {
state.setPose(desirablePose.poseName)
if (state.primaryAnimation == animation) {
state.primaryAnimation = null
}
}
}
return previousPose.poseName
}
/**
* Figures out where all of this model's locators are in real space, so that they can be
* found and used from other client-side systems.
*/
fun updateLocators(entity: Entity?, state: PosableState) {
entity ?: return
val matrixStack = MatrixStack()
var scale = 1F
// We could improve this to be generalized for other entities. First we'd have to figure out wtf is going on, though.
if (entity is PokemonEntity) {
matrixStack.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(180 - entity.bodyYaw))
matrixStack.push()
matrixStack.scale(-1F, -1F, 1F)
scale = entity.pokemon.form.baseScale * entity.pokemon.scaleModifier * (entity.delegate as PokemonClientDelegate).entityScaleModifier
matrixStack.scale(scale, scale, scale)
} else if (entity is EmptyPokeBallEntity) {
matrixStack.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(entity.yaw))
matrixStack.push()
matrixStack.scale(1F, -1F, -1F)
scale = 0.7F
matrixStack.scale(scale, scale, scale)
} else if (entity is GenericBedrockEntity) {
matrixStack.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(entity.yaw))
matrixStack.push()
// Not 100% convinced we need the -1 on Y but if we needed it for the Poke Ball then probably?
matrixStack.scale(1F, -1F, 1F)
} else if (entity is NPCEntity) {
matrixStack.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(180 - entity.bodyYaw))
matrixStack.push()
matrixStack.scale(-1F, -1F, 1F)
}
if (isForLivingEntityRenderer) {
// Standard living entity offset, only God knows why Mojang did this.
matrixStack.translate(0.0, -1.5, 0.0)
}
locatorAccess.update(matrixStack, entity, scale, state.locatorStates, isRoot = true)
}
fun ModelPart.translation(
function: WaveFunction,
axis: Int,
timeVariable: (state: PosableState, limbSwing: Float, ageInTicks: Float) -> Float?
) = TranslationFunctionPoseAnimation(
part = this,
function = function,
axis = axis,
timeVariable = timeVariable
)
fun ModelPart.rotation(
function: WaveFunction,
axis: Int,
timeVariable: (state: PosableState, limbSwing: Float, ageInTicks: Float) -> Float?
) = RotationFunctionPoseAnimation(
part = this,
function = function,
axis = axis,
timeVariable = timeVariable
)
fun bedrock(
animationGroup: String,
animation: String,
animationPrefix: String = "animation.$animationGroup"
) = BedrockPoseAnimation(
BedrockAnimationRepository.getAnimation(animationGroup, "$animationPrefix.$animation")
)
fun bedrockStateful(
animationGroup: String,
animation: String,
animationPrefix: String = "animation.$animationGroup"
) = BedrockActiveAnimation(BedrockAnimationRepository.getAnimation(animationGroup, "$animationPrefix.$animation"))
fun quirk(
secondsBetweenOccurrences: Pair<Float, Float> = 8F to 30F,
loopTimes: IntRange = 1..1,
condition: (state: PosableState) -> Boolean = { true },
animation: (state: PosableState) -> ActiveAnimation
) = SimpleQuirk(
secondsBetweenOccurrences = secondsBetweenOccurrences,
loopTimes = loopTimes,
condition = condition,
animations = { listOf(animation(it)) }
)
fun quirkMultiple(
secondsBetweenOccurrences: Pair<Float, Float> = 8F to 30F,
loopTimes: IntRange = 1..1,
condition: (state: PosableState) -> Boolean = { true },
animations: (state: PosableState) -> List<ActiveAnimation>
) = SimpleQuirk(
secondsBetweenOccurrences = secondsBetweenOccurrences,
loopTimes = loopTimes,
condition = condition,
animations = { animations(it) }
)
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.client.render.models.blockbench
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.struct.ArrayStruct
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.struct.VariableStruct
import com.bedrockk.molang.runtime.value.DoubleValue
@@ -16,7 +17,6 @@ import com.bedrockk.molang.runtime.value.MoValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.scheduling.Schedulable
@@ -24,17 +24,17 @@ import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
import com.cobblemon.mod.common.client.particle.BedrockParticleEffectRepository
import com.cobblemon.mod.common.client.particle.ParticleStorm
import com.cobblemon.mod.common.client.render.MatrixWrapper
import com.cobblemon.mod.common.client.render.models.blockbench.animation.ActiveAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PoseAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PrimaryAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.StatefulAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.StatelessAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockActiveAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockParticleKeyframe
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockStatefulAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockStatelessAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.ModelFrame
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockPoseAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.client.render.models.blockbench.quirk.ModelQuirk
import com.cobblemon.mod.common.client.render.models.blockbench.quirk.QuirkData
import com.cobblemon.mod.common.entity.Poseable
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import java.util.concurrent.ConcurrentLinkedQueue
import net.minecraft.client.MinecraftClient
@@ -46,39 +46,90 @@ import net.minecraft.sound.SoundEvent
import net.minecraft.util.math.Vec3d
/**
* Represents the entity-specific state for a poseable model. The implementation is responsible for
* handling all the state for an entity's model, and needs to be conscious of the fact that the
* model may change without this state changing.
* Represents some kind of animation state for an entity or GUI element or other renderable component in the game.
* This state object is responsible for maintaining the current pose, current active animations, any quirks that
* are in effect, and the [MoLangRuntime] to use for its animations. These states can be effectively mocked using
* [FloatingState].
*
* This also handles setting up the molang runtime that animations have access to
* This class also represents a [Schedulable] frame of reference. The core contract of a PosableState is that it is
* going to have a central timing mechanism that is moving forward with ticking or rendering or both. This is seen
* with the [age] and [currentPartialTicks] properties. You don't necessarily have to use both as GUIs will rely
* entirely on partial ticks and endlessly increase them without resetting. [animationSeconds] is derived from the
* combination of these two properties.
*
* @author Hiroku
* @since December 5th, 2021
*/
abstract class PoseableEntityState<T : Entity> : Schedulable {
var currentModel: PoseableEntityModel<T>? = null
abstract class PosableState : Schedulable {
var currentModel: PosableModel? = null
set(value) {
val changed = field != value
field = value
if (value != null) {
val entity = getEntity() as? Poseable ?: return
if (value != null && changed) {
val entity = getEntity() as? PosableEntity ?: return
entity.struct.addFunctions(value.functions.functions)
runtime.environment.getQueryStruct().addFunctions(value.functions.functions)
runtime.environment.query.addFunctions(value.functions.functions)
// Locators need to be initialized asap, even if they aren't in perfect positions. The reason for this
// is that the locators might be called upon by frame 0 particle effects and if they aren't defined
// it'll crash. For non-entity states we don't give a shit though.
value.updateLocators(getEntity() ?: return, this)
}
}
/** The name of the current pose. */
var currentPose: String? = null
var primaryAnimation: PrimaryAnimation<T>? = null
val statefulAnimations: MutableList<StatefulAnimation<T, *>> = mutableListOf()
val quirks = mutableMapOf<ModelQuirk<T, *>, QuirkData<T>>()
/** A kind of cache for the current aspects being rendered. It's a bit sloppily maintained but necessary. */
var currentAspects: Set<String> = emptySet()
/** The currently active [PrimaryAnimation], if there is one. */
var primaryAnimation: PrimaryAnimation? = null
/** All of the current [ActiveAnimation]. */
val activeAnimations: MutableList<ActiveAnimation> = mutableListOf()
/** Any active [ModelQuirk]s and their [QuirkData]. */
val quirks = mutableMapOf<ModelQuirk<*>, QuirkData>()
/**
* Any particle effect keyframes that are generated from a [Pose] and area active right now. This prevents duplicates
* for animations that loop and have particle keyframes at some point in time on that loop.
*/
val poseParticles = mutableListOf<BedrockParticleKeyframe>()
private val reusableAnimTime = DoubleValue(0.0) // This gets called 500 million times so use a mutable value for runtime
/** A simple getter to pull together all non-pose animations that are non-primary. */
val allActiveAnimations: List<ActiveAnimation> get() = activeAnimations + quirks.flatMap { it.value.animations }
/** The tick-driven age of the state. Combines with [currentPartialTicks] to produce the animation seconds. */
protected var age = 0
/**
* The partial tick value of the state. Combines with [age] to produce the animation seconds. For entity-based state
* this will be consistently reset to a new value, whereas non-entity-based state will increment this value.
*
* See [FloatingState].
*/
protected var currentPartialTicks = 0F
/** The current intensity to be used for [Pose] animations. */
var poseIntensity = 1F
/** The derived animation seconds. It is [age] + [currentPartialTicks], converted to seconds. */
val animationSeconds: Float get() = (age + getPartialTicks()) / 20F
/**
* The current positions of all locators. The positions are stored as full [MatrixWrapper]s so it is not only the
* position but the local space.
*/
val locatorStates = mutableMapOf<String, MatrixWrapper>()
/** A set of actions that should occur soon, on the render thread. I doubt this will be needed long term. */
val renderQueue = ConcurrentLinkedQueue<() -> Unit>()
/** This gets called 500 million times so use a mutable value for runtime */
private val reusableAnimTime = DoubleValue(0.0)
/** All of the MoLang functions that can be applied to something with this state. */
val functions = QueryStruct(hashMapOf())
.addFunction("anim_time") {
reusableAnimTime.value = animationSeconds.toDouble()
reusableAnimTime
}
.addFunction("current_aspects") { ArrayStruct(currentAspects.mapIndexed { index, s -> "$index" to StringValue(s)}.toMap())}
.addFunction("has_aspect") { params -> DoubleValue(params.get<MoValue>(0).asString() in currentAspects) }
.addFunction("has_entity") { DoubleValue(getEntity() != null) }
.addFunction("pose") { StringValue(currentPose ?: "") }
.addFunction("sound") { params ->
@@ -98,15 +149,15 @@ abstract class PoseableEntityState<T : Entity> : Schedulable {
.addFunction("play_animation") { params ->
val animationParameter = params.get<MoValue>(0)
val animation = if (animationParameter is ObjectValue<*>) {
animationParameter.obj as BedrockStatefulAnimation<T>
animationParameter.obj as BedrockActiveAnimation
} else {
currentModel?.getAnimation(this, animationParameter.asString(), runtime)
}
if (animation != null) {
if (animation is PrimaryAnimation<T>) {
if (animation is PrimaryAnimation) {
addPrimaryAnimation(animation)
} else {
addStatefulAnimation(animation)
addActiveAnimation(animation)
}
}
return@addFunction Unit
@@ -133,7 +184,7 @@ abstract class PoseableEntityState<T : Entity> : Schedulable {
val matrixWrapper = locatorStates[locator] ?: locatorStates["root"]!!
val particleRuntime = MoLangRuntime().setup().setupClient()
particleRuntime.environment.getQueryStruct().addFunction("entity") { runtime.environment.getQueryStruct() }
particleRuntime.environment.query.addFunction("entity") { runtime.environment.query }
val storm = ParticleStorm(
effect = effect,
@@ -151,25 +202,28 @@ abstract class PoseableEntityState<T : Entity> : Schedulable {
}
val runtime: MoLangRuntime = MoLangRuntime().setup().setupClient().also {
it.environment.getQueryStruct().addFunctions(functions.functions)
it.environment.query.addFunctions(functions.functions)
}
val allStatefulAnimations: List<StatefulAnimation<T, *>> get() = statefulAnimations + quirks.flatMap { it.value.animations }
protected var age = 0
protected var currentPartialTicks = 0F
var primaryOverridePortion = 1F
abstract fun getEntity(): T?
/** Gets the entity related to this state if this state is actually attached to an entity. */
abstract fun getEntity(): Entity?
fun getPartialTicks() = currentPartialTicks
open fun updateAge(age: Int) {
this.age = age
}
open fun incrementAge(entity: T) {
/**
* Performs some ticking operation with an entity. This will apply a lot of logic that occurs each client tick
* and that needs an entity. This includes updating locators, checking for any primary animation expiry, applying
* particle effects for any active animation, and updating [age].
*/
open fun incrementAge(entity: Entity) {
val previousAge = age
updateAge(age + 1)
currentModel?.let {
updateLocatorPosition(entity.pos)
it.validatePose(entity as? PosableEntity, this)
}
runEffects(entity, previousAge, age)
val primaryAnimation = primaryAnimation ?: return
if (primaryAnimation.started + primaryAnimation.duration <= animationSeconds) {
@@ -178,19 +232,14 @@ abstract class PoseableEntityState<T : Entity> : Schedulable {
}
}
/** Decides how an update to partial ticks should be applied to the state. See [FloatingState] for how it could happen. */
abstract fun updatePartialTicks(partialTicks: Float)
/** Can be used to reset the animation timer for poses that don't transition nicely. Bit of an afterthought solution. */
open fun reset() {
updateAge(0)
}
val animationSeconds: Float get() = (age + getPartialTicks()) / 20F
var timeEnteredPose = 0F
val locatorStates = mutableMapOf<String, MatrixWrapper>()
val renderQueue = ConcurrentLinkedQueue<() -> Unit>()
/**
* Scans through the set of animations provided and begins playing the first one that is registered
* on the entity. The goal is to have most-specific animations first and more generic ones last, so
@@ -201,15 +250,15 @@ abstract class PoseableEntityState<T : Entity> : Schedulable {
fun addFirstAnimation(animation: Set<String>) {
val model = currentModel ?: return
val animation = animation.firstNotNullOfOrNull { model.getAnimation(this, it, runtime) } ?: return
if (animation is PrimaryAnimation<T>) {
if (animation is PrimaryAnimation) {
addPrimaryAnimation(animation)
} else {
addStatefulAnimation(animation)
addActiveAnimation(animation)
}
}
fun isPosedIn(vararg poses: Pose<T, in ModelFrame>) = poses.any { it.poseName == currentPose }
fun isNotPosedIn(vararg poses: Pose<T, in ModelFrame>) = poses.none { it.poseName == currentPose }
fun isPosedIn(vararg poses: Pose) = poses.any { it.poseName == currentPose }
fun isNotPosedIn(vararg poses: Pose) = poses.none { it.poseName == currentPose }
fun preRender() {
while (renderQueue.peek() != null) {
@@ -222,22 +271,37 @@ abstract class PoseableEntityState<T : Entity> : Schedulable {
renderQueue.offer(action)
}
fun getPose(): String? {
return currentPose
fun setPoseToFirstSuitable(poseType: PoseType? = null) {
val model = currentModel ?: return
val pose = model.getFirstSuitablePose(this, poseType)
if (pose.poseName == this.currentPose) {
return
}
setPose(pose.poseName)
}
/**
* Changes the current pose over to the one mentioned by name. This relies on [currentModel] being non-null.
*
* As part of this process there is some juggling to deal with particle effects so that if they exist on both
* pose's animations and the current pose's animations, they are not duplicated. This is done by removing any
* particle effects that are not unique to the old pose. We also immediately run the particle effects that are
* on the new pose and are set to run at the start of the animation.
*
* It's honestly quite jank but I'm not sure how Bedrock would even go about solving this.
*/
fun setPose(pose: String) {
currentPose = pose
primaryOverridePortion = 1F
poseIntensity = 1F
val model = currentModel
if (model != null) {
val poseImpl = model.getPose(pose) ?: return
poseParticles.removeIf { particle -> poseImpl.idleAnimations.filterIsInstance<BedrockStatelessAnimation<*>>().flatMap { it.particleKeyFrames }.none(particle::isSameAs) }
val poseImpl = model.poses[pose] ?: return
poseParticles.removeIf { particle -> poseImpl.animations.filterIsInstance<BedrockPoseAnimation>().flatMap { it.particleKeyFrames }.none(particle::isSameAs) }
poseImpl.onTransitionedInto(this)
val entity = getEntity()
if (entity != null) {
poseImpl.idleAnimations
.filterIsInstance<BedrockStatelessAnimation<*>>()
poseImpl.animations
.filterIsInstance<BedrockPoseAnimation>()
.flatMap { it.particleKeyFrames }
.filter { particle -> particle.seconds == 0F && poseParticles.none(particle::isSameAs) }
.forEach { it.run(entity, this) }
@@ -245,17 +309,20 @@ abstract class PoseableEntityState<T : Entity> : Schedulable {
}
}
fun setStatefulAnimations(vararg animations: StatefulAnimation<T, out ModelFrame>) {
statefulAnimations.clear()
statefulAnimations.addAll(animations)
fun setActiveAnimations(vararg animations: ActiveAnimation) {
activeAnimations.clear()
activeAnimations.addAll(animations)
}
/**
* Updates the base position of all the locators. Doesn't require the model.
*/
fun updateLocatorPosition(position: Vec3d) {
locatorStates.values.toList().forEach { it.updatePosition(position) }
}
fun addStatefulAnimation(animation: StatefulAnimation<T, *>, whenComplete: (state: PoseableEntityState<T>) -> Unit = {}) {
this.statefulAnimations.add(animation)
fun addActiveAnimation(animation: ActiveAnimation, whenComplete: (state: PosableState) -> Unit = {}) {
this.activeAnimations.add(animation)
val duration = animation.duration
if (duration > 0F) {
after(seconds = (duration * 20F).toInt() / 20F) {
@@ -264,39 +331,51 @@ abstract class PoseableEntityState<T : Entity> : Schedulable {
}
}
fun addPrimaryAnimation(primaryAnimation: PrimaryAnimation<T>) {
/**
* Sets the primary animation and dumps any active animations.
*/
fun addPrimaryAnimation(primaryAnimation: PrimaryAnimation) {
this.primaryAnimation = primaryAnimation
this.statefulAnimations.clear()
this.activeAnimations.clear()
this.quirks.clear()
this.primaryOverridePortion = 1F
this.poseIntensity = 1F
primaryAnimation.started = animationSeconds
}
fun runEffects(entity: T, previousAge: Int, newAge: Int) {
/**
* Runs any effects that are associated with the current state of the entity. This includes running effects for
* all active animations, the primary animation, and any pose animations that are relevant.
*/
fun runEffects(entity: Entity, previousAge: Int, newAge: Int) {
val previousSeconds = previousAge / 20F
val currentSeconds = newAge / 20F
currentModel?.let { model ->
val pose = currentPose?.let(model::getPose)
allStatefulAnimations.forEach { it.applyEffects(entity, this, previousSeconds, currentSeconds) }
val pose = currentPose?.let { model.poses[it] }
allActiveAnimations.forEach { it.applyEffects(entity, this, previousSeconds, currentSeconds) }
primaryAnimation?.animation?.applyEffects(entity, this, previousSeconds, currentSeconds)
pose?.idleAnimations?.filter { shouldIdleRun(it, 0.5F) }?.forEach { it.applyEffects(entity, this, previousSeconds, currentSeconds) }
// Effects start playing from pose animations as long as the intensity is above 0.5. Pretty sloppy honestly.
pose?.animations
?.filter { shouldIdleRun(it, 0.5F) && it.condition(this) }
?.forEach { it.applyEffects(entity, this, previousSeconds, currentSeconds) }
}
}
fun shouldIdleRun(idleAnimation: StatelessAnimation<T, *>, requiredIntensity: Float): Boolean {
/** Checks if the current primary animation interferes with the given pose animation and is below a threshold intensity. */
fun shouldIdleRun(poseAnimation: PoseAnimation, requiredIntensity: Float): Boolean {
val primaryAnimation = primaryAnimation
return if (primaryAnimation != null) {
!primaryAnimation.prevents(idleAnimation) || this.primaryOverridePortion > requiredIntensity
!primaryAnimation.prevents(poseAnimation) || this.poseIntensity > requiredIntensity
} else {
true
}
}
fun getIdleIntensity(idleAnimation: StatelessAnimation<T, *>): Float {
/** Gets the appropriate intensity for the given pose animation, given the current primary animation. */
fun getIdleIntensity(poseAnimation: PoseAnimation): Float {
val primaryAnimation = primaryAnimation
return if (primaryAnimation != null && primaryAnimation.prevents(idleAnimation)) {
this.primaryOverridePortion
return if (primaryAnimation != null && primaryAnimation.prevents(poseAnimation)) {
this.poseIntensity
} else {
1F
}
@@ -0,0 +1,110 @@
/*
* 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.render.models.blockbench
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.client.render.models.blockbench.animation.ActiveAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.isDusk
import com.cobblemon.mod.common.util.isStandingOnRedSand
import com.cobblemon.mod.common.util.isStandingOnSand
import com.cobblemon.mod.common.util.isStandingOnSandOrRedSand
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import java.lang.reflect.Type
/**
* An adapter for deserializing a [Pose] object from a JSON object.
*
* @author Hiroku
* @since October 18th, 2022
*/
class PoseAdapter(
val poseConditionReader: (JsonObject) -> List<(PosableState) -> Boolean>,
val modelFinder: () -> PosableModel
) : JsonDeserializer<Pose> {
companion object {
val runtime = MoLangRuntime()
}
override fun deserialize(json: JsonElement, type: Type, ctx: JsonDeserializationContext): Pose {
val model = modelFinder()
val obj = json as JsonObject
val pose = JsonPose(model, obj)
val conditionsList = mutableListOf<(PosableState) -> Boolean>()
val mustBeTouchingWater = json.get("isTouchingWater")?.asBoolean
if (mustBeTouchingWater != null) {
conditionsList.add { mustBeTouchingWater == it.getEntity()?.isTouchingWater }
}
val mustBeTouchingWaterOrRain = json.get("isTouchingWaterOrRain")?.asBoolean
if (mustBeTouchingWaterOrRain != null) {
conditionsList.add { mustBeTouchingWaterOrRain == it.getEntity()?.isTouchingWaterOrRain }
}
val mustBeSubmergedInWater = json.get("isSubmergedInWater")?.asBoolean
if (mustBeSubmergedInWater != null) {
conditionsList.add { mustBeSubmergedInWater == it.getEntity()?.isSubmergedInWater }
}
val mustBeStandingOnRedSand = json.get("isStandingOnRedSand")?.asBoolean
if (mustBeStandingOnRedSand != null) {
conditionsList.add { mustBeStandingOnRedSand == it.getEntity()?.isStandingOnRedSand() }
}
val mustBeStandingOnSand = json.get("isStandingOnSand")?.asBoolean
if (mustBeStandingOnSand != null) {
conditionsList.add { mustBeStandingOnSand == it.getEntity()?.isStandingOnSand() }
}
val mustBeStandingOnSandOrRedSand = json.get("isStandingOnSandOrRedSand")?.asBoolean
if (mustBeStandingOnSandOrRedSand != null) {
conditionsList.add { mustBeStandingOnSandOrRedSand == it.getEntity()?.isStandingOnSandOrRedSand() }
}
val mustBeDusk = json.get("isDusk")?.asBoolean
if (mustBeDusk != null) {
conditionsList.add { mustBeDusk == it.getEntity()?.isDusk() }
}
conditionsList.addAll(poseConditionReader(json))
if (json.has("condition")) {
val condition = json.get("condition").asString
conditionsList.add {
val entity = it.getEntity()
if (entity is PosableEntity) {
runtime.environment.query = entity.struct
condition.asExpressionLike().resolveBoolean(runtime)
} else {
false
}
}
}
val poseCondition: (PosableState) -> Boolean = if (conditionsList.isEmpty()) { { true } } else conditionsList.reduce { acc, function -> { acc(it) && function(it) } }
return Pose(
poseName = pose.poseName,
poseTypes = pose.poseTypes.toSet(),
condition = poseCondition,
transformTicks = pose.transformTicks,
animations = pose.idleAnimations,
transformedParts = pose.transformedParts,
quirks = pose.quirks.toTypedArray()
).also {
it.transitions.putAll(
pose.transitions
.mapNotNull<JsonPose.JsonPoseTransition, Pair<String, (Pose, Pose) -> ActiveAnimation>> {
it.to to { _, _ -> it.animation.resolveObject(model.runtime).obj as ActiveAnimation }
}.toMap()
)
}
}
}
@@ -1,10 +0,0 @@
/*
* 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.render.models.blockbench
class RangeOfMotion(var low: Float, var high: Float)
@@ -19,6 +19,12 @@ import net.minecraft.client.model.ModelTransform
import net.minecraft.client.model.TexturedModelData
import net.minecraft.client.util.math.MatrixStack
/**
* Bedrock's GEO format.
*
* @author Chris Fusco
* @since May 10th, 2022
*/
class TexturedModel {
@SerializedName("format_version")
val formatVersion: String = "0"
@@ -0,0 +1,44 @@
/*
* 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.render.models.blockbench.animation
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import net.minecraft.entity.Entity
/**
* An animation that requires some kind of state specific to an instance of [PosableState].
* This differs from [PoseAnimation]s in that that variety uses a single shared
* instance of the animation for the model, even when there may be a hundred entities using it.
*
* @author Hiroku
* @since December 5th, 2021
*/
interface ActiveAnimation {
/** Whether or not this animation is being used as a transition and therefore should prevent other pose transitions from occurring. */
val isTransition: Boolean
/** The animation's duration in seconds. */
val duration: Float
/** Runs the animation. Returns true if the animation should continue. */
fun run(
context: RenderContext,
model: PosableModel,
state: PosableState,
limbSwing: Float,
limbSwingAmount: Float,
ageInTicks: Float,
headYaw: Float,
headPitch: Float,
intensity: Float
): Boolean
/** Applies animation effects, such as particle effects. These can occur on tick, therefore not necessarily on screen. */
fun applyEffects(entity: Entity, state: PosableState, previousSeconds: Float, newSeconds: Float) {}
}
@@ -8,15 +8,14 @@
package com.cobblemon.mod.common.client.render.models.blockbench.animation
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.addRotation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BimanualFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.ModelFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Bone
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation.Companion.Y_AXIS
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation.Companion.Z_AXIS
import net.minecraft.entity.Entity
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import net.minecraft.util.math.MathHelper
/**
@@ -26,29 +25,26 @@ import net.minecraft.util.math.MathHelper
* @author Deltric
* @since December 21st, 2021
*/
class BimanualSwingAnimation<T : Entity>(
frame: ModelFrame,
class BimanualSwingAnimation(
/** The multiplier to apply to the cosine movement of the arms. The smaller this value, the quicker the arms move. */
val swingPeriodMultiplier: Float = 0.6662F,
/** The multiplier to apply to the swing of the entity. The larger this is, the further the arms move. */
val amplitudeMultiplier: Float = 1F,
val leftArm: Bone?,
val rightArm: Bone?
) : StatelessAnimation<T, ModelFrame>(frame) {
) : PoseAnimation() {
constructor(
frame: BimanualFrame,
swingPeriodMultiplier: Float = 0.6662F,
amplitudeMultiplier: Float = 1F
): this(
frame = frame,
swingPeriodMultiplier = swingPeriodMultiplier,
amplitudeMultiplier = amplitudeMultiplier,
leftArm = frame.leftArm,
rightArm = frame.rightArm
)
override val targetFrame: Class<ModelFrame> = ModelFrame::class.java
override fun setAngles(entity: T?, model: PoseableEntityModel<T>, state: PoseableEntityState<T>?, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, headYaw: Float, headPitch: Float, intensity: Float) {
override fun setAngles(context: RenderContext, model: PosableModel, state: PosableState, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, headYaw: Float, headPitch: Float, intensity: Float) {
// Movement swing
rightArm?.addRotation(Y_AXIS, MathHelper.cos(limbSwing * swingPeriodMultiplier) * limbSwingAmount * amplitudeMultiplier * intensity)
leftArm?.addRotation(Y_AXIS, MathHelper.cos(limbSwing * swingPeriodMultiplier) * limbSwingAmount * amplitudeMultiplier * intensity)
@@ -8,47 +8,43 @@
package com.cobblemon.mod.common.client.render.models.blockbench.animation
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.addRotation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.ModelFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Bone
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation.Companion.X_AXIS
import net.minecraft.entity.Entity
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import net.minecraft.util.math.MathHelper
/**
* A biped animation that will have zero-rotations on all legs at
* stateless and otherwise does simple predictable walking like Minecraft
* zero and otherwise does simple predictable walking like Minecraft
* quadrupeds.
*
* @author Deltric
* @since December 21st, 2021
*/
class BipedWalkAnimation<T : Entity>(
frame: ModelFrame,
class BipedWalkAnimation(
/** The multiplier to apply to the cosine movement of the legs. The smaller this value, the quicker the legs move. */
val periodMultiplier: Float = 0.6662F,
/** The multiplier to apply to the stride of the entity. The larger this is, the further the legs move. */
val amplitudeMultiplier: Float = 1.4F,
val leftLeg: Bone?,
val rightLeg: Bone?
) : StatelessAnimation<T, ModelFrame>(frame) {
) : PoseAnimation() {
constructor(
frame: BipedFrame,
periodMultiplier: Float = 0.6662F,
amplitudeMultiplier: Float = 1.4F
): this(
frame = frame,
periodMultiplier = periodMultiplier,
amplitudeMultiplier = amplitudeMultiplier,
leftLeg = frame.leftLeg,
rightLeg = frame.rightLeg
)
override val targetFrame: Class<ModelFrame> = ModelFrame::class.java
override fun setAngles(entity: T?, model: PoseableEntityModel<T>, state: PoseableEntityState<T>?, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, headYaw: Float, headPitch: Float, intensity: Float) {
override fun setAngles(context: RenderContext, model: PosableModel, state: PosableState, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, headYaw: Float, headPitch: Float, intensity: Float) {
rightLeg?.addRotation(X_AXIS, MathHelper.cos(limbSwing * periodMultiplier + Math.PI.toFloat()) * limbSwingAmount * amplitudeMultiplier * intensity)
leftLeg?.addRotation(X_AXIS, MathHelper.cos(limbSwing * periodMultiplier) * limbSwingAmount * amplitudeMultiplier * intensity)
}
@@ -1,55 +0,0 @@
/*
* 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.render.models.blockbench.animation
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.frame.ModelFrame
import kotlin.math.cos
import kotlin.math.sin
import net.minecraft.client.model.ModelPart
import net.minecraft.entity.Entity
/**
* A cascading animation that will increase movement over chained parts
*
* @author Deltric
* @since December 21st, 2021
*/
class CascadeAnimation<T : Entity>(
frame: ModelFrame,
val rootFunction: RootFunction,
val amplitudeFunction: AmplitudeFunction,
val segments: Array<ModelPart>
): StatelessAnimation<T, ModelFrame>(frame) {
override val targetFrame = ModelFrame::class.java
override fun setAngles(entity: T?, model: PoseableEntityModel<T>, state: PoseableEntityState<T>?, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, headYaw: Float, headPitch: Float, intensity: Float) {
segments.forEachIndexed { index, modelPart ->
modelPart.yaw += rootFunction(ageInTicks) * amplitudeFunction(index+1) * intensity
}
}
}
typealias AmplitudeFunction = (Int) -> Float
typealias RootFunction = (Float) -> Float
fun gradualFunction(base: Float = 1F, step: Float = 1F): AmplitudeFunction = { index ->
base + step * index
}
fun cosineFunction(period: Float = 1F): RootFunction = { x ->
cos(x * period)
}
fun sineFunction(period: Float = 1F): RootFunction = { x ->
sin(x * period)
}
@@ -0,0 +1,68 @@
/*
* 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.render.models.blockbench.animation
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.util.resolveBoolean
import net.minecraft.entity.Entity
/**
* Represents an animation that is shared across every instance of a model. This means that
* a pose animation cannot store any instance variables that relate to a particular entity
* that is playing the animation, as this instance is used for all of them.
*
* @author Hiroku
* @since December 4th, 2021
*/
abstract class PoseAnimation {
open var labels: Set<String> = setOf()
open var condition: (PosableState) -> Boolean = { true }
open fun withCondition(condition: (PosableState) -> Boolean): PoseAnimation {
this.condition = condition
return this
}
open fun withCondition(condition: ExpressionLike): PoseAnimation {
this.condition = { state -> state.runtime.resolveBoolean(condition) }
return this
}
protected abstract fun setAngles(
context: RenderContext,
model: PosableModel,
state: PosableState,
limbSwing: Float,
limbSwingAmount: Float,
ageInTicks: Float,
headYaw: Float,
headPitch: Float,
intensity: Float
)
fun apply(
context: RenderContext,
model: PosableModel,
state: PosableState,
limbSwing: Float,
limbSwingAmount: Float,
ageInTicks: Float,
headYaw: Float,
headPitch: Float,
intensity: Float
) {
if (!condition(state)) return
setAngles(context, model, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch, intensity)
}
open fun applyEffects(entity: Entity, state: PosableState, previousSeconds: Float, newSeconds: Float) {}
}
@@ -8,14 +8,14 @@
package com.cobblemon.mod.common.client.render.models.blockbench.animation
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.frame.ModelFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunction
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.sineFunction
import java.lang.Float.min
import net.minecraft.entity.Entity
/**
* An animation that gradually moves any [ModelFrame] from one pose to another.
@@ -23,13 +23,13 @@ import net.minecraft.entity.Entity
* @author Hiroku
* @since December 5th, 2021
*/
class PoseTransitionAnimation<T : Entity>(
val beforePose: Pose<T, *>,
val afterPose: Pose<T, *>,
class PoseTransitionAnimation(
val beforePose: Pose,
val afterPose: Pose,
val durationTicks: Int = 20,
val curve: WaveFunction = sineFunction(amplitude = 0.5F, period = 2F, phaseShift = 0.5F, verticalShift = 0.5F)
) : StatefulAnimation<T, ModelFrame> {
override val isTransform = true
) : ActiveAnimation {
override val isTransition = true
override val duration: Float = durationTicks / 20F
@@ -37,16 +37,16 @@ class PoseTransitionAnimation<T : Entity>(
var startTime = 0F
var endTime = 0F// startTime + durationTicks * 50L
fun initialize(state: PoseableEntityState<T>) {
fun initialize(state: PosableState) {
startTime = state.animationSeconds
endTime = startTime + durationTicks / 20F
initialized = true
}
override fun run(
entity: T?,
model: PoseableEntityModel<T>,
state: PoseableEntityState<T>,
context: RenderContext,
model: PosableModel,
state: PosableState,
limbSwing: Float,
limbSwingAmount: Float,
ageInTicks: Float,
@@ -67,14 +67,14 @@ class PoseTransitionAnimation<T : Entity>(
model.setDefault()
model.applyPose(beforePose.poseName, oldIntensity)
beforePose.idleAnimations.forEach {
it.apply(entity, model, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch, oldIntensity)
model.applyPose(state, beforePose, oldIntensity)
beforePose.animations.forEach {
it.apply(context, model, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch, oldIntensity)
}
model.applyPose(afterPose.poseName, newIntensity)
afterPose.idleAnimations.forEach {
it.apply(entity, model, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch, newIntensity)
model.applyPose(state, afterPose, newIntensity)
afterPose.animations.forEach {
it.apply(context, model, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch, newIntensity)
}
return ratio < 1F
@@ -8,25 +8,43 @@
package com.cobblemon.mod.common.client.render.models.blockbench.animation
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.frame.ModelFrame
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunction
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunctions
import java.util.function.Consumer
import net.minecraft.entity.Entity
/**
* An animation that plays to the exclusion of other primary animations. This can control
* whether other animations are blocked or faded in or out. It is able to prevent some idle
* animations based on labels.
*
* Fainting, pose transitions, and attack animations are examples of primary animations. They
* are mandated as being strictly one at a time.
*
* @author Hiroku
* @since November 20th, 2023
*/
class PrimaryAnimation<T : Entity>(
val animation: StatefulAnimation<T, *>,
class PrimaryAnimation(
val animation: ActiveAnimation,
/**
* The transition curve.
*
* When a primary animation is in effect, the pose animations and the primary animation will continue running
* at the same time but with different 'intensities'. The intensity is a value between 0 and 1 that represents
* how much of the animation's planned motion will be applied to the bone.
*
* This curve parameter is a function that takes a time value between 0 and 1 such that it begins at 0 and reaches
* 1 at the moment that the primary animation is complete. The function returns a value between 0 and 1 that will
* be used as the intensity of the primary animation. One minus this intensity will be used as the pose animation's
* intensity. In other words, the pose animation intensity and the primary animation intensity will always sum to
* 1 and this curve function represents the intensity of the primary animation.
*
* The default value for this curve makes the primary animation rapidly transition in, but does not transition out.
* The different pre-made options are defined in [WaveFunctions].
*/
var curve: WaveFunction = { t ->
// parabolaFunction(0.5F, 0.5F, 0.5F)
if (t < 0.1) {
t * 10
} else if (t < 0.9) {
@@ -35,17 +53,19 @@ class PrimaryAnimation<T : Entity>(
1F
}
},
/** Labels that can exist on a pose animation that will let it play even when this primary animation is in effect. */
val excludedLabels: Set<String> = emptySet(),
override val isTransform: Boolean = false
): StatefulAnimation<T, ModelFrame> {
override var isTransition: Boolean = false
): ActiveAnimation {
var started = -1F
override val duration: Float = animation.duration
/** Action to occur after the animation is done. */
var afterAction: Consumer<Unit> = Consumer { }
override fun run(
entity: T?,
model: PoseableEntityModel<T>,
state: PoseableEntityState<T>,
context: RenderContext,
model: PosableModel,
state: PosableState,
limbSwing: Float,
limbSwingAmount: Float,
ageInTicks: Float,
@@ -53,8 +73,15 @@ class PrimaryAnimation<T : Entity>(
headPitch: Float,
intensity: Float
): Boolean {
return animation.run(entity, model, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch, intensity)
return animation.run(context, model, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch, intensity)
}
fun prevents(idleAnimation: StatelessAnimation<T, *>) = idleAnimation.labels.intersect(excludedLabels).isEmpty() && "all" !in excludedLabels
/**
* Whether this animation will attempt to block the motion of the given pose animation. If true, the current
* intensity of the primary animation will be used to determine how much of the pose animation is blocked.
*
* If this primary animation does not block the given pose animation, the pose animation will play at full intensity
* in parallel to the primary animation. This is mainly used for look animations.
*/
fun prevents(poseAnimation: PoseAnimation) = poseAnimation.labels.intersect(excludedLabels).isEmpty() && "all" !in excludedLabels
}
@@ -8,26 +8,24 @@
package com.cobblemon.mod.common.client.render.models.blockbench.animation
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.addRotation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.ModelFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.QuadrupedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Bone
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation.Companion.X_AXIS
import net.minecraft.entity.Entity
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import net.minecraft.util.math.MathHelper
/**
* A quadruped animation that will have zero-rotations on all legs at
* stateless and otherwise does simple predictable walking like Minecraft
* zero and otherwise does simple predictable walking like Minecraft
* quadrupeds.
*
* @author Hiroku
* @since December 4th, 2021
*/
class QuadrupedWalkAnimation<T : Entity>(
frame: ModelFrame,
class QuadrupedWalkAnimation(
val legFrontLeft: Bone?,
val legFrontRight: Bone?,
val legBackLeft: Bone?,
@@ -36,13 +34,12 @@ class QuadrupedWalkAnimation<T : Entity>(
val periodMultiplier: Float = 0.6662F,
/** The multiplier to apply to the stride of the entity. The larger this is, the further the legs move. */
val amplitudeMultiplier: Float = 1.4F
) : StatelessAnimation<T, ModelFrame>(frame) {
) : PoseAnimation() {
constructor(
frame: QuadrupedFrame,
periodMultiplier: Float = 0.6662F,
amplitudeMultiplier: Float = 1.4F
): this(
frame = frame,
legFrontLeft = frame.foreLeftLeg,
legFrontRight = frame.foreRightLeg,
legBackLeft = frame.hindLeftLeg,
@@ -51,8 +48,7 @@ class QuadrupedWalkAnimation<T : Entity>(
amplitudeMultiplier = amplitudeMultiplier
)
override val targetFrame: Class<ModelFrame> = ModelFrame::class.java
override fun setAngles(entity: T?, model: PoseableEntityModel<T>, state: PoseableEntityState<T>?, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, headYaw: Float, headPitch: Float, intensity: Float) {
override fun setAngles(context: RenderContext, model: PosableModel, state: PosableState, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, headYaw: Float, headPitch: Float, intensity: Float) {
val hindRightLeg = legBackRight ?: return
val hindLeftLeg = legBackLeft ?: return
val foreRightLeg = legFrontRight ?: return
@@ -8,26 +8,23 @@
package com.cobblemon.mod.common.client.render.models.blockbench.animation
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.addRotation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.ModelFrame
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunction
import net.minecraft.client.model.ModelPart
import net.minecraft.entity.Entity
/**
* Animation simply works by moving a part's rotation along a particular function
*/
class RotationFunctionStatelessAnimation<T : Entity>(
class RotationFunctionPoseAnimation(
val part: ModelPart,
val function: WaveFunction,
val axis: Int,
val timeVariable: (state: PoseableEntityState<T>?, limbSwing: Float, ageInTicks: Float) -> Float?,
frame: ModelFrame
) : StatelessAnimation<T, ModelFrame>(frame) {
override val targetFrame = ModelFrame::class.java
override fun setAngles(entity: T?, model: PoseableEntityModel<T>, state: PoseableEntityState<T>?, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, headYaw: Float, headPitch: Float, intensity: Float) {
val timeVariable: (state: PosableState, limbSwing: Float, ageInTicks: Float) -> Float?,
) : PoseAnimation() {
override fun setAngles(context: RenderContext, model: PosableModel, state: PosableState, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, headYaw: Float, headPitch: Float, intensity: Float) {
part.addRotation(axis, function(timeVariable(state, limbSwing, ageInTicks) ?: 0F) * intensity)
}
}
@@ -8,16 +8,15 @@
package com.cobblemon.mod.common.client.render.models.blockbench.animation
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.addRotation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.ModelFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Bone
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation.Companion.X_AXIS
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation.Companion.Y_AXIS
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.entity.Entity
/**
* A very simple animation for [HeadedFrame]s which has the entity look along the head yaw and pitch.
@@ -27,8 +26,7 @@ import net.minecraft.entity.Entity
* @author Hiroku
* @since December 5th, 2021
*/
class SingleBoneLookAnimation<T : Entity>(
frame: ModelFrame,
class SingleBoneLookAnimation(
val bone: Bone?,
val pitchMultiplier: Float = 1F,
val yawMultiplier: Float = 1F,
@@ -36,7 +34,7 @@ class SingleBoneLookAnimation<T : Entity>(
val minPitch: Float = -45F,
val maxYaw: Float = 45F,
val minYaw: Float = -45F,
) : StatelessAnimation<T, ModelFrame>(frame) {
) : PoseAnimation() {
constructor(
frame: HeadedFrame,
invertX: Boolean,
@@ -50,7 +48,6 @@ class SingleBoneLookAnimation<T : Entity>(
maxYaw: Float? = null,
minYaw: Float? = null,
): this(
frame = frame,
bone = frame.head,
pitchMultiplier = pitchMultiplier ?: if (disableX) 0F else if (invertX) -1F else 1F,
yawMultiplier = yawMultiplier ?: if (disableY) 0F else if (invertY) -1F else 1F,
@@ -61,9 +58,8 @@ class SingleBoneLookAnimation<T : Entity>(
)
override val targetFrame: Class<ModelFrame> = ModelFrame::class.java
override var labels = setOf("look")
override fun setAngles(entity: T?, model: PoseableEntityModel<T>, state: PoseableEntityState<T>?, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, headYaw: Float, headPitch: Float, intensity: Float) {
override fun setAngles(context: RenderContext, model: PosableModel, state: PosableState, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, headYaw: Float, headPitch: Float, intensity: Float) {
val head = bone ?: return
val pitch = pitchMultiplier * headPitch.coerceIn(minPitch, maxPitch)
val yaw = yawMultiplier * headYaw.coerceIn(minYaw, maxYaw)
@@ -1,41 +0,0 @@
/*
* 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.render.models.blockbench.animation
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.frame.ModelFrame
import net.minecraft.entity.Entity
/**
* An animation that requires entity state. It is able to prevent some idle
* animations, usually on the basis of what [ModelFrame] class the animation
* uses.
*
* @author Hiroku
* @since December 5th, 2021
*/
interface StatefulAnimation<T : Entity, F : ModelFrame> {
val isTransform: Boolean
val duration: Float
/** Runs the animation. You can check that the model fits a particular frame. Returns true if the animation should continue. */
fun run(
entity: T?,
model: PoseableEntityModel<T>,
state: PoseableEntityState<T>,
limbSwing: Float,
limbSwingAmount: Float,
ageInTicks: Float,
headYaw: Float,
headPitch: Float,
intensity: Float
): Boolean
fun applyEffects(entity: T, state: PoseableEntityState<T>, previousSeconds: Float, newSeconds: Float) {}
}
@@ -1,34 +0,0 @@
/*
* 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.render.models.blockbench.animation
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.frame.ModelFrame
import net.minecraft.entity.Entity
/**
* An animation that can run without an entity associated. These are
* locked to a specific [ModelFrame], and CAN be given an entity along with
* limb swing and age information, but might not.
*
* @author Hiroku
* @since December 4th, 2021
*/
abstract class StatelessAnimation<T : Entity, F : ModelFrame>(val frame: F) {
abstract val targetFrame: Class<F>
open var labels: Set<String> = setOf()
protected abstract fun setAngles(entity: T?, model: PoseableEntityModel<T>, state: PoseableEntityState<T>?, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, headYaw: Float, headPitch: Float, intensity: Float)
fun apply(entity: T?, model: PoseableEntityModel<T>, state: PoseableEntityState<T>?, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, headYaw: Float, headPitch: Float, intensity: Float) {
setAngles(entity, model, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch, intensity)
}
open fun applyEffects(entity: T, state: PoseableEntityState<T>, previousSeconds: Float, newSeconds: Float) {}
}
@@ -8,26 +8,23 @@
package com.cobblemon.mod.common.client.render.models.blockbench.animation
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.addPosition
import com.cobblemon.mod.common.client.render.models.blockbench.frame.ModelFrame
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunction
import net.minecraft.client.model.ModelPart
import net.minecraft.entity.Entity
/**
* Animation simply works by moving a part along a particular function
*/
class TranslationFunctionStatelessAnimation<T : Entity>(
class TranslationFunctionPoseAnimation(
val part: ModelPart,
val function: WaveFunction,
val axis: Int,
val timeVariable: (state: PoseableEntityState<T>?, limbSwing: Float, ageInTicks: Float) -> Float?,
frame: ModelFrame
) : StatelessAnimation<T, ModelFrame>(frame) {
override val targetFrame = ModelFrame::class.java
override fun setAngles(entity: T?, model: PoseableEntityModel<T>, state: PoseableEntityState<T>?, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, headYaw: Float, headPitch: Float, intensity: Float) {
val timeVariable: (state: PosableState, limbSwing: Float, ageInTicks: Float) -> Float?
) : PoseAnimation() {
override fun setAngles(context: RenderContext, model: PosableModel, state: PosableState, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, headYaw: Float, headPitch: Float, intensity: Float) {
part.addPosition(axis, function(timeVariable(state, limbSwing, ageInTicks) ?: 0F) * intensity)
}
}
@@ -8,15 +8,14 @@
package com.cobblemon.mod.common.client.render.models.blockbench.animation
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.addPosition
import com.cobblemon.mod.common.client.render.models.blockbench.addRotation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.ModelFrame
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunction
import kotlin.math.atan
import net.minecraft.client.model.ModelPart
import net.minecraft.entity.Entity
/**
* An animation that interpolates some segment of limbs onto a given wave function. This wave function
@@ -40,8 +39,7 @@ import net.minecraft.entity.Entity
* @author Hiroku
* @since December 13, 2021
*/
class WaveAnimation<T : Entity>(
frame: ModelFrame,
class WaveAnimation(
val waveFunction: WaveFunction,
val oscillationsScalar: Float,
val head: ModelPart,
@@ -51,14 +49,12 @@ class WaveAnimation<T : Entity>(
val motionAxis: Int,
val basedOnLimbSwing: Boolean = false,
val segments: Array<WaveSegment>
): StatelessAnimation<T, ModelFrame>(frame) {
override val targetFrame = ModelFrame::class.java
override fun setAngles(entity: T?, model: PoseableEntityModel<T>, state: PoseableEntityState<T>?, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, headYaw: Float, headPitch: Float, intensity: Float) {
): PoseAnimation() {
override fun setAngles(context: RenderContext, model: PosableModel, state: PosableState, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, headYaw: Float, headPitch: Float, intensity: Float) {
val t = if (basedOnLimbSwing) {
limbSwing
} else {
entity?.let { model.getState(it).animationSeconds } ?: 0F
state.animationSeconds
}
var totalTimeDisplacement = (headLength + segments.map { it.length }.sum()) / oscillationsScalar
@@ -8,31 +8,27 @@
package com.cobblemon.mod.common.client.render.models.blockbench.animation
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.addRotation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BiWingedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.ModelFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Bone
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunction
import net.minecraft.entity.Entity
import net.minecraft.item.BoneMealItem
class WingFlapIdleAnimation<T : Entity>(
frame: ModelFrame,
class WingFlapIdleAnimation(
val leftWing: Bone?,
val rightWing: Bone?,
val rotation: WaveFunction,
val timeVariable: (state: PoseableEntityState<T>?, limbSwing: Float, ageInTicks: Float) -> Float? = { state, _, _ -> state?.animationSeconds ?: 0F },
val timeVariable: (state: PosableState, limbSwing: Float, ageInTicks: Float) -> Float? = { state, _, _ -> state.animationSeconds },
val axis: Int
) : StatelessAnimation<T, ModelFrame>(frame) {
) : PoseAnimation() {
constructor(
frame: BiWingedFrame,
flapFunction: WaveFunction,
timeVariable: (state: PoseableEntityState<T>?, limbSwing: Float, ageInTicks: Float) -> Float?,
timeVariable: (state: PosableState, limbSwing: Float, ageInTicks: Float) -> Float?,
axis: Int
): this(
frame = frame,
leftWing = frame.leftWing,
rightWing = frame.rightWing,
rotation = flapFunction,
@@ -40,9 +36,7 @@ class WingFlapIdleAnimation<T : Entity>(
axis = axis
)
override val targetFrame = ModelFrame::class.java
override fun setAngles(entity: T?, model: PoseableEntityModel<T>, state: PoseableEntityState<T>?, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, headYaw: Float, headPitch: Float, intensity: Float) {
override fun setAngles(context: RenderContext, model: PosableModel, state: PosableState, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, headYaw: Float, headPitch: Float, intensity: Float) {
val time = timeVariable(state, limbSwing, ageInTicks) ?: 0F
val angle = rotation(time)
leftWing?.addRotation(axis, angle)
@@ -8,39 +8,38 @@
package com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.animation.StatefulAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.StatelessAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.ModelFrame
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.animation.ActiveAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import net.minecraft.entity.Entity
/**
* A stateful animation that runs a [BedrockAnimation]. It is completed when the underlying
* An active animation that runs a [BedrockAnimation]. It is completed when the underlying
* bedrock animation is complete.
*
* @author Hiroku
* @since July 16th, 2022
*/
open class BedrockStatefulAnimation<T : Entity>(
open class BedrockActiveAnimation(
val animation: BedrockAnimation,
) : StatefulAnimation<T, ModelFrame> {
) : ActiveAnimation {
var startedSeconds = -1F
var isTransformAnimation = false
override val duration = animation.animationLength.toFloat()
private var afterAction: (T, PoseableEntityState<T>) -> Unit = { _, _ -> }
private var afterAction: (RenderContext, PosableState) -> Unit = { _, _ -> }
override val isTransform: Boolean
override val isTransition: Boolean
get() = isTransformAnimation
fun andThen(action: (entity: T, PoseableEntityState<T>) -> Unit) = this.also {
fun andThen(action: (context: RenderContext, PosableState) -> Unit) = this.also {
it.afterAction = action
}
override fun run(
entity: T?,
model: PoseableEntityModel<T>,
state: PoseableEntityState<T>,
context: RenderContext,
model: PosableModel,
state: PosableState,
limbSwing: Float,
limbSwingAmount: Float,
ageInTicks: Float,
@@ -52,14 +51,14 @@ open class BedrockStatefulAnimation<T : Entity>(
startedSeconds = state.animationSeconds
}
return animation.run(model, state, state.animationSeconds - startedSeconds, limbSwing, limbSwingAmount, ageInTicks, intensity).also {
if (!it && entity != null) {
afterAction(entity, state)
return animation.run(context, model, state, state.animationSeconds - startedSeconds, limbSwing, limbSwingAmount, ageInTicks, intensity).also {
if (!it) {
afterAction(context, state)
}
}
}
override fun applyEffects(entity: T, state: PoseableEntityState<T>, previousSeconds: Float, newSeconds: Float) {
override fun applyEffects(entity: Entity, state: PosableState, previousSeconds: Float, newSeconds: Float) {
val previousSecondsOffset = previousSeconds - startedSeconds
val currentSecondsOffset = newSeconds - startedSeconds
animation.applyEffects(entity, state, previousSecondsOffset, currentSecondsOffset)
@@ -10,28 +10,18 @@ package com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animati
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.MoParams
import com.bedrockk.molang.runtime.MoScope
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.snowstorm.BedrockParticleEffect
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.particle.ParticleStorm
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.getString
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.resolve
import com.cobblemon.mod.common.util.resolveDouble
import java.util.SortedMap
import java.util.TreeMap
import net.minecraft.client.MinecraftClient
import net.minecraft.client.model.ModelPart
import net.minecraft.client.sound.PositionedSoundInstance
@@ -50,7 +40,7 @@ data class BedrockAnimationGroup(
)
abstract class BedrockEffectKeyframe(val seconds: Float) {
abstract fun <T : Entity> run(entity: T, state: PoseableEntityState<T>)
abstract fun run(entity: Entity, state: PosableState)
}
class BedrockParticleKeyframe(
@@ -73,7 +63,7 @@ class BedrockParticleKeyframe(
}
}
override fun <T : Entity> run(entity: T, state: PoseableEntityState<T>) {
override fun run(entity: Entity, state: PosableState) {
val world = entity.world as? ClientWorld ?: return
val matrixWrapper = state.locatorStates[locator] ?: state.locatorStates["root"]!!
@@ -84,7 +74,7 @@ class BedrockParticleKeyframe(
val particleRuntime = MoLangRuntime()
// Share the query struct from the entity so the particle can query entity properties
particleRuntime.environment.structs["query"] = state.runtime.environment.getQueryStruct()
particleRuntime.environment.query = state.runtime.environment.query
val storm = ParticleStorm(
effect = effect,
@@ -108,7 +98,7 @@ class BedrockSoundKeyframe(
seconds: Float,
val sound: Identifier
): BedrockEffectKeyframe(seconds) {
override fun <T : Entity> run(entity: T, state: PoseableEntityState<T>) {
override fun run(entity: Entity, state: PosableState) {
val soundEvent = SoundEvent.of(sound) // Means we don't need to setup a sound registry entry for every single thing
if (soundEvent != null) {
MinecraftClient.getInstance().soundManager.play(
@@ -131,7 +121,7 @@ class BedrockInstructionKeyframe(
seconds: Float,
val expressions: ExpressionLike
): BedrockEffectKeyframe(seconds) {
override fun <T : Entity> run(entity: T, state: PoseableEntityState<T>) {
override fun run(entity: Entity, state: PosableState) {
expressions.resolve(state.runtime)
}
}
@@ -142,16 +132,10 @@ data class BedrockAnimation(
val effects: List<BedrockEffectKeyframe>,
val boneTimelines: Map<String, BedrockBoneTimeline>
) {
companion object {
val sharedRuntime = MoLangRuntime().also {
val zero = DoubleValue(0.0)
it.environment.getQueryStruct().addFunctions(mapOf(
"anim_time" to java.util.function.Function { return@Function zero }
))
}
}
/** Useful to have, gets set after loading the animation. */
var name: String = ""
fun run(model: PoseableEntityModel<*>, state: PoseableEntityState<*>?, animationSeconds: Float, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, intensity: Float): Boolean {
fun run(context: RenderContext, model: PosableModel, state: PosableState, animationSeconds: Float, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, intensity: Float): Boolean {
var animationSeconds = animationSeconds
if (shouldLoop) {
animationSeconds %= animationLength.toFloat()
@@ -159,7 +143,7 @@ data class BedrockAnimation(
return false
}
val runtime = state?.runtime ?: sharedRuntime
val runtime = state.runtime
runtime.environment.setSimpleVariable("limb_swing", DoubleValue(limbSwing.toDouble()))
runtime.environment.setSimpleVariable("limb_swing_amount", DoubleValue(limbSwingAmount.toDouble()))
@@ -189,9 +173,7 @@ data class BedrockAnimation(
val exception = IllegalStateException("Bad animation for entity: ${(model.context.request(RenderContext.ENTITY))!!.displayName.string}", e)
val crash = CrashReport("Cobblemon encountered an unexpected crash", exception)
val section = crash.addElement("Animation Details")
state?.let {
section.add("Pose", state.currentPose!!)
}
section.add("Pose", state.currentPose!!)
section.add("Bone", boneName)
throw CrashException(crash)
@@ -199,19 +181,28 @@ data class BedrockAnimation(
}
if (!timeline.scale.isEmpty()) {
var scale = timeline.scale.resolve(animationSeconds.toDouble(), runtime).multiply(intensity.toDouble())
val deviation = scale.multiply(-1.0).add(1.0, 1.0, 1.0)//.multiply(intensity.toDouble())
scale = deviation.subtract(1.0, 1.0, 1.0).multiply(-1.0)
part.xScale *= scale.x.toFloat()
part.yScale *= scale.y.toFloat()
part.zScale *= scale.z.toFloat()
var scale = timeline.scale.resolve(animationSeconds.toDouble(), runtime)
// If the goal is to make the invisible then kick that into gear after 0.5. Maybe could work better somehow else.
if (scale == Vec3d.ZERO && intensity > 0.5) {
part.xScale *= scale.x.toFloat()
part.yScale *= scale.y.toFloat()
part.zScale *= scale.z.toFloat()
} else {
// The deviation from 1 is what we want to multiply by the intensity of the animation.
val deviation = scale.multiply(-1.0).add(1.0, 1.0, 1.0)
val weakenedDeviation = deviation.multiply(intensity.toDouble())
scale = weakenedDeviation.subtract(1.0, 1.0, 1.0).multiply(-1.0)
part.xScale *= scale.x.toFloat()
part.yScale *= scale.y.toFloat()
part.zScale *= scale.z.toFloat()
}
}
}
}
return true
}
fun <T : Entity> applyEffects(entity: T, state: PoseableEntityState<T>, previousSeconds: Float, newSeconds: Float) {
fun applyEffects(entity: Entity, state: PosableState, previousSeconds: Float, newSeconds: Float) {
val effectCondition: (effectKeyframe: BedrockEffectKeyframe) -> Boolean =
if (previousSeconds > newSeconds) {
{ it.seconds >= previousSeconds || it.seconds <= newSeconds }
@@ -259,7 +250,7 @@ class MolangBoneValue(
}
}
class BedrockKeyFrameBoneValue : HashMap<Double, BedrockAnimationKeyFrame>(), BedrockBoneValue {
class BedrockKeyFrameBoneValue : TreeMap<Double, BedrockAnimationKeyFrame>(), BedrockBoneValue {
fun SortedMap<Double, BedrockAnimationKeyFrame>.getAtIndex(index: Int?): BedrockAnimationKeyFrame? {
if (index == null) return null
val key = this.keys.elementAtOrNull(index)
@@ -267,17 +258,15 @@ class BedrockKeyFrameBoneValue : HashMap<Double, BedrockAnimationKeyFrame>(), Be
}
override fun resolve(time: Double, runtime: MoLangRuntime): Vec3d {
val sortedTimeline = toSortedMap()
var afterIndex : Int? = sortedTimeline.keys.indexOfFirst { it > time }
var afterIndex : Int? = keys.indexOfFirst { it > time }
if (afterIndex == -1) afterIndex = null
val beforeIndex = when (afterIndex) {
null -> sortedTimeline.size - 1
null -> size - 1
0 -> null
else -> afterIndex - 1
}
val after = sortedTimeline.getAtIndex(afterIndex)
val before = sortedTimeline.getAtIndex(beforeIndex)
val after = getAtIndex(afterIndex)
val before = getAtIndex(beforeIndex)
val afterData = after?.pre?.resolve(time, runtime) ?: Vec3d.ZERO
val beforeData = before?.post?.resolve(time, runtime) ?: Vec3d.ZERO
@@ -287,9 +276,9 @@ class BedrockKeyFrameBoneValue : HashMap<Double, BedrockAnimationKeyFrame>(), Be
when {
before != null && after != null -> {
val beforePlusIndex = if (beforeIndex == null || beforeIndex == 0) null else beforeIndex - 1
val beforePlus = sortedTimeline.getAtIndex(beforePlusIndex)
val beforePlus = getAtIndex(beforePlusIndex)
val afterPlusIndex = if (afterIndex == null || afterIndex == size - 1) null else afterIndex + 1
val afterPlus = sortedTimeline.getAtIndex(afterPlusIndex)
val afterPlus = getAtIndex(afterPlusIndex)
return catmullromLerp(beforePlus, before, after, afterPlus, time, runtime)
}
before != null -> return beforeData
@@ -11,8 +11,6 @@ package com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animati
import com.bedrockk.molang.MoLang
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.client.particle.BedrockParticleEffectRepository
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.google.gson.JsonArray
@@ -27,7 +25,7 @@ import java.lang.reflect.Type
* Gson adapter for converting bedrock/blockbench json data into a friendlier object model.
*
* @author landonjw
* @since January 5, 2022
* @since January 5, 2022
*/
object BedrockAnimationAdapter : JsonDeserializer<BedrockAnimation> {
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): BedrockAnimation {
@@ -10,7 +10,7 @@ package com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animati
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.client.render.models.blockbench.BedrockAnimationReferenceFactory
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.JsonPokemonPoseableModel
import com.cobblemon.mod.common.client.render.models.blockbench.JsonPose
import com.cobblemon.mod.common.util.fromJson
import com.google.gson.FieldNamingPolicy
import com.google.gson.GsonBuilder
@@ -18,7 +18,8 @@ import net.minecraft.resource.ResourceManager
/**
* Handles the loading and retrieval of bedrock animations.
* Handles the loading and retrieval of bedrock animations. These animations are agnostic of the type of
* model that they are loaded onto.
*
* @author landonjw
* @since January 5, 2022
@@ -34,7 +35,7 @@ object BedrockAnimationRepository {
private val animationGroups = mutableMapOf<String, BedrockAnimationGroup>()
fun loadAnimations(resourceManager: ResourceManager, directories: List<String>) {
JsonPokemonPoseableModel.registerFactory("bedrock", BedrockAnimationReferenceFactory)
JsonPose.registerAnimationFactory("bedrock", BedrockAnimationReferenceFactory)
LOGGER.info("Loading animations...")
var animationCount = 0
@@ -44,6 +45,7 @@ object BedrockAnimationRepository {
.forEach { (identifier, resource) ->
try {
val animationGroup = gson.fromJson<BedrockAnimationGroup>(resource.inputStream.reader())
animationGroup.animations.entries.forEach { (name, animation) -> animation.name = name }
val animationGroupName = identifier.path.substringAfterLast("/").replace(".animation.json", "")
animationGroups[animationGroupName] = animationGroup
animationCount += animationGroup.animations.size
@@ -8,31 +8,36 @@
package com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.animation.StatelessAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.ModelFrame
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PoseAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import net.minecraft.entity.Entity
/**
* Animation that analyzes a [BedrockAnimation] and applies transformations to the model based on
* the given animation time.
*
* @param frame The model frame to apply the animation to
* @param animation The [BedrockAnimation] to be played
*
* @author landonjw
* @since January 5th, 2022
*/
class BedrockStatelessAnimation<T: Entity>(frame: ModelFrame, val animation: BedrockAnimation) : StatelessAnimation<T, ModelFrame>(frame) {
override val targetFrame: Class<ModelFrame> = ModelFrame::class.java
class BedrockPoseAnimation(val animation: BedrockAnimation) : PoseAnimation() {
val particleKeyFrames = animation.effects.filterIsInstance<BedrockParticleKeyframe>()
override fun setAngles(entity: T?, model: PoseableEntityModel<T>, state: PoseableEntityState<T>?, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, headYaw: Float, headPitch: Float, intensity: Float) {
animation.run(model, state, state?.animationSeconds ?: 0F, limbSwing, limbSwingAmount, ageInTicks, intensity)
init {
if (animation.animationLength != -1.0 && !animation.shouldLoop) {
Cobblemon.LOGGER.error("Found animation that is not set to loop and has a set length but is being used in an idle animation. This will cause things like T-posing! ${animation.name}")
}
}
override fun applyEffects(entity: T, state: PoseableEntityState<T>, previousSeconds: Float, newSeconds: Float) {
override fun setAngles(context: RenderContext, model: PosableModel, state: PosableState, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, headYaw: Float, headPitch: Float, intensity: Float) {
animation.run(context, model, state, state.animationSeconds, limbSwing, limbSwingAmount, ageInTicks, intensity)
}
override fun applyEffects(entity: Entity, state: PosableState, previousSeconds: Float, newSeconds: Float) {
val effectiveAnimationLength = animation.animationLength.takeUnless { it <= 0 }?.toFloat() ?: animation.effects.maxOfOrNull { it.seconds }?.takeIf { it != 0F }
val (loopedPreviousSeconds, loopedNewSeconds) = if (effectiveAnimationLength != null) {
(previousSeconds % effectiveAnimationLength) to (newSeconds % effectiveAnimationLength)
@@ -14,7 +14,6 @@ import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTr
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation.Companion.Z_AXIS
import kotlin.math.floor
import net.minecraft.util.math.Vec3d
import org.joml.Vector3d
/**
* Interpolates a vector based on a Catmull-Rom spline.
@@ -89,16 +88,4 @@ private fun catmullrom(t: Double, p0: Double, p1: Double, p2: Double, p3: Double
return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (-3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1
}
private fun Vector3d.get(axis: Int) : Double {
return when (axis) {
X_AXIS -> this.x
Y_AXIS -> this.y
Z_AXIS -> this.z
else -> throw IllegalStateException()
}
}
private data class Vector2d(
val a: Double,
val b: Double
)
private data class Vector2d(val a: Double, val b: Double)
@@ -8,37 +8,18 @@
package com.cobblemon.mod.common.client.render.models.blockbench.blockentity
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.animation.StatelessAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.ModelFrame
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Bone
import com.cobblemon.mod.common.entity.PoseType
import com.google.gson.annotations.SerializedName
import net.minecraft.client.model.ModelPart
import net.minecraft.entity.Entity
class BlockEntityModel(val root: Bone) : PoseableEntityModel<Entity>() {
val boneName: String = root.children.entries.first().key
override val rootPart = (root as ModelPart).registerChildWithAllChildren(boneName)
override val isForLivingEntityRenderer = false
var idleAnimations: Array<StatelessAnimation<Entity, out ModelFrame>> = emptyArray()
class BlockEntityModel(root: Bone) : PosableModel(root) {
@Transient
@SerializedName("dummy")
override var isForLivingEntityRenderer = false
@Transient
@SerializedName("Don't bloody deserialize this, Gson! I mean it!")
override val rootPart = (root as ModelPart).children.entries.first().let { root.registerChildWithAllChildren(it.key) }
var maxScale = 1F
var yTranslation = 0F
override fun registerPoses() {
val closedPose = registerPose<ModelFrame>(poseName = "CLOSED", poseType = PoseType.NONE)
val openPose = registerPose(
poseType = PoseType.OPEN,
idleAnimations = arrayOf(bedrock("gilded_chest", "open"))
)
closedPose.transitions[openPose.poseName] = { _, _ ->
bedrockStateful("gilded_chest", "opening")
}
openPose.transitions[closedPose.poseName] = { _, _ ->
bedrockStateful("gilded_chest", "closing")
}
}
override fun getState(entity: Entity) = throw NotImplementedError("This is not supported for the gilded chest")
}
@@ -8,20 +8,27 @@
package com.cobblemon.mod.common.client.render.models.blockbench.fossil
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.animation.StatelessAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.ModelFrame
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PoseAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Bone
import com.cobblemon.mod.common.client.render.models.blockbench.quirk.ModelQuirk
import com.cobblemon.mod.common.entity.PoseType
import com.google.gson.annotations.SerializedName
import net.minecraft.client.model.ModelPart
import net.minecraft.entity.Entity
class FossilModel(root: Bone) : PoseableEntityModel<Entity>() {
override val isForLivingEntityRenderer = false
//TODO: Find a better way to fetch this bone name - Update the BlockEntityModel too when you figure it out
val boneName: String = root.children.entries.first().key
override val rootPart = (root as ModelPart).registerChildWithAllChildren(boneName)
/**
* A model for rendering in the restoration tank. This model is not intended to be used for living entities.
*
* @author Hiroku
* @since October 30th, 2023
*/
class FossilModel(root: Bone) : PosableModel(root) {
@Transient
@SerializedName("dummy")
override var isForLivingEntityRenderer = false
@Transient
@SerializedName("Something that isn't root part. Gson thinks they're the same as the root field and so field duplication. Stupid.")
override val rootPart = (root as ModelPart).registerChildWithAllChildren(root.children.entries.first().key)
// Represents a very rough middle of the model
// The reason to do this is to
// 1. The embryo is aligned with the center of the model
@@ -29,15 +36,14 @@ class FossilModel(root: Bone) : PoseableEntityModel<Entity>() {
var yGrowthPoint = 0F
var maxScale = 1F
var yTranslation = 0F // Offset inside the tank
var tankAnimations: Array<StatelessAnimation<Entity, out ModelFrame>> = emptyArray()
var tankQuirks: Array<ModelQuirk<Entity, *>> = emptyArray()
var tankAnimations: Array<PoseAnimation> = emptyArray()
var tankQuirks: Array<ModelQuirk<*>> = emptyArray()
override fun registerPoses() {
registerPose(
poseType = PoseType.SLEEP,
idleAnimations = tankAnimations,
animations = tankAnimations,
quirks = tankQuirks
)
}
override fun getState(entity: Entity) = throw NotImplementedError("This is not supported for fossil models")
}
@@ -9,9 +9,7 @@
package com.cobblemon.mod.common.client.render.models.blockbench.fossil
import com.cobblemon.mod.common.api.scheduling.SchedulingTracker
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import net.minecraft.entity.Entity
import kotlin.math.floor
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
/**
* Floating state for a fossil Pokémon in the resurrection machine.
@@ -19,7 +17,7 @@ import kotlin.math.floor
* @author Hiroku
* @since October 30th, 2023
*/
class FossilState(startAge: Int = -1, startPartialTicks: Float = 0F) : PoseableEntityState<Entity>() {
class FossilState(startAge: Int = -1, startPartialTicks: Float = 0F) : PosableState() {
var totalPartialTicks = 0F
init {
// generate phase offset if new
@@ -8,19 +8,18 @@
package com.cobblemon.mod.common.client.render.models.blockbench.frame
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.animation.WingFlapIdleAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunction
import net.minecraft.client.model.ModelPart
import net.minecraft.entity.Entity
interface BiWingedFrame : ModelFrame {
val leftWing: ModelPart
val rightWing: ModelPart
fun <T : Entity> wingFlap(
fun wingFlap(
flapFunction: WaveFunction,
timeVariable: (state: PoseableEntityState<T>?, limbSwing: Float, ageInTicks: Float) -> Float?,
timeVariable: (state: PosableState, limbSwing: Float, ageInTicks: Float) -> Float?,
axis: Int
) = WingFlapIdleAnimation(
frame = this,

Some files were not shown because too many files have changed in this diff Show More