forked from mirrors/cobblemon
Major refactor of animation system
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -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) }
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -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) }
|
||||
|
||||
+2
-2
@@ -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)
|
||||
|
||||
+1
-1
@@ -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
|
||||
)
|
||||
|
||||
|
||||
+3
-3
@@ -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))
|
||||
}
|
||||
}
|
||||
-1
@@ -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
|
||||
|
||||
+3
-5
@@ -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()) }
|
||||
}
|
||||
|
||||
}
|
||||
+1
-7
@@ -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
|
||||
|
||||
-6
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
+2
-7
@@ -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,
|
||||
|
||||
+5
-6
@@ -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) {
|
||||
|
||||
+5
-7
@@ -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
|
||||
|
||||
+2
-2
@@ -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()
|
||||
|
||||
}
|
||||
+4
-8
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-10
@@ -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) {
|
||||
|
||||
+48
-51
@@ -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)
|
||||
|
||||
|
||||
+12
-11
@@ -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()
|
||||
|
||||
|
||||
+4
-3
@@ -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
|
||||
|
||||
+3
-4
@@ -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 }
|
||||
|
||||
+17
-22
@@ -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,
|
||||
|
||||
+2
-2
@@ -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 }
|
||||
|
||||
|
||||
+2
-3
@@ -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
|
||||
|
||||
+3
-1
@@ -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
|
||||
)
|
||||
|
||||
+3
-3
@@ -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
|
||||
)
|
||||
|
||||
+3
-1
@@ -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
|
||||
)
|
||||
|
||||
+2
-3
@@ -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) {
|
||||
|
||||
+3
-2
@@ -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
|
||||
)
|
||||
|
||||
+6
-6
@@ -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,
|
||||
|
||||
+7
-8
@@ -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())
|
||||
}
|
||||
+2
-2
@@ -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()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
+4
-4
@@ -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)
|
||||
}
|
||||
|
||||
+5
-6
@@ -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))
|
||||
|
||||
+10
-13
@@ -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
|
||||
|
||||
+7
-8
@@ -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
|
||||
|
||||
+32
-7
@@ -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()
|
||||
|
||||
+14
-5
@@ -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()
|
||||
|
||||
+22
-8
@@ -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()
|
||||
|
||||
+7
-3
@@ -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
|
||||
|
||||
+23
-5
@@ -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()
|
||||
|
||||
+43
-13
@@ -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()
|
||||
|
||||
+9
-9
@@ -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
|
||||
}
|
||||
+4
-9
@@ -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],
|
||||
|
||||
-19
@@ -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)
|
||||
+1
@@ -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>)
|
||||
+4
-6
@@ -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) {
|
||||
+31
@@ -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!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
+65
-36
@@ -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()
|
||||
}
|
||||
-125
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+938
@@ -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) }
|
||||
)
|
||||
}
|
||||
+150
-71
@@ -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
|
||||
}
|
||||
+110
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
-944
File diff suppressed because it is too large
Load Diff
-10
@@ -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)
|
||||
+6
@@ -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"
|
||||
|
||||
+44
@@ -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) {}
|
||||
}
|
||||
+6
-10
@@ -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)
|
||||
|
||||
+7
-11
@@ -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)
|
||||
}
|
||||
|
||||
-55
@@ -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)
|
||||
}
|
||||
+68
@@ -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) {}
|
||||
}
|
||||
+18
-18
@@ -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
|
||||
|
||||
+41
-14
@@ -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
|
||||
}
|
||||
+7
-11
@@ -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
|
||||
|
||||
+7
-10
@@ -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)
|
||||
}
|
||||
}
|
||||
+6
-10
@@ -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)
|
||||
|
||||
-41
@@ -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) {}
|
||||
}
|
||||
-34
@@ -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) {}
|
||||
}
|
||||
+7
-10
@@ -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)
|
||||
}
|
||||
}
|
||||
+7
-11
@@ -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
-14
@@ -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)
|
||||
|
||||
+17
-18
@@ -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)
|
||||
+36
-47
@@ -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
|
||||
|
||||
+1
-3
@@ -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 {
|
||||
|
||||
+5
-3
@@ -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
|
||||
|
||||
+15
-10
@@ -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)
|
||||
+1
-14
@@ -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)
|
||||
+9
-28
@@ -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")
|
||||
}
|
||||
+20
-14
@@ -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")
|
||||
}
|
||||
+2
-4
@@ -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
|
||||
|
||||
+3
-4
@@ -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
Reference in New Issue
Block a user