Merge branch 'move-animations' into 'main'
Simplistic move animations See merge request cable-mc/cobblemon!871
@@ -33,3 +33,4 @@ common/src/main/resources/assets/cobblemon/showdown
|
||||
# Generated files
|
||||
*/generated/resources/*
|
||||
/fabric/src/generated/resources/cobblemon-common.accesswidener
|
||||
/.ignored/
|
||||
|
Before Width: | Height: | Size: 281 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 519 B |
|
Before Width: | Height: | Size: 209 B |
|
Before Width: | Height: | Size: 696 B |
|
Before Width: | Height: | Size: 224 B |
|
Before Width: | Height: | Size: 351 B |
|
Before Width: | Height: | Size: 608 B |
@@ -19,6 +19,7 @@
|
||||
- You can now find Nether Fire Stone Ore in, surprisingly, the Nether.
|
||||
- Added effects for many Poké Balls when sending out Pokémon or capturing.
|
||||
- Added a new send-out ball toss animation that showcases the ball used for the Pokémon.
|
||||
- Added simple move animations for Pokémon in battle with a proof of concept for more complicated animations.
|
||||
|
||||
### Changes
|
||||
- Pokémon now transition more smoothly between different poses.
|
||||
|
||||
@@ -13,6 +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.battle.BattleApplyPassResponseHandler
|
||||
import com.cobblemon.mod.common.client.net.battle.BattleCaptureEndHandler
|
||||
import com.cobblemon.mod.common.client.net.battle.BattleCaptureShakeHandler
|
||||
@@ -37,8 +38,10 @@ import com.cobblemon.mod.common.client.net.callback.party.OpenPartyCallbackHandl
|
||||
import com.cobblemon.mod.common.client.net.callback.partymove.OpenPartyMoveCallbackHandler
|
||||
import com.cobblemon.mod.common.client.net.data.DataRegistrySyncPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.data.UnlockReloadPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.effect.RunPosableMoLangHandler
|
||||
import com.cobblemon.mod.common.client.net.dialogue.DialogueClosedHandler
|
||||
import com.cobblemon.mod.common.client.net.dialogue.DialogueOpenedHandler
|
||||
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormEntityParticleHandler
|
||||
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormParticleHandler
|
||||
import com.cobblemon.mod.common.client.net.gui.InteractPokemonUIPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.gui.SummaryUIPacketHandler
|
||||
@@ -48,7 +51,6 @@ import com.cobblemon.mod.common.client.net.pasture.PokemonPasturedHandler
|
||||
import com.cobblemon.mod.common.client.net.pasture.PokemonUnpasturedHandler
|
||||
import com.cobblemon.mod.common.client.net.pokemon.update.PokemonUpdatePacketHandler
|
||||
import com.cobblemon.mod.common.client.net.settings.ServerSettingsPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.sound.PokemonCryHandler
|
||||
import com.cobblemon.mod.common.client.net.sound.UnvalidatedPlaySoundS2CPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.spawn.SpawnExtraDataEntityHandler
|
||||
import com.cobblemon.mod.common.client.net.starter.StarterUIPacketHandler
|
||||
@@ -73,6 +75,7 @@ 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.battle.*
|
||||
import com.cobblemon.mod.common.net.messages.client.callback.OpenMoveCallbackPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyCallbackPacket
|
||||
@@ -85,6 +88,8 @@ import com.cobblemon.mod.common.net.messages.client.data.SpeciesRegistrySyncPack
|
||||
import com.cobblemon.mod.common.net.messages.client.data.UnlockReloadPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueClosedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueOpenedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticlePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.fossil.FossilRegistrySyncPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.fossil.NaturalMaterialRegistrySyncPacket
|
||||
@@ -97,7 +102,6 @@ import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.Add
|
||||
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.ClearEvolutionsPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.RemoveEvolutionPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.sound.PokemonCryPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.sound.UnvalidatedPlaySoundS2CPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.spawn.SpawnGenericBedrockPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.spawn.SpawnPokeballPacket
|
||||
@@ -315,6 +319,8 @@ object CobblemonNetwork : NetworkManager {
|
||||
|
||||
// Effects
|
||||
this.createClientBound(SpawnSnowstormParticlePacket.ID, SpawnSnowstormParticlePacket::decode, SpawnSnowstormParticleHandler)
|
||||
this.createClientBound(SpawnSnowstormEntityParticlePacket.ID, SpawnSnowstormEntityParticlePacket::decode, SpawnSnowstormEntityParticleHandler)
|
||||
this.createClientBound(RunPosableMoLangPacket.ID, RunPosableMoLangPacket::decode, RunPosableMoLangHandler)
|
||||
|
||||
// Hax
|
||||
this.createClientBound(UnvalidatedPlaySoundS2CPacket.ID, UnvalidatedPlaySoundS2CPacket::decode, UnvalidatedPlaySoundS2CPacketHandler)
|
||||
@@ -339,7 +345,7 @@ object CobblemonNetwork : NetworkManager {
|
||||
this.createClientBound(PokemonUnpasturedPacket.ID, PokemonUnpasturedPacket::decode, PokemonUnpasturedHandler)
|
||||
|
||||
// Behaviours
|
||||
this.createClientBound(PokemonCryPacket.ID, PokemonCryPacket::decode, PokemonCryHandler)
|
||||
this.createClientBound(PlayPoseableAnimationPacket.ID, PlayPoseableAnimationPacket::decode, PlayPoseableAnimationHandler)
|
||||
|
||||
// Move select packets
|
||||
this.createClientBound(OpenMoveCallbackPacket.ID, OpenMoveCallbackPacket::decode, OpenMoveCallbackHandler)
|
||||
|
||||
@@ -129,6 +129,43 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, RegistryKey<Regi
|
||||
@JvmField
|
||||
val TUMBLESTONE_STEP = this.create("tumblestone.step")
|
||||
|
||||
@JvmField
|
||||
val IMPACT_NORMAL = this.create("impact.normal")
|
||||
@JvmField
|
||||
val IMPACT_BUG = this.create("impact.bug")
|
||||
@JvmField
|
||||
val IMPACT_DARK = this.create("impact.dark")
|
||||
@JvmField
|
||||
val IMPACT_DRAGON = this.create("impact.dragon")
|
||||
@JvmField
|
||||
val IMPACT_ELECTRIC = this.create("impact.electric")
|
||||
@JvmField
|
||||
val IMPACT_FAIRY = this.create("impact.fairy")
|
||||
@JvmField
|
||||
val IMPACT_FIGHTING = this.create("impact.fighting")
|
||||
@JvmField
|
||||
val IMPACT_FIRE = this.create("impact.fire")
|
||||
@JvmField
|
||||
val IMPACT_FLYING = this.create("impact.flying")
|
||||
@JvmField
|
||||
val IMPACT_GHOST = this.create("impact.ghost")
|
||||
@JvmField
|
||||
val IMPACT_GRASS = this.create("impact.grass")
|
||||
@JvmField
|
||||
val IMPACT_GROUND = this.create("impact.ground")
|
||||
@JvmField
|
||||
val IMPACT_ICE = this.create("impact.ice")
|
||||
@JvmField
|
||||
val IMPACT_POISON = this.create("impact.poison")
|
||||
@JvmField
|
||||
val IMPACT_PSYCHIC = this.create("impact.psychic")
|
||||
@JvmField
|
||||
val IMPACT_ROCK = this.create("impact.rock")
|
||||
@JvmField
|
||||
val IMPACT_STEEL = this.create("impact.steel")
|
||||
@JvmField
|
||||
val IMPACT_WATER = this.create("impact.water")
|
||||
|
||||
@JvmField
|
||||
val TUMBLESTONE_SOUNDS = BlockSoundGroup(1f, 1f,
|
||||
TUMBLESTONE_BREAK,
|
||||
|
||||
@@ -8,9 +8,13 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.battles.model
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangEnvironment
|
||||
import com.bedrockk.molang.runtime.struct.QueryStruct
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.Cobblemon.LOGGER
|
||||
import com.cobblemon.mod.common.CobblemonNetwork
|
||||
import com.cobblemon.mod.common.Environment
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
@@ -18,6 +22,8 @@ import com.cobblemon.mod.common.api.battles.model.actor.EntityBackedBattleActor
|
||||
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.addFunction
|
||||
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
|
||||
@@ -464,4 +470,13 @@ open class PokemonBattle(
|
||||
LOGGER.error("Missing interpretation on '{}' action: \nPublic » {}\nPrivate » {}", publicMessage.id, publicMessage.rawMessage, privateMessage.rawMessage)
|
||||
return Text.literal("Missing interpretation on '${publicMessage.id}' action please report to the developers").red()
|
||||
}
|
||||
|
||||
fun addQueryFunctions(queryStruct: QueryStruct): QueryStruct {
|
||||
queryStruct.addFunction("pvp") { DoubleValue(isPvP) }
|
||||
queryStruct.addFunction("pvn") { DoubleValue(isPvN) }
|
||||
queryStruct.addFunction("pvw") { DoubleValue(isPvW) }
|
||||
queryStruct.addFunction("has_rule") { params -> DoubleValue(params.getString(0) in format.ruleSet) }
|
||||
|
||||
return queryStruct
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ import com.bedrockk.molang.runtime.value.MoValue
|
||||
import com.bedrockk.molang.runtime.value.StringValue
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.dialogue.PlayerDialogueFaceProvider
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunction
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunctions
|
||||
import com.cobblemon.mod.common.util.isInt
|
||||
import com.cobblemon.mod.common.util.itemRegistry
|
||||
import net.minecraft.block.Block
|
||||
@@ -51,7 +52,30 @@ object MoLangFunctions {
|
||||
"is_number" to java.util.function.Function { params -> DoubleValue(params.get<MoValue>(0).asString().toDoubleOrNull() != null) },
|
||||
"to_number" to java.util.function.Function { params -> DoubleValue(params.get<MoValue>(0).asString().toDoubleOrNull() ?: 0.0) },
|
||||
"to_int" to java.util.function.Function { params -> DoubleValue(params.get<MoValue>(0).asString().toIntOrNull() ?: 0) },
|
||||
"to_string" to java.util.function.Function { params -> StringValue(params.get<MoValue>(0).asString()) }
|
||||
"to_string" to java.util.function.Function { params -> StringValue(params.get<MoValue>(0).asString()) },
|
||||
"do_effect_walks" to java.util.function.Function { _ ->
|
||||
DoubleValue(Cobblemon.config.walkingInBattleAnimations)
|
||||
},
|
||||
"random" to java.util.function.Function { params ->
|
||||
val options = mutableListOf<MoValue>()
|
||||
var index = 0
|
||||
while (params.contains(index)) {
|
||||
options.add(params.get(index))
|
||||
index++
|
||||
}
|
||||
return@Function options.random() // Can throw an exception if they specified no args. They'd be idiots though.
|
||||
},
|
||||
"curve" to java.util.function.Function { params ->
|
||||
val curveName = params.getString(0)
|
||||
val curve = WaveFunctions.functions[curveName] ?: throw IllegalArgumentException("Unknown curve: $curveName")
|
||||
return@Function ObjectValue(curve)
|
||||
},
|
||||
"array" to java.util.function.Function { params ->
|
||||
val values = params.params
|
||||
val array = ArrayStruct(hashMapOf())
|
||||
values.forEachIndexed { index, moValue -> array.setDirectly("$index", moValue) }
|
||||
return@Function array
|
||||
}
|
||||
)
|
||||
val biomeFunctions = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
|
||||
val worldFunctions = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
|
||||
@@ -110,6 +134,11 @@ object MoLangFunctions {
|
||||
return this
|
||||
}
|
||||
|
||||
fun <T : QueryStruct> T.addFunction(name: String, function: java.util.function.Function<MoParams, Any>): T {
|
||||
this.functions[name] = function
|
||||
return this
|
||||
}
|
||||
|
||||
fun MoLangEnvironment.getQueryStruct(name: String = "query") = structs.getOrPut(name) { QueryStruct(hashMapOf()) } as QueryStruct
|
||||
|
||||
fun MoLangRuntime.setup(): MoLangRuntime {
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.moves
|
||||
|
||||
import com.bedrockk.molang.runtime.struct.MoStruct
|
||||
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.MoLangFunctions.addFunction
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectTimeline
|
||||
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
|
||||
import com.cobblemon.mod.common.api.moves.categories.DamageCategory
|
||||
import com.cobblemon.mod.common.api.types.ElementalType
|
||||
@@ -45,8 +51,22 @@ open class MoveTemplate(
|
||||
val pp: Int,
|
||||
val priority: Int,
|
||||
val critRatio: Double,
|
||||
val effectChances: Array<Double>
|
||||
val effectChances: Array<Double>,
|
||||
val actionEffect: ActionEffectTimeline?
|
||||
) {
|
||||
val struct: MoStruct by lazy {
|
||||
QueryStruct(hashMapOf())
|
||||
.addFunction("name") { StringValue(name) }
|
||||
.addFunction("type") { StringValue(elementalType.name) }
|
||||
.addFunction("damage_category") { StringValue(damageCategory.name) }
|
||||
.addFunction("power") { DoubleValue(power) }
|
||||
.addFunction("target") { StringValue(target.name) }
|
||||
.addFunction("accuracy") { DoubleValue(accuracy) }
|
||||
.addFunction("pp") { DoubleValue(pp) }
|
||||
.addFunction("priority") { DoubleValue(priority) }
|
||||
.addFunction("crit_ratio") { DoubleValue(critRatio) }
|
||||
}
|
||||
|
||||
|
||||
val displayName: MutableText
|
||||
get() = lang("move.$name")
|
||||
@@ -65,7 +85,8 @@ open class MoveTemplate(
|
||||
pp = 5,
|
||||
priority = 0,
|
||||
critRatio = 0.0,
|
||||
effectChances = emptyArray()
|
||||
effectChances = emptyArray(),
|
||||
actionEffect = null
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -10,12 +10,15 @@ package com.cobblemon.mod.common.api.moves
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.data.DataRegistry
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectTimeline
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
|
||||
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.api.types.ElementalTypes
|
||||
import com.cobblemon.mod.common.battles.MoveTarget
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownService
|
||||
import com.cobblemon.mod.common.net.messages.client.data.MovesRegistrySyncPacket
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonObject
|
||||
@@ -72,7 +75,18 @@ object Moves : DataRegistry {
|
||||
effectChances += secondaryMember.get("chance").asDouble
|
||||
}
|
||||
}
|
||||
val move = MoveTemplate(id, num, elementalType, damageCategory, power, target, accuracy, pp, priority, critRatio, effectChances.toTypedArray())
|
||||
val actionEffect = ActionEffects.actionEffects[id.asIdentifierDefaultingNamespace()]
|
||||
?: run {
|
||||
ActionEffects.actionEffects["generic_move".asIdentifierDefaultingNamespace()]
|
||||
// if (damageCategory == DamageCategories.STATUS) {
|
||||
// ActionEffects.actionEffects[cobblemonResource("status")]
|
||||
// } else {
|
||||
// val type = elementalType.name.lowercase()
|
||||
// val category = damageCategory.name.lowercase()
|
||||
// ActionEffects.actionEffects["${category}_$type".asIdentifierDefaultingNamespace()]
|
||||
// }
|
||||
}
|
||||
val move = MoveTemplate(id, num, elementalType, damageCategory, power, target, accuracy, pp, priority, critRatio, effectChances.toTypedArray(), actionEffect)
|
||||
this.register(move)
|
||||
} catch (e: Exception) {
|
||||
Cobblemon.LOGGER.error("Caught exception trying to resolve the move '{}'", id, e)
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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.api.moves.animations
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.struct.VariableStruct
|
||||
import com.cobblemon.mod.common.api.moves.animations.keyframes.ActionEffectKeyframe
|
||||
import com.cobblemon.mod.common.api.scheduling.ScheduledTask
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import net.minecraft.entity.Entity
|
||||
|
||||
/**
|
||||
* An action effect will run and execute a series of 'keyframes', with each running once
|
||||
* the previous has completed. The entire effect is considered complete for the given context
|
||||
* once all the 'holds' provided in the context have been cleared or once every keyframe
|
||||
* has finished.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since October 20th, 2023
|
||||
*/
|
||||
class ActionEffectTimeline(
|
||||
val timeline: List<ActionEffectKeyframe> = mutableListOf(),
|
||||
val condition: Expression = "true".asExpression()
|
||||
) {
|
||||
companion object {
|
||||
val NONE = ActionEffectTimeline()
|
||||
}
|
||||
|
||||
fun run(context: ActionEffectContext): CompletableFuture<Unit> {
|
||||
return if (timeline.isEmpty() || !context.runtime.resolveBoolean(condition)) {
|
||||
CompletableFuture.completedFuture(Unit)
|
||||
} else {
|
||||
val finalFuture = CompletableFuture<Unit>()
|
||||
// .toList copy because I'm paranoid about iterators being trying to share between identical effects playing
|
||||
chainKeyframes(context, timeline.toList().iterator(), finalFuture)
|
||||
finalFuture
|
||||
}
|
||||
}
|
||||
|
||||
fun chainKeyframes(context: ActionEffectContext, iterator: Iterator<ActionEffectKeyframe>, finalFuture: CompletableFuture<Unit>) {
|
||||
if (!iterator.hasNext()) {
|
||||
finalFuture.complete(Unit)
|
||||
} else {
|
||||
val keyframe = iterator.next()
|
||||
context.currentKeyframes.add(keyframe)
|
||||
keyframe.play(context)
|
||||
.thenRun { context.currentKeyframes.remove(keyframe) }
|
||||
.thenApply { chainKeyframes(context, iterator, finalFuture) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ActionEffectContext(
|
||||
val actionEffect: ActionEffectTimeline,
|
||||
val holds: MutableSet<String> = mutableSetOf(),
|
||||
val providers: MutableList<Any> = mutableListOf(),
|
||||
val runtime: MoLangRuntime,
|
||||
var canBeInterrupted: Boolean = false,
|
||||
var interrupted: Boolean = false,
|
||||
var currentKeyframes: MutableList<ActionEffectKeyframe> = mutableListOf()
|
||||
) {
|
||||
inline fun <reified T> findOneProvider() = providers.filterIsInstance<T>().firstOrNull()
|
||||
|
||||
|
||||
}
|
||||
|
||||
class UsersProvider(users: List<Entity>): EntityProvider {
|
||||
override val entities = users
|
||||
constructor(vararg users: Entity): this(users.toList())
|
||||
}
|
||||
|
||||
class TargetsProvider(targets: List<Entity>): EntityProvider {
|
||||
override val entities = targets
|
||||
constructor(vararg targets: Entity): this(targets.toList())
|
||||
}
|
||||
|
||||
interface EntityProvider {
|
||||
val entities: List<Entity>
|
||||
}
|
||||
|
||||
//class MoveAnimationKeyframe(
|
||||
// val sound: Identifier? = null,
|
||||
// val animation: List<AnimationWithMoments> = listOf(),
|
||||
// val effects: List<ActionEffect> = listOf()
|
||||
//)
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.api.moves.animations
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.moves.animations.keyframes.*
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stat
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.pokemon.adapters.CobblemonStatTypeAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.*
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import java.awt.Color
|
||||
import net.minecraft.predicate.NumberRange
|
||||
import net.minecraft.resource.ResourceType
|
||||
import net.minecraft.server.network.ServerPlayerEntity
|
||||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.util.math.Box
|
||||
import net.minecraft.util.math.Vec3d
|
||||
|
||||
/**
|
||||
* Data registry containing all of the [ActionEffectTimeline]s that can be triggered from various actions.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since October 21st, 2023
|
||||
*/
|
||||
object ActionEffects : JsonDataRegistry<ActionEffectTimeline> {
|
||||
override val id: Identifier = cobblemonResource("action_effects")
|
||||
override val type: ResourceType = ResourceType.SERVER_DATA
|
||||
override val observable = SimpleObservable<ActionEffects>()
|
||||
|
||||
init {
|
||||
ActionEffectKeyframe.register<AnimationActionEffectKeyframe>("animation")
|
||||
ActionEffectKeyframe.register<EntityMoLangActionEffectKeyframe>("entity_molang")
|
||||
ActionEffectKeyframe.register<MoLangActionEffectKeyframe>("molang")
|
||||
ActionEffectKeyframe.register<ParallelActionEffectKeyframe>("parallel")
|
||||
ActionEffectKeyframe.register<CanInterruptActionEffectKeyframe>("can_interrupt")
|
||||
ActionEffectKeyframe.register<CannotInterruptActionEffectKeyframe>("cannot_interrupt")
|
||||
ActionEffectKeyframe.register<RemoveHoldsActionEffectKeyframe>("remove_holds")
|
||||
ActionEffectKeyframe.register<AddHoldsActionEffectKeyframe>("add_holds")
|
||||
ActionEffectKeyframe.register<MoveToTargetActionEffectKeyframe>("move_to_target")
|
||||
ActionEffectKeyframe.register<ReturnToPositionActionEffectKeyframe>("return_to_position")
|
||||
ActionEffectKeyframe.register<PauseActionEffectKeyframe>("pause")
|
||||
ActionEffectKeyframe.register<SavePositionActionEffectKeyframe>("save_position")
|
||||
ActionEffectKeyframe.register<ForkActionEffectKeyframe>("fork")
|
||||
ActionEffectKeyframe.register<SequenceActionEffectKeyframe>("sequence")
|
||||
ActionEffectKeyframe.register<RunActionEffectKeyframe>("run_action_effect")
|
||||
ActionEffectKeyframe.register<EntityParticlesActionEffectKeyframe>("entity_particles")
|
||||
ActionEffectKeyframe.register<EntitySoundActionEffectKeyframe>("entity_sound")
|
||||
}
|
||||
|
||||
override val gson = GsonBuilder()
|
||||
.disableHtmlEscaping()
|
||||
.setPrettyPrinting()
|
||||
.registerTypeAdapter(ActionEffectKeyframe::class.java, ActionEffectKeyframeAdapter)
|
||||
.registerTypeAdapter(NumberRange.FloatRange::class.java, FloatNumberRangeAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(Collection::class.java, Box::class.java).type, BoxCollectionAdapter)
|
||||
.registerTypeAdapter(Box::class.java, BoxAdapter)
|
||||
.registerTypeAdapter(Vec3d::class.java, VerboseVec3dAdapter)
|
||||
.registerTypeAdapter(Identifier::class.java, IdentifierAdapter)
|
||||
.registerTypeAdapter(IntRange::class.java, VerboseIntRangeAdapter)
|
||||
.registerTypeAdapter(Color::class.java, LiteralHexColorAdapter)
|
||||
.registerTypeAdapter(Stat::class.java, CobblemonStatTypeAdapter)
|
||||
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
|
||||
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
|
||||
.registerTypeAdapter(
|
||||
TypeToken.getParameterized(
|
||||
TypeToken.get(List::class.java).type,
|
||||
TypeToken.get(ActionEffectKeyframe::class.java).type
|
||||
).type,
|
||||
SingleToPluralAdapter(ActionEffectKeyframe::class.java) { it }
|
||||
)
|
||||
.create()
|
||||
|
||||
override val typeToken: TypeToken<ActionEffectTimeline> = TypeToken.get(ActionEffectTimeline::class.java)
|
||||
override val resourcePath = "action_effects"
|
||||
|
||||
val actionEffects = mutableMapOf<Identifier, ActionEffectTimeline>()
|
||||
override fun reload(data: Map<Identifier, ActionEffectTimeline>) {
|
||||
actionEffects.clear()
|
||||
actionEffects.putAll(data)
|
||||
observable.emit(this)
|
||||
}
|
||||
|
||||
override fun sync(player: ServerPlayerEntity) {}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.api.moves.animations.keyframes
|
||||
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
/**
|
||||
* A single component of an action effect. When played it must return a completable future that completes
|
||||
* when the action this keyframe represents is finished. [interrupt] is provided for when something successfully
|
||||
* interrupts the effect so that if any cancellation logic is necessary, it can be provided.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since October 26th, 2023
|
||||
*/
|
||||
interface ActionEffectKeyframe {
|
||||
companion object {
|
||||
val types = mutableMapOf<String, Class<out ActionEffectKeyframe>>()
|
||||
|
||||
inline fun <reified T : ActionEffectKeyframe> register(type: String) {
|
||||
types[type] = T::class.java
|
||||
}
|
||||
}
|
||||
|
||||
fun play(context: ActionEffectContext): CompletableFuture<Unit>
|
||||
fun interrupt(context: ActionEffectContext) {}
|
||||
fun skip(): CompletableFuture<Unit> = CompletableFuture.completedFuture(Unit)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.moves.animations.keyframes
|
||||
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
/**
|
||||
* An [ActionEffectKeyframe] that adds some holds from the context for future code to recognize as blocking some
|
||||
* functionality from proceeding.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since October 27th, 2023
|
||||
*/
|
||||
class AddHoldsActionEffectKeyframe : ActionEffectKeyframe {
|
||||
val holds = mutableSetOf<String>()
|
||||
override fun play(context: ActionEffectContext): CompletableFuture<Unit> {
|
||||
context.holds.addAll(holds)
|
||||
return CompletableFuture.completedFuture(Unit)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.api.moves.animations.keyframes
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
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.util.asExpressionLike
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import net.minecraft.server.world.ServerWorld
|
||||
|
||||
class AnimationActionEffectKeyframe : ConditionalActionEffectKeyframe(), EntityConditionalActionEffectKeyframe {
|
||||
var delay = "0".asExpressionLike()
|
||||
var visibilityRange = 200
|
||||
var animation: Set<String> = setOf("physical")
|
||||
var variables: List<Expression> = listOf()
|
||||
override val entityCondition = "q.entity.is_user".asExpressionLike()
|
||||
|
||||
override fun playWhenTrue(context: ActionEffectContext): CompletableFuture<Unit> {
|
||||
val entities = context.providers
|
||||
.filterIsInstance<EntityProvider>()
|
||||
.flatMap { prov -> prov.entities.filter { test(context, it, isUser = prov is UsersProvider) } }
|
||||
|
||||
val expressions = variables.map { it.originalString }.toSet()
|
||||
|
||||
// Treat them as expressions if possible but otherwise yeah just send them as strings
|
||||
val animation = animation.map {
|
||||
try {
|
||||
it.asExpressionLike().resolveString(context.runtime).takeIf { it != "0.0" } ?: it
|
||||
} catch (e: Exception) {
|
||||
it
|
||||
}
|
||||
}.toSet()
|
||||
|
||||
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)
|
||||
players.forEach { it.sendPacket(pkt) }
|
||||
}
|
||||
|
||||
return delayedFuture(seconds = delay.resolveFloat(context.runtime), serverThread = true)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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.api.moves.animations.keyframes
|
||||
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class CanInterruptActionEffectKeyframe : ConditionalActionEffectKeyframe() {
|
||||
override fun playWhenTrue(context: ActionEffectContext): CompletableFuture<Unit> {
|
||||
context.canBeInterrupted = true
|
||||
return CompletableFuture.completedFuture(Unit)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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.api.moves.animations.keyframes
|
||||
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class CannotInterruptActionEffectKeyframe : ConditionalActionEffectKeyframe() {
|
||||
override fun playWhenTrue(context: ActionEffectContext): CompletableFuture<Unit> {
|
||||
context.canBeInterrupted = true
|
||||
return CompletableFuture.completedFuture(Unit)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.moves.animations.keyframes
|
||||
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
|
||||
import com.cobblemon.mod.common.util.asExpressionLike
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
abstract class ConditionalActionEffectKeyframe : ActionEffectKeyframe {
|
||||
var condition: ExpressionLike = "true".asExpressionLike()
|
||||
override fun play(context: ActionEffectContext): CompletableFuture<Unit> {
|
||||
return if (condition.resolveBoolean(context.runtime)) {
|
||||
playWhenTrue(context)
|
||||
} else {
|
||||
skip()
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun playWhenTrue(context: ActionEffectContext): CompletableFuture<Unit>
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.api.moves.animations.keyframes
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
* An action effect keyframe that plays for all entities for which the condition is true.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since January 21st, 2024
|
||||
*/
|
||||
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") {
|
||||
QueryStruct(hashMapOf())
|
||||
.addFunction("uuid") { StringValue(entity.uuidAsString) }
|
||||
.addFunction("is_user") { DoubleValue(isUser) }
|
||||
}
|
||||
return entityCondition.resolveBoolean(context.runtime)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.api.moves.animations.keyframes
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
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.effect.RunPosableMoLangPacket
|
||||
import com.cobblemon.mod.common.util.asExpressionLike
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import net.minecraft.server.world.ServerWorld
|
||||
|
||||
/**
|
||||
* An action effect keyframe that will run client-side MoLang for all entities that fit the entity condition.
|
||||
*
|
||||
* This MoLang executes within that entity's runtime, meaning you can trigger animations or locational particles
|
||||
* or sounds.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since October 30th, 2023
|
||||
*/
|
||||
class EntityMoLangActionEffectKeyframe : ConditionalActionEffectKeyframe(), EntityConditionalActionEffectKeyframe {
|
||||
var delay = "0".asExpressionLike()
|
||||
val expressions = mutableSetOf<String>()
|
||||
val visibilityRange = 200
|
||||
override val entityCondition: ExpressionLike = "q.entity.is_user".asExpressionLike()
|
||||
|
||||
override fun playWhenTrue(context: ActionEffectContext): CompletableFuture<Unit> {
|
||||
val entities = context.providers
|
||||
.filterIsInstance<EntityProvider>()
|
||||
.flatMap { prov -> prov.entities.filter { test(context, it, isUser = prov is UsersProvider) } }
|
||||
|
||||
for (entity in entities) {
|
||||
val world = entity.world as ServerWorld
|
||||
val players = world.getPlayers { it.distanceTo(entity) <= visibilityRange }
|
||||
val pkt = RunPosableMoLangPacket(entityId = entity.id, expressions = expressions)
|
||||
players.forEach { it.sendPacket(pkt) }
|
||||
}
|
||||
|
||||
return delayedFuture(seconds = delay.resolveFloat(context.runtime), serverThread = true)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.api.moves.animations.keyframes
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
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.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
|
||||
import com.cobblemon.mod.common.util.asExpressionLike
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import net.minecraft.server.world.ServerWorld
|
||||
|
||||
/**
|
||||
* Spawns particles on the entity based on the given particle effect and locator.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since January 21st, 2024
|
||||
*/
|
||||
class EntityParticlesActionEffectKeyframe : ConditionalActionEffectKeyframe(), EntityConditionalActionEffectKeyframe {
|
||||
override val entityCondition = "q.entity.is_user".asExpressionLike()
|
||||
var effect: String? = null
|
||||
var locator: String = "root"
|
||||
val delay: ExpressionLike = "0".asExpressionLike()
|
||||
val visibilityRange = 200
|
||||
|
||||
override fun playWhenTrue(context: ActionEffectContext): CompletableFuture<Unit> {
|
||||
val entities = context.providers
|
||||
.filterIsInstance<EntityProvider>()
|
||||
.flatMap { prov -> prov.entities.filter { test(context, it, isUser = prov is UsersProvider) } }
|
||||
|
||||
val effectIdentifier = try {
|
||||
effect?.asExpressionLike()?.resolveString(context.runtime)
|
||||
} catch (e: Exception) {
|
||||
effect
|
||||
}?.asIdentifierDefaultingNamespace() ?: return skip()
|
||||
|
||||
entities.filter { it is Poseable }.forEach { entity ->
|
||||
val packet = SpawnSnowstormEntityParticlePacket(effectIdentifier, entity.id, locator)
|
||||
val players = (entity.world as ServerWorld).getPlayers { it.distanceTo(entity) <= visibilityRange }
|
||||
packet.sendToPlayers(players)
|
||||
}
|
||||
|
||||
return delayedFuture(seconds = delay.resolveFloat(context.runtime), serverThread = true)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.api.moves.animations.keyframes
|
||||
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
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.util.asExpressionLike
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import net.minecraft.registry.RegistryKeys
|
||||
|
||||
/**
|
||||
* An action effect keyframe that plays a positioned sound for all matching entities.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since January 21st, 2024
|
||||
*/
|
||||
class EntitySoundActionEffectKeyframe : ConditionalActionEffectKeyframe(), EntityConditionalActionEffectKeyframe {
|
||||
override val entityCondition = "q.entity.is_user".asExpressionLike()
|
||||
var sound: String? = null
|
||||
val delay: ExpressionLike = "0".asExpressionLike()
|
||||
|
||||
override fun playWhenTrue(context: ActionEffectContext): CompletableFuture<Unit> {
|
||||
val entities = context.providers
|
||||
.filterIsInstance<EntityProvider>()
|
||||
.flatMap { prov -> prov.entities.filter { test(context, it, isUser = prov is UsersProvider) } }
|
||||
|
||||
val soundIdentifier = try {
|
||||
sound?.asExpressionLike()?.resolveString(context.runtime)?.takeIf { it != "0.0" } ?: sound
|
||||
} catch (e: Exception) {
|
||||
sound
|
||||
}?.asIdentifierDefaultingNamespace() ?: return skip()
|
||||
|
||||
entities.forEach { entity ->
|
||||
val soundEvent = entity.world.registryManager.get(RegistryKeys.SOUND_EVENT).get(soundIdentifier) ?: return skip()
|
||||
entity.playSound(soundEvent, 1f, 1f)
|
||||
}
|
||||
|
||||
return delayedFuture(seconds = delay.resolveFloat(context.runtime), serverThread = true)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.api.moves.animations.keyframes
|
||||
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
|
||||
import com.cobblemon.mod.common.util.asExpressionLike
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class ForkActionEffectKeyframe(
|
||||
val condition: ExpressionLike = "true".asExpressionLike(),
|
||||
val ifTrue: List<ActionEffectKeyframe> = listOf(),
|
||||
val ifFalse: List<ActionEffectKeyframe> = listOf()
|
||||
) : ActionEffectKeyframe {
|
||||
override fun play(context: ActionEffectContext): CompletableFuture<Unit> {
|
||||
val future = CompletableFuture<Unit>()
|
||||
/*
|
||||
* Using .toList() before running chainKeyframes because iterators can be weird about sharing,
|
||||
* .toList() creates a copy of the list and then grabs its iterator so much safer (three parachutes)
|
||||
*/
|
||||
if (!condition.resolveBoolean(context.runtime)) {
|
||||
context.actionEffect.chainKeyframes(context, ifFalse.toList().iterator(), future)
|
||||
} else {
|
||||
context.actionEffect.chainKeyframes(context, ifTrue.toList().iterator(), future)
|
||||
}
|
||||
|
||||
return future
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.api.moves.animations.keyframes
|
||||
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
|
||||
import com.cobblemon.mod.common.api.scheduling.delayedFuture
|
||||
import com.cobblemon.mod.common.util.asExpressionLike
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
/**
|
||||
* An action effect keyframe that simply executes some MoLang on the effect context's runtime.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since January 21st, 2024
|
||||
*/
|
||||
class MoLangActionEffectKeyframe : ActionEffectKeyframe {
|
||||
val expressions: ExpressionLike = "0".asExpressionLike()
|
||||
val delay: ExpressionLike = "0".asExpressionLike()
|
||||
override fun play(context: ActionEffectContext): CompletableFuture<Unit> {
|
||||
expressions.resolve(context.runtime)
|
||||
return delayedFuture(seconds = delay.resolveFloat(context.runtime), serverThread = true)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.api.moves.animations.keyframes
|
||||
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectTimeline
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
|
||||
import com.cobblemon.mod.common.api.moves.animations.TargetsProvider
|
||||
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
|
||||
import com.cobblemon.mod.common.api.scheduling.afterOnServer
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonNavigation
|
||||
import com.cobblemon.mod.common.util.asExpressionLike
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sqrt
|
||||
import net.minecraft.util.Identifier
|
||||
|
||||
class MoveToTargetActionEffectKeyframe : ActionEffectKeyframe {
|
||||
val speed = 1F
|
||||
val timeout = "4".asExpressionLike()
|
||||
var proximity = -1F
|
||||
val timeoutActionEffect: Identifier? = null
|
||||
|
||||
override fun play(context: ActionEffectContext): CompletableFuture<Unit> {
|
||||
val user = context.findOneProvider<UsersProvider>()?.entities?.firstOrNull() as? PokemonEntity ?: return CompletableFuture.completedFuture(Unit)
|
||||
val target = context.findOneProvider<TargetsProvider>()?.entities?.firstOrNull() ?: return CompletableFuture.completedFuture(Unit)
|
||||
|
||||
val future = CompletableFuture<Unit>()
|
||||
|
||||
var timedOut = false
|
||||
|
||||
val proximity = this.proximity.takeIf { it != -1F } ?: (sqrt(2 * user.boundingBox.xLength.pow(2)) + 1.5F + sqrt(2 * target.boundingBox.xLength.pow(2))).toFloat()
|
||||
|
||||
if (target.distanceTo(user) !in proximity..20F) {
|
||||
future.complete(Unit)
|
||||
return future
|
||||
}
|
||||
|
||||
val timeoutEffect = timeoutActionEffect?.let { ActionEffects.actionEffects[it] } ?: ActionEffectTimeline.NONE
|
||||
|
||||
val nav = user.navigation
|
||||
val navContext = PokemonNavigation.NavigationContext(
|
||||
destinationProximity = proximity,
|
||||
onArrival = {
|
||||
if (!future.isDone && !timedOut) {
|
||||
future.complete(Unit)
|
||||
}
|
||||
},
|
||||
onCannotReach = {
|
||||
if (!future.isDone && !timedOut) {
|
||||
timedOut = true
|
||||
timeoutEffect.run(context).thenApply { future.complete(Unit) }
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
afterOnServer(seconds = timeout.resolveFloat(context.runtime)) {
|
||||
if (!future.isDone && !timedOut) {
|
||||
timedOut = true
|
||||
timeoutEffect.run(context).thenApply { future.complete(Unit) }
|
||||
nav.stop()
|
||||
}
|
||||
}
|
||||
|
||||
nav.startMovingTo(target.x, target.y, target.z, speed.toDouble(), navContext)
|
||||
return future
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.moves.animations.keyframes
|
||||
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class ParallelActionEffectKeyframe : ConditionalActionEffectKeyframe() {
|
||||
var keyframes: MutableList<ActionEffectKeyframe> = mutableListOf()
|
||||
|
||||
override fun playWhenTrue(context: ActionEffectContext): CompletableFuture<Unit> {
|
||||
return CompletableFuture.allOf(
|
||||
*keyframes.map {
|
||||
context.currentKeyframes.add(it)
|
||||
it.play(context).thenRun { context.currentKeyframes.remove(it) }
|
||||
}.toTypedArray()
|
||||
).thenApply {} // Map Void -> Unit at the end
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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.api.moves.animations.keyframes
|
||||
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
|
||||
import com.cobblemon.mod.common.api.scheduling.delayedFuture
|
||||
import com.cobblemon.mod.common.util.asExpressionLike
|
||||
|
||||
class PauseActionEffectKeyframe : ConditionalActionEffectKeyframe() {
|
||||
val pause = "1".asExpressionLike()
|
||||
override fun playWhenTrue(context: ActionEffectContext) = delayedFuture(seconds = pause.resolveFloat(context.runtime), serverThread = true)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.api.moves.animations.keyframes
|
||||
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
|
||||
import com.cobblemon.mod.common.api.scheduling.delayedFuture
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
/**
|
||||
* An [ActionEffectKeyframe] that clears some holds from the context, potentially marking the effect as
|
||||
* complete as far as any blocked actions are concerned. If no holds are specified, then all holds are
|
||||
* removed.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since October 27th, 2023
|
||||
*/
|
||||
class RemoveHoldsActionEffectKeyframe : ActionEffectKeyframe {
|
||||
val delay = 0F
|
||||
val holds = mutableSetOf<String>()
|
||||
override fun play(context: ActionEffectContext): CompletableFuture<Unit> {
|
||||
return delayedFuture(seconds = delay, serverThread = true).thenApply {
|
||||
if (holds.isEmpty()) {
|
||||
context.holds.clear()
|
||||
} else {
|
||||
context.holds.removeAll(holds)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.api.moves.animations.keyframes
|
||||
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectTimeline
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
|
||||
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
|
||||
import com.cobblemon.mod.common.api.scheduling.after
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonNavigation
|
||||
import com.cobblemon.mod.common.util.asExpressionLike
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.util.math.Vec3d
|
||||
|
||||
class ReturnToPositionActionEffectKeyframe : ActionEffectKeyframe {
|
||||
val speed = 1F
|
||||
val timeout = "4".asExpressionLike()
|
||||
val timeoutActionEffect: Identifier? = null
|
||||
|
||||
override fun play(context: ActionEffectContext): CompletableFuture<Unit> {
|
||||
val user = context.findOneProvider<UsersProvider>()?.entities?.firstOrNull() as? PokemonEntity ?: return skip()
|
||||
val future = CompletableFuture<Unit>()
|
||||
|
||||
val pos = (context.runtime.environment.getValue(setOf("${user.uuidAsString}-pos").iterator())?.value() as? ObjectValue<*>)?.obj as? Vec3d ?: return skip()
|
||||
|
||||
var timedOut = false
|
||||
|
||||
if (pos.distanceTo(user.pos) > 20) {
|
||||
future.complete(Unit)
|
||||
return future
|
||||
}
|
||||
|
||||
val timeoutEffect = timeoutActionEffect?.let { ActionEffects.actionEffects[it] } ?: ActionEffectTimeline.NONE
|
||||
|
||||
val nav = user.navigation
|
||||
val navContext = PokemonNavigation.NavigationContext(
|
||||
onArrival = {
|
||||
if (!future.isDone && !timedOut) {
|
||||
future.complete(Unit)
|
||||
}
|
||||
},
|
||||
onCannotReach = {
|
||||
if (!future.isDone && !timedOut) {
|
||||
timedOut = true
|
||||
timeoutEffect.run(context).thenApply { future.complete(Unit) }
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
after(seconds = timeout.resolveFloat(context.runtime), serverThread = true) {
|
||||
if (!future.isDone && !timedOut) {
|
||||
timedOut = true
|
||||
timeoutEffect.run(context).thenApply { future.complete(Unit) }
|
||||
nav.stop()
|
||||
}
|
||||
}
|
||||
|
||||
nav.startMovingTo(pos.x, pos.y, pos.z, speed.toDouble(), navContext)
|
||||
return future
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.api.moves.animations.keyframes
|
||||
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import net.minecraft.util.Identifier
|
||||
|
||||
/**
|
||||
* An action effect keyframe that simply runs another action effect based on an identifier. This action
|
||||
* effect is marked complete only when the action effect it runs is complete unless [waitForActionEffect] is
|
||||
* false.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since January 21st, 2024
|
||||
*/
|
||||
class RunActionEffectKeyframe : ConditionalActionEffectKeyframe() {
|
||||
val actionEffect: Identifier? = null
|
||||
val waitForActionEffect = true
|
||||
override fun playWhenTrue(context: ActionEffectContext): CompletableFuture<Unit> {
|
||||
return if (actionEffect != null) {
|
||||
ActionEffects.actionEffects[actionEffect]?.run(context)?.takeIf { waitForActionEffect } ?: skip()
|
||||
} else {
|
||||
skip()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.api.moves.animations.keyframes
|
||||
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
|
||||
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class SavePositionActionEffectKeyframe: ConditionalActionEffectKeyframe() {
|
||||
override fun playWhenTrue(context: ActionEffectContext): CompletableFuture<Unit> {
|
||||
val user = context.findOneProvider<UsersProvider>()?.entities?.firstOrNull() ?: return skip()
|
||||
context.runtime.environment.setSimpleVariable("${user.uuidAsString}-pos", ObjectValue(user.pos))
|
||||
return skip()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.api.moves.animations.keyframes
|
||||
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
/**
|
||||
* A sequence of action effects that are dependent on a condition.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since October 29th, 2023
|
||||
*/
|
||||
class SequenceActionEffectKeyframe(val keyframes: List<ActionEffectKeyframe> = listOf()) : ConditionalActionEffectKeyframe() {
|
||||
override fun playWhenTrue(context: ActionEffectContext): CompletableFuture<Unit> {
|
||||
val future = CompletableFuture<Unit>()
|
||||
context.actionEffect.chainKeyframes(context, keyframes.toList().iterator(), future)
|
||||
return future
|
||||
}
|
||||
}
|
||||
@@ -8,13 +8,27 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.scheduling
|
||||
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
@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)
|
||||
}
|
||||
|
||||
fun delayedFuture(ticks: Int = 0, seconds: Float = 0F, serverThread: Boolean = false): CompletableFuture<Unit> {
|
||||
val future = CompletableFuture<Unit>()
|
||||
if (ticks == 0 && seconds == 0F) {
|
||||
future.complete(Unit)
|
||||
} else {
|
||||
after(ticks = ticks, seconds = seconds, serverThread = serverThread) { future.complete(Unit) }
|
||||
}
|
||||
return future
|
||||
}
|
||||
|
||||
/**
|
||||
* Delayed task is created to run on the main thread. This is for when the task
|
||||
* being completed after the delay does things like entity removal or other thread-unsafe actions.
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.mojang.serialization.DynamicOps
|
||||
import com.mojang.serialization.codecs.ListCodec
|
||||
import com.mojang.serialization.codecs.PrimitiveCodec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import com.mojang.serialization.codecs.UnboundedMapCodec
|
||||
import kotlin.math.floor
|
||||
import net.minecraft.network.PacketByteBuf
|
||||
|
||||
@@ -44,6 +45,7 @@ interface MoLangCurve : CodecMapped {
|
||||
registerSubtype(CurveType.LINEAR, LinearMoLangCurve::class.java, LinearMoLangCurve.CODEC)
|
||||
registerSubtype(CurveType.CATMULL_ROM, CatmullRomMoLangCurve::class.java, CatmullRomMoLangCurve.CODEC)
|
||||
registerSubtype(CurveType.BEZIER, BezierMoLangCurve::class.java, BezierMoLangCurve.CODEC)
|
||||
registerSubtype(CurveType.BEZIER_CHAIN, BezierChainMoLangCurve::class.java, BezierChainMoLangCurve.CODEC)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +166,103 @@ class CatmullRomMoLangCurve(
|
||||
}
|
||||
}
|
||||
|
||||
class BezierChainMoLangCurve(
|
||||
override var name: String = "variable",
|
||||
override var input: Expression = NumberExpression(0.5),
|
||||
var nodes: Map<Double, BezierChainNode> = emptyMap()
|
||||
) : MoLangCurve {
|
||||
var nodePairs: List<BezierChainNodePair> = emptyList()
|
||||
|
||||
fun deriveNodePairs() {
|
||||
val nodePairs = mutableListOf<BezierChainNodePair>()
|
||||
val sortedNodes = nodes.entries.sortedBy { it.key }
|
||||
for (i in 1 until sortedNodes.size) {
|
||||
val node1 = sortedNodes[i - 1]
|
||||
val node2 = sortedNodes[i]
|
||||
|
||||
nodePairs.add(BezierChainNodePair(node1.key, node2.key, node1.value, node2.value))
|
||||
}
|
||||
this.nodePairs = nodePairs
|
||||
}
|
||||
|
||||
init {
|
||||
deriveNodePairs()
|
||||
}
|
||||
|
||||
class BezierChainNode(var value: Double, var slope: Double) {
|
||||
fun writeToBuffer(buffer: PacketByteBuf) {
|
||||
buffer.writeDouble(value)
|
||||
buffer.writeDouble(slope)
|
||||
}
|
||||
|
||||
fun readFromBuffer(buffer: PacketByteBuf) {
|
||||
value = buffer.readDouble()
|
||||
slope = buffer.readDouble()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CODEC = RecordCodecBuilder.create<BezierChainNode> { instance ->
|
||||
instance.group(
|
||||
PrimitiveCodec.DOUBLE.fieldOf("value").forGetter { it.value },
|
||||
PrimitiveCodec.DOUBLE.fieldOf("slope").forGetter { it.slope }
|
||||
).apply(instance, ::BezierChainNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BezierChainNodePair(
|
||||
val startTime: Double,
|
||||
val endTime: Double,
|
||||
node1: BezierChainNode,
|
||||
node2: BezierChainNode
|
||||
) {
|
||||
val curve: CubedBezierCurve
|
||||
init {
|
||||
val v1 = node1.value
|
||||
val v2 = node1.value + node1.slope / 3
|
||||
val v3 = node2.value - node2.slope / 3
|
||||
val v4 = node2.value
|
||||
curve = CubedBezierCurve(v1, v2, v3, v4)
|
||||
}
|
||||
}
|
||||
|
||||
class BezierChainPointData(val time: Double, val value: Double, val slope: Double)
|
||||
|
||||
companion object {
|
||||
val CODEC: Codec<BezierChainMoLangCurve> = RecordCodecBuilder.create { instance ->
|
||||
instance.group(
|
||||
PrimitiveCodec.STRING.fieldOf("type").forGetter { it.type.name },
|
||||
PrimitiveCodec.STRING.fieldOf("name").forGetter { it.name },
|
||||
EXPRESSION_CODEC.fieldOf("input").forGetter { it.input },
|
||||
UnboundedMapCodec(PrimitiveCodec.DOUBLE, BezierChainNode.CODEC).fieldOf("nodes").forGetter { it.nodes }
|
||||
).apply(instance) { _, name, input, nodes -> BezierChainMoLangCurve(name, input, nodes) }
|
||||
}
|
||||
}
|
||||
|
||||
override val type: CurveType = CurveType.BEZIER_CHAIN
|
||||
override fun resolve(runtime: MoLangRuntime, inputValue: Double): Double {
|
||||
val position = inputValue.coerceIn(0.0, 1.0)
|
||||
val nodePair = nodePairs.last { position >= it.startTime }
|
||||
val curve = nodePair.curve
|
||||
val t = (position - nodePair.startTime) / (nodePair.endTime - nodePair.startTime)
|
||||
return curve.getY(t)
|
||||
}
|
||||
|
||||
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
|
||||
override fun readFromBuffer(buffer: PacketByteBuf) {
|
||||
name = buffer.readString()
|
||||
input = MoLang.createParser(buffer.readString()).parseExpression()
|
||||
nodes = buffer.readMap({ buffer.readDouble() }, { BezierChainNode(0.0, 0.0).also { it.readFromBuffer(buffer) } })
|
||||
deriveNodePairs()
|
||||
}
|
||||
|
||||
override fun writeToBuffer(buffer: PacketByteBuf) {
|
||||
buffer.writeString(name)
|
||||
buffer.writeString(input.getString())
|
||||
buffer.writeMap(nodes, { pb, key -> pb.writeDouble(key) }, { pb, value -> value.writeToBuffer(pb) })
|
||||
}
|
||||
}
|
||||
|
||||
class BezierMoLangCurve(
|
||||
override var name: String = "variable",
|
||||
override var input: Expression = NumberExpression(0.5),
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.api.data.ArbitrarilyMappedSerializableCompanion
|
||||
import com.cobblemon.mod.common.client.particle.ParticleStorm
|
||||
import com.cobblemon.mod.common.util.codec.EXPRESSION_CODEC
|
||||
import com.cobblemon.mod.common.util.getString
|
||||
import com.cobblemon.mod.common.util.math.geometry.transformDirection
|
||||
import com.cobblemon.mod.common.util.resolveDouble
|
||||
import com.mojang.serialization.Codec
|
||||
import com.mojang.serialization.DynamicOps
|
||||
@@ -135,7 +136,13 @@ class InwardsMotionDirection : ParticleMotionDirection {
|
||||
}
|
||||
|
||||
override val type = ParticleMotionDirectionType.INWARDS
|
||||
override fun getDirectionVector(runtime: MoLangRuntime, storm: ParticleStorm, emitterPos: Vec3d, particlePos: Vec3d) = emitterPos.subtract(particlePos).normalize()
|
||||
override fun getDirectionVector(runtime: MoLangRuntime, storm: ParticleStorm, emitterPos: Vec3d, particlePos: Vec3d): Vec3d {
|
||||
return if (particlePos == emitterPos) {
|
||||
Vec3d(storm.world.random.nextDouble() - 0.5, storm.world.random.nextDouble() - 0.5, storm.world.random.nextDouble() - 0.5)
|
||||
} else {
|
||||
particlePos.subtract(emitterPos)
|
||||
}.normalize()
|
||||
}
|
||||
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
|
||||
override fun readFromBuffer(buffer: PacketByteBuf) {}
|
||||
override fun writeToBuffer(buffer: PacketByteBuf) {}
|
||||
@@ -151,7 +158,11 @@ class OutwardsMotionDirection : ParticleMotionDirection {
|
||||
|
||||
override val type = ParticleMotionDirectionType.OUTWARDS
|
||||
override fun getDirectionVector(runtime: MoLangRuntime, storm: ParticleStorm, emitterPos: Vec3d, particlePos: Vec3d): Vec3d {
|
||||
return particlePos.subtract(emitterPos).normalize()
|
||||
return if (particlePos == emitterPos) {
|
||||
Vec3d(storm.world.random.nextDouble() - 0.5, storm.world.random.nextDouble() - 0.5, storm.world.random.nextDouble() - 0.5)
|
||||
} else {
|
||||
particlePos.subtract(emitterPos)
|
||||
}.normalize()
|
||||
}
|
||||
|
||||
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
|
||||
@@ -185,7 +196,7 @@ class CustomMotionDirection(
|
||||
runtime.resolveDouble(direction.second),
|
||||
runtime.resolveDouble(direction.third)
|
||||
)
|
||||
return if (storm.effect.space.localPosition) storm.transformDirection(v) else v
|
||||
return storm.matrixWrapper.matrix.transformDirection(v)
|
||||
}
|
||||
|
||||
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
|
||||
|
||||
@@ -39,15 +39,7 @@ 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.battles.interpreter.ContextManager
|
||||
import com.cobblemon.mod.common.battles.interpreter.instructions.DamageInstruction
|
||||
import com.cobblemon.mod.common.battles.interpreter.instructions.DeprecatedInstruction
|
||||
import com.cobblemon.mod.common.battles.interpreter.instructions.DeprecatedSplitInstruction
|
||||
import com.cobblemon.mod.common.battles.interpreter.instructions.FaintInstruction
|
||||
import com.cobblemon.mod.common.battles.interpreter.instructions.IgnoredInstruction
|
||||
import com.cobblemon.mod.common.battles.interpreter.instructions.MoveInstruction
|
||||
import com.cobblemon.mod.common.battles.interpreter.instructions.TurnInstruction
|
||||
import com.cobblemon.mod.common.battles.interpreter.instructions.UnknownInstruction
|
||||
import com.cobblemon.mod.common.battles.interpreter.instructions.UpkeepInstruction
|
||||
import com.cobblemon.mod.common.battles.interpreter.instructions.*
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleHealthChangePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMadeInvalidChoicePacket
|
||||
@@ -124,12 +116,13 @@ object ShowdownInterpreter {
|
||||
updateInstructionParser["upkeep"] = { _, _, _, _ -> UpkeepInstruction() }
|
||||
updateInstructionParser["faint"] = { battle, _, message, _ -> FaintInstruction(battle, message) }
|
||||
updateInstructionParser["move"] = { _, instructionSet, message, _ -> MoveInstruction(instructionSet, message) }
|
||||
splitInstructionParser["-damage"] = { _, targetActor, _, publicMessage, privateMessage, _ ->
|
||||
DamageInstruction(targetActor, publicMessage, privateMessage)
|
||||
updateInstructionParser["-activate"] = { _, instructionSet, message, _ -> ActivateInstruction(instructionSet, message) }
|
||||
updateInstructionParser["-ability"] = { _, instructionSet, message, _ -> AbilityInstruction(instructionSet, message) }
|
||||
updateInstructionParser["-miss"] = { battle, instructionSet, message, _ -> MissInstruction(battle, instructionSet, message) }
|
||||
splitInstructionParser["-damage"] = { _, targetActor, instructionSet, publicMessage, privateMessage, _ ->
|
||||
DamageInstruction(instructionSet, targetActor, publicMessage, privateMessage)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Note '-cureteam' is a legacy thing that is only used in generation 2 and 4 mods for heal bell and aromatherapy respectively as such we can just ignore that
|
||||
|
||||
updateInstructions["|win|"] = this::handleWinInstruction
|
||||
@@ -143,11 +136,9 @@ object ShowdownInterpreter {
|
||||
updateInstructions["|-fail|"] = this::handleFailInstruction
|
||||
updateInstructions["|-start|"] = this::handleStartInstructions
|
||||
updateInstructions["|-block|"] = this::handleBlockInstructions
|
||||
updateInstructions["|-activate|"] = this::handleActivateInstructions
|
||||
updateInstructions["|-curestatus|"] = this::handleCureStatusInstruction
|
||||
updateInstructions["|-fieldstart|"] = this::handleFieldStartInstructions
|
||||
updateInstructions["|-fieldend|"] = this::handleFieldEndInstructions
|
||||
updateInstructions["|-ability|"] = this::handleAbilityInstructions
|
||||
updateInstructions["|-endability|"] = this::handleEndAbilityInstruction
|
||||
updateInstructions["|-nothing"] = { battle, _, _ ->
|
||||
battle.dispatchGo { battle.broadcastChatMessage(battleLang("nothing")) }
|
||||
@@ -169,7 +160,6 @@ object ShowdownInterpreter {
|
||||
updateInstructions["|-invertboost|"] = this::handleInvertBoostInstruction
|
||||
updateInstructions["|-status|"] = this::handleStatusInstruction
|
||||
updateInstructions["|-end|"] = this::handleEndInstruction
|
||||
updateInstructions["|-miss|"] = this::handleMissInstruction
|
||||
updateInstructions["|-hitcount|"] = this::handleHitCountInstruction
|
||||
updateInstructions["|-item|"] = this::handleItemInstruction
|
||||
updateInstructions["|-enditem|"] = this::handleEndItemInstruction
|
||||
@@ -594,20 +584,6 @@ object ShowdownInterpreter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format:
|
||||
* |-miss|SOURCE|TARGET
|
||||
*
|
||||
* The move used by the SOURCE Pokémon missed (maybe absent) the TARGET Pokémon.
|
||||
*/
|
||||
private fun handleMissInstruction(battle: PokemonBattle, message: BattleMessage, remainingLines: MutableList<String>) {
|
||||
battle.dispatchWaiting(1.5F) {
|
||||
val pokemon = message.getBattlePokemon(0, battle) ?: return@dispatchWaiting
|
||||
battle.broadcastChatMessage(battleLang("missed").red())
|
||||
battle.minorBattleActions[pokemon.uuid] = message
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format:
|
||||
* |-immune|POKEMON
|
||||
@@ -663,7 +639,7 @@ object ShowdownInterpreter {
|
||||
val effectID = message.effectAt(1)?.id ?: return@dispatchWaiting
|
||||
val name = pokemon.getName()
|
||||
// Move may be null as it's not always given
|
||||
val moveName = message.moveAt(2)?.displayName ?: run { println(message.argumentAt(2)); "(Unrecognized: ${message.argumentAt(2)})".text() }
|
||||
val moveName = message.moveAt(2)?.displayName ?: run { "(Unrecognized: ${message.argumentAt(2)})".text() }
|
||||
|
||||
val lang = when (effectID) {
|
||||
// TODO: in the games they use a generic image because there is a popup of the ability and the sprite of the mon, it may be good to have a similar system here
|
||||
@@ -995,46 +971,6 @@ object ShowdownInterpreter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format:
|
||||
* |-activate|POKEMON|EFFECT
|
||||
*
|
||||
* A miscellaneous effect has activated.This is triggered whenever an effect could not be better described by one of the other minor messages.
|
||||
*/
|
||||
private fun handleActivateInstructions(battle: PokemonBattle, message: BattleMessage, remainingLines: MutableList<String>){
|
||||
val pokemon = message.getBattlePokemon(0, battle) ?: return
|
||||
val pokemonName = pokemon.getName()
|
||||
val sourceName = message.getSourceBattlePokemon(battle)?.getName() ?: Text.literal("UNKNOWN")
|
||||
val effect = message.effectAt(1) ?: return
|
||||
val extraEffect = message.effectAt(2)?.typelessData ?: Text.literal("UNKNOWN")
|
||||
broadcastOptionalAbility(battle, effect, pokemonName)
|
||||
|
||||
battle.dispatch{
|
||||
this.lastCauser[battle.battleId] = message
|
||||
battle.minorBattleActions[pokemon.uuid] = message
|
||||
|
||||
val lang = when (effect.id) {
|
||||
// Includes a 3rd argument being the magnitude level as a number
|
||||
"magnitude" -> battleLang("activate.magnitude", message.argumentAt(2)?.toIntOrNull() ?: 1)
|
||||
// Includes spited move and the PP it was reduced by
|
||||
"spite", "eeriespell" -> battleLang("activate.spite", pokemonName, extraEffect, message.argumentAt(3)!!)
|
||||
// Don't need additional lang, announced elsewhere
|
||||
"toxicdebris", "shedskin" -> return@dispatch GO
|
||||
// Add activation to each Pokemon's history
|
||||
"destinybond" -> {
|
||||
battle.activePokemon.mapNotNull { it.battlePokemon?.uuid }.forEach { battle.minorBattleActions[it] = message }
|
||||
battleLang("activate.destinybond", pokemonName)
|
||||
}
|
||||
"focussash", "focusband" -> battleLang("activate.focusband", pokemonName, effect.typelessData)
|
||||
"maxguard", "protect" -> battleLang("activate.protect", pokemonName)
|
||||
"shadowforce", "hyperspacefury", "hyperspacehole" -> battleLang("activate.phantomforce", pokemonName)
|
||||
else -> battleLang("activate.${effect.id}", pokemonName, sourceName, extraEffect)
|
||||
}
|
||||
battle.broadcastChatMessage(lang)
|
||||
WaitDispatch(1F)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format:
|
||||
* |-fieldstart|CONDITION
|
||||
@@ -1092,54 +1028,6 @@ object ShowdownInterpreter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format:
|
||||
* |-ability|POKEMON|ABILITY|(from)EFFECT
|
||||
*
|
||||
* The ABILITY of the POKEMON has been changed due to a move/ability EFFECT.
|
||||
*
|
||||
* Format:
|
||||
* |-ability|POKEMON|ABILITY
|
||||
*
|
||||
* POKEMON has just switched-in, and its ability ABILITY is being announced to have a long-term effect.
|
||||
*/
|
||||
private fun handleAbilityInstructions(battle: PokemonBattle, message: BattleMessage, remainingLines: MutableList<String>) {
|
||||
val pokemon = message.getBattlePokemon(0, battle) ?: return
|
||||
val pokemonName = pokemon.getName()
|
||||
val effect = message.effectAt(1) ?: return
|
||||
val optionalEffect = message.effect()
|
||||
val optionalPokemon = message.getSourceBattlePokemon(battle)
|
||||
val optionalPokemonName = optionalPokemon?.getName()
|
||||
|
||||
// If there is an optional effect causing the activation, broadcast that instead of the standard effect
|
||||
if (optionalEffect != null) {
|
||||
broadcastAbility(battle, optionalEffect, pokemonName)
|
||||
} else {
|
||||
broadcastAbility(battle, effect, pokemonName)
|
||||
}
|
||||
|
||||
battle.dispatch {
|
||||
this.lastCauser[battle.battleId] = message
|
||||
|
||||
val lang = when (optionalEffect?.id) {
|
||||
"trace" -> optionalPokemonName?.let { battleLang("ability.trace", pokemonName, it, effect.typelessData) }
|
||||
"receiver", "powerofalchemy" -> optionalPokemonName?.let { battleLang("ability.receiver", it, effect.typelessData) } // Receiver and Power of Alchemy share the same text
|
||||
else -> when (effect.id) {
|
||||
"sturdy", "unnerve", "anticipation" -> battleLang("ability.${effect.id}", pokemonName) // Unique message
|
||||
"airlock", "cloudnine" -> battleLang("ability.airlock") // Cloud Nine shares the same text as Air Lock
|
||||
else -> null // Effect broadcasted by a succeeding instruction
|
||||
}
|
||||
}
|
||||
|
||||
battle.minorBattleActions[pokemon.uuid] = message
|
||||
if (lang != null) {
|
||||
battle.broadcastChatMessage(lang)
|
||||
return@dispatch WaitDispatch(1F)
|
||||
}
|
||||
else return@dispatch GO
|
||||
}
|
||||
}
|
||||
|
||||
fun broadcastOptionalAbility(battle: PokemonBattle, effect: Effect?, pokemonName: MutableText) {
|
||||
if (effect != null && effect.type == Effect.Type.ABILITY)
|
||||
broadcastAbility(battle, effect, pokemonName)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* 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.battles.dispatch
|
||||
|
||||
interface CauserInstruction
|
||||
@@ -9,9 +9,11 @@
|
||||
package com.cobblemon.mod.common.battles.dispatch
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import org.objectweb.asm.tree.analysis.Interpreter
|
||||
|
||||
class InstructionSet {
|
||||
val instructions: MutableList<InterpreterInstruction> = mutableListOf()
|
||||
|
||||
fun getSubsequentInstructions(instruction: InterpreterInstruction): List<InterpreterInstruction> {
|
||||
val index = instructions.indexOf(instruction)
|
||||
return instructions.subList(index + 1, instructions.size).toList()
|
||||
@@ -22,6 +24,31 @@ class InstructionSet {
|
||||
return instructions.subList(0, index).toList()
|
||||
}
|
||||
|
||||
inline fun <reified T> getMostRecentInstruction(comparedTo: InterpreterInstruction, predicate: (T) -> Boolean = { true }): T? {
|
||||
val index = instructions.indexOf(comparedTo)
|
||||
return instructions.subList(0, index).filterIsInstance<T>().lastOrNull(predicate)
|
||||
}
|
||||
|
||||
fun findInstructionsCausedBy(causerInstruction: CauserInstruction): List<InterpreterInstruction> {
|
||||
causerInstruction as InterpreterInstruction
|
||||
val thisCauseIndex = instructions.indexOf(causerInstruction)
|
||||
if (thisCauseIndex == instructions.size - 1) {
|
||||
return emptyList()
|
||||
}
|
||||
val nextCauseIndex = getNextInstruction<CauserInstruction>(causerInstruction)?.let { instructions.indexOf(it as InterpreterInstruction) }
|
||||
return instructions.subList(thisCauseIndex + 1, nextCauseIndex ?: instructions.size)
|
||||
}
|
||||
|
||||
inline fun <reified T> getNextInstruction(comparedTo: InterpreterInstruction, predicate: (T) -> Boolean = { true }): T? {
|
||||
val index = instructions.indexOf(comparedTo)
|
||||
if (instructions.last() == comparedTo) {
|
||||
return null
|
||||
}
|
||||
return instructions.subList(index + 1, instructions.size).filterIsInstance<T>().firstOrNull(predicate)
|
||||
}
|
||||
|
||||
fun getMostRecentCauser(comparedTo: InterpreterInstruction) = getMostRecentInstruction<CauserInstruction>(comparedTo)
|
||||
|
||||
fun execute(battle: PokemonBattle) {
|
||||
instructions.forEach { it(battle) }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.battles.interpreter.instructions
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.battles.ShowdownInterpreter
|
||||
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.WaitDispatch
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
|
||||
/**
|
||||
* Format:
|
||||
* |-ability|POKEMON|ABILITY|(from)EFFECT
|
||||
*
|
||||
* The ABILITY of the POKEMON has been changed due to a move/ability EFFECT.
|
||||
*
|
||||
* Format:
|
||||
* |-ability|POKEMON|ABILITY
|
||||
*
|
||||
* POKEMON has just switched-in, and its ability ABILITY is being announced to have a long-term effect.
|
||||
*/
|
||||
class AbilityInstruction(val instructionSet: InstructionSet, val message: BattleMessage) : InterpreterInstruction, CauserInstruction {
|
||||
override fun invoke(battle: PokemonBattle) {
|
||||
val pokemon = message.getBattlePokemon(0, battle) ?: return
|
||||
val pokemonName = pokemon.getName()
|
||||
val effect = message.effectAt(1) ?: return
|
||||
val optionalEffect = message.effect()
|
||||
val optionalPokemon = message.getSourceBattlePokemon(battle)
|
||||
val optionalPokemonName = optionalPokemon?.getName()
|
||||
|
||||
// If there is an optional effect causing the activation, broadcast that instead of the standard effect
|
||||
if (optionalEffect != null) {
|
||||
ShowdownInterpreter.broadcastAbility(battle, optionalEffect, pokemonName)
|
||||
} else {
|
||||
ShowdownInterpreter.broadcastAbility(battle, effect, pokemonName)
|
||||
}
|
||||
|
||||
battle.dispatch {
|
||||
ShowdownInterpreter.lastCauser[battle.battleId] = message
|
||||
|
||||
val lang = when (optionalEffect?.id) {
|
||||
"trace" -> optionalPokemonName?.let { battleLang("ability.trace", pokemonName, it, effect.typelessData) }
|
||||
"receiver", "powerofalchemy" -> optionalPokemonName?.let { battleLang("ability.receiver", it, effect.typelessData) } // Receiver and Power of Alchemy share the same text
|
||||
else -> when (effect.id) {
|
||||
"sturdy", "unnerve", "anticipation" -> battleLang("ability.${effect.id}", pokemonName) // Unique message
|
||||
"airlock", "cloudnine" -> battleLang("ability.airlock") // Cloud Nine shares the same text as Air Lock
|
||||
else -> null // Effect broadcasted by a succeeding instruction
|
||||
}
|
||||
}
|
||||
|
||||
battle.minorBattleActions[pokemon.uuid] = message
|
||||
if (lang != null) {
|
||||
battle.broadcastChatMessage(lang)
|
||||
return@dispatch WaitDispatch(1F)
|
||||
}
|
||||
else return@dispatch GO
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.battles.interpreter.instructions
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.battles.ShowdownInterpreter
|
||||
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.WaitDispatch
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import net.minecraft.text.Text
|
||||
|
||||
/**
|
||||
* Format:
|
||||
* |-activate|POKEMON|EFFECT
|
||||
*
|
||||
* A miscellaneous effect has activated.This is triggered whenever an effect could not be better described by one of the other minor messages.
|
||||
*/
|
||||
class ActivateInstruction(val instructionSet: InstructionSet, val message: BattleMessage) : InterpreterInstruction, CauserInstruction {
|
||||
override fun invoke(battle: PokemonBattle) {
|
||||
val pokemon = message.getBattlePokemon(0, battle) ?: return
|
||||
val pokemonName = pokemon.getName()
|
||||
val sourceName = message.getSourceBattlePokemon(battle)?.getName() ?: Text.literal("UNKNOWN")
|
||||
val effect = message.effectAt(1) ?: return
|
||||
val extraEffect = message.effectAt(2)?.typelessData ?: Text.literal("UNKNOWN")
|
||||
ShowdownInterpreter.broadcastOptionalAbility(battle, effect, pokemonName)
|
||||
|
||||
battle.dispatch{
|
||||
ShowdownInterpreter.lastCauser[battle.battleId] = message
|
||||
battle.minorBattleActions[pokemon.uuid] = message
|
||||
|
||||
val lang = when (effect.id) {
|
||||
// Includes a 3rd argument being the magnitude level as a number
|
||||
"magnitude" -> battleLang("activate.magnitude", message.argumentAt(2)?.toIntOrNull() ?: 1)
|
||||
// Includes spited move and the PP it was reduced by
|
||||
"spite", "eeriespell" -> battleLang("activate.spite", pokemonName, extraEffect, message.argumentAt(3)!!)
|
||||
// Don't need additional lang, announced elsewhere
|
||||
"toxicdebris", "shedskin" -> return@dispatch GO
|
||||
// Add activation to each Pokemon's history
|
||||
"destinybond" -> {
|
||||
battle.activePokemon.mapNotNull { it.battlePokemon?.uuid }.forEach { battle.minorBattleActions[it] = message }
|
||||
battleLang("activate.destinybond", pokemonName)
|
||||
}
|
||||
"focussash", "focusband" -> battleLang("activate.focusband", pokemonName, effect.typelessData)
|
||||
"maxguard", "protect" -> battleLang("activate.protect", pokemonName)
|
||||
"shadowforce", "hyperspacefury", "hyperspacehole" -> battleLang("activate.phantomforce", pokemonName)
|
||||
else -> battleLang("activate.${effect.id}", pokemonName, sourceName, extraEffect)
|
||||
}
|
||||
battle.broadcastChatMessage(lang)
|
||||
WaitDispatch(1F)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,24 +15,34 @@ 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.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.net.messages.client.animation.PlayPoseableAnimationPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleHealthChangePacket
|
||||
import com.cobblemon.mod.common.pokemon.evolution.progress.DamageTakenEvolutionProgress
|
||||
import com.cobblemon.mod.common.pokemon.evolution.progress.RecoilEvolutionProgress
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import kotlin.math.roundToInt
|
||||
import net.minecraft.text.Text
|
||||
|
||||
class DamageInstruction(
|
||||
val instructionSet: InstructionSet,
|
||||
val actor: BattleActor,
|
||||
val publicMessage: BattleMessage,
|
||||
val privateMessage: BattleMessage
|
||||
) : InterpreterInstruction {
|
||||
var future = CompletableFuture.completedFuture(Unit)
|
||||
val battlePokemon = publicMessage.getBattlePokemon(0, actor.battle)
|
||||
|
||||
override fun invoke(battle: PokemonBattle) {
|
||||
val battlePokemon = publicMessage.getBattlePokemon(0, battle) ?: return
|
||||
if (privateMessage.optionalArgument("from")?.equals("recoil", true) == true) {
|
||||
battlePokemon ?: return
|
||||
val recoiling = privateMessage.optionalArgument("from")?.equals("recoil", true) == true
|
||||
val lastCauser = instructionSet.getMostRecentCauser(comparedTo = this)
|
||||
if (recoiling) {
|
||||
battlePokemon.effectedPokemon.let { pokemon ->
|
||||
if (RecoilEvolutionProgress.supports(pokemon)) {
|
||||
val newPercentage = privateMessage.argumentAt(1)?.split("/")?.getOrNull(0)?.toIntOrNull() ?: 0
|
||||
@@ -48,10 +58,34 @@ class DamageInstruction(
|
||||
val newHealth = privateMessage.argumentAt(1)?.split(" ")?.get(0) ?: return
|
||||
val effect = privateMessage.effect()
|
||||
val pokemonName = battlePokemon.getName()
|
||||
val sourceName = privateMessage.getSourceBattlePokemon(battle)?.getName() ?: Text.literal("UNKOWN")
|
||||
ShowdownInterpreter.broadcastOptionalAbility(battle, effect, sourceName)
|
||||
val sourceName = privateMessage.getSourceBattlePokemon(battle)?.getName() ?: Text.literal("UNKNOWN")
|
||||
var causedFaint = newHealth == "0"
|
||||
|
||||
battle.dispatch {
|
||||
if (lastCauser is MoveInstruction && recoiling) {
|
||||
UntilDispatch { "recoil" !in lastCauser.holds }
|
||||
} else {
|
||||
GO
|
||||
}
|
||||
}
|
||||
|
||||
battle.dispatch {
|
||||
if (!causedFaint) {
|
||||
|
||||
battlePokemon.entity?.let { pokemonEntity ->
|
||||
val pkt = PlayPoseableAnimationPacket(pokemonEntity.id, setOf("recoil"), emptySet())
|
||||
pkt.sendToPlayersAround(
|
||||
x = pokemonEntity.x,
|
||||
y = pokemonEntity.y,
|
||||
z = pokemonEntity.z,
|
||||
worldKey = pokemonEntity.world.registryKey,
|
||||
distance = 50.0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ShowdownInterpreter.broadcastOptionalAbility(battle, effect, sourceName)
|
||||
val newHealthRatio: Float
|
||||
val remainingHealth = newHealth.split("/")[0].toInt()
|
||||
|
||||
@@ -70,8 +104,7 @@ class DamageInstruction(
|
||||
battle.broadcastChatMessage(lang.red())
|
||||
}
|
||||
|
||||
var causedFaint = false
|
||||
if (newHealth == "0") {
|
||||
if (causedFaint) {
|
||||
newHealthRatio = 0F
|
||||
battle.dispatch {
|
||||
battlePokemon.effectedPokemon.currentHealth = 0
|
||||
@@ -102,7 +135,9 @@ class DamageInstruction(
|
||||
battle.minorBattleActions[battlePokemon.uuid] = privateMessage
|
||||
|
||||
// If they faint from this damage then don't bother waiting for the damage to be applied.
|
||||
if (causedFaint) {
|
||||
if (lastCauser is MoveInstruction && lastCauser.actionEffect != null && !causedFaint) {
|
||||
UntilDispatch { lastCauser.future.isDone }
|
||||
} else if (causedFaint) {
|
||||
GO
|
||||
} else {
|
||||
WaitDispatch(1F)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.battles.interpreter.instructions
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.text.red
|
||||
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
|
||||
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
|
||||
/**
|
||||
* Format:
|
||||
* |-miss|SOURCE|TARGET
|
||||
*
|
||||
* The move used by the SOURCE Pokémon missed (maybe absent) the TARGET Pokémon.
|
||||
*/
|
||||
class MissInstruction(val battle: PokemonBattle, val instructionSet: InstructionSet, val message: BattleMessage): InterpreterInstruction {
|
||||
val target = message.getBattlePokemon(1, battle)
|
||||
|
||||
override fun invoke(battle: PokemonBattle) {
|
||||
battle.dispatchGo {
|
||||
val pokemon = message.getBattlePokemon(0, battle) ?: return@dispatchGo
|
||||
battle.broadcastChatMessage(battleLang("missed").red())
|
||||
battle.minorBattleActions[pokemon.uuid] = message
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,30 +8,55 @@
|
||||
|
||||
package com.cobblemon.mod.common.battles.interpreter.instructions
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
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.addFunction
|
||||
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
|
||||
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
|
||||
import com.cobblemon.mod.common.battles.ShowdownInterpreter
|
||||
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.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.pokemon.evolution.progress.UseMoveEvolutionProgress
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class MoveInstruction(
|
||||
val instructionSet: InstructionSet,
|
||||
val message: BattleMessage
|
||||
) : InterpreterInstruction {
|
||||
) : InterpreterInstruction, CauserInstruction {
|
||||
val effect = message.effectAt(1) ?: Effect.pure("", "")
|
||||
val move = Moves.getByNameOrDummy(effect.id)
|
||||
val actionEffect = move.actionEffect
|
||||
|
||||
var future = CompletableFuture.completedFuture(Unit)
|
||||
var holds = mutableSetOf<String>()
|
||||
|
||||
lateinit var userPokemon: BattlePokemon
|
||||
var targetPokemon: BattlePokemon? = null
|
||||
|
||||
override fun invoke(battle: PokemonBattle) {
|
||||
val userPokemon = message.getBattlePokemon(0, battle) ?: return
|
||||
val targetPokemon = message.getBattlePokemon(2, battle)
|
||||
val effect = message.effectAt(1) ?: return
|
||||
userPokemon = message.getBattlePokemon(0, battle)!!
|
||||
targetPokemon = message.getBattlePokemon(2, battle)
|
||||
val targetPokemon = targetPokemon // So smart non-null casts can happen
|
||||
|
||||
val optionalEffect = message.effect()
|
||||
val move = Moves.getByNameOrDummy(effect.id)
|
||||
val pokemonName = userPokemon.getName()
|
||||
ShowdownInterpreter.broadcastOptionalAbility(battle, optionalEffect, pokemonName)
|
||||
|
||||
battle.dispatch { UntilDispatch { instructionSet.getMostRecentInstruction<MoveInstruction>(this)?.future?.isDone != false } }
|
||||
|
||||
battle.dispatchGo {
|
||||
battle.dispatch {
|
||||
ShowdownInterpreter.lastCauser[battle.battleId] = message
|
||||
|
||||
userPokemon.effectedPokemon.let { pokemon ->
|
||||
@@ -50,8 +75,50 @@ class MoveInstruction(
|
||||
battleLang("used_move", pokemonName, move.displayName)
|
||||
}
|
||||
battle.broadcastChatMessage(lang)
|
||||
|
||||
battle.majorBattleActions[userPokemon.uuid] = message
|
||||
|
||||
val providers = mutableListOf<Any>(battle)
|
||||
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()
|
||||
}
|
||||
|
||||
actionEffect ?: return@dispatch GO
|
||||
val context = ActionEffectContext(
|
||||
actionEffect = actionEffect,
|
||||
runtime = runtime,
|
||||
providers = providers
|
||||
)
|
||||
|
||||
val subsequentInstructions = instructionSet.findInstructionsCausedBy(this)
|
||||
val missedTargets = subsequentInstructions.filterIsInstance<MissInstruction>().mapNotNull { it.target }
|
||||
|
||||
runtime.environment.getQueryStruct().addFunction("missed") { params ->
|
||||
if (params.params.size == 0) {
|
||||
return@addFunction DoubleValue(missedTargets.isNotEmpty())
|
||||
} else {
|
||||
val entityUUID = params.getString(0)
|
||||
return@addFunction DoubleValue(missedTargets.any { it.entity?.uuidAsString == entityUUID })
|
||||
}
|
||||
}
|
||||
|
||||
val hurtTargets = subsequentInstructions.filterIsInstance<DamageInstruction>().mapNotNull { it.battlePokemon }
|
||||
runtime.environment.getQueryStruct().addFunction("hurt") { params ->
|
||||
if (params.params.size == 0) {
|
||||
return@addFunction DoubleValue(hurtTargets.isNotEmpty())
|
||||
} else {
|
||||
val entityUUID = params.getString(0)
|
||||
return@addFunction DoubleValue(hurtTargets.any { it.entity?.uuidAsString == entityUUID })
|
||||
}
|
||||
}
|
||||
|
||||
runtime.environment.getQueryStruct().addFunction("move") { move.struct }
|
||||
|
||||
this.future = actionEffect.run(context)
|
||||
holds = context.holds // Reference so future things can check on this action effect's holds
|
||||
future.thenApply { holds.clear() }
|
||||
return@dispatch UntilDispatch { "effects" !in holds }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,10 @@
|
||||
|
||||
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
|
||||
@@ -37,6 +40,11 @@ class EmptyPokeBallClientDelegate : PokeBallPoseableState(), EntitySideDelegate<
|
||||
this.currentEntity = entity
|
||||
age = entity.age
|
||||
initSubscriptions()
|
||||
this.runtime.environment.getQueryStruct().addFunctions(mapOf(
|
||||
"pokeball_type" to java.util.function.Function {
|
||||
return@Function DoubleValue(currentEntity.pokeBall.name.toString())
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
override fun tick(entity: EmptyPokeBallEntity) {
|
||||
|
||||
@@ -8,9 +8,13 @@
|
||||
|
||||
package com.cobblemon.mod.common.client.entity
|
||||
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.bedrockk.molang.runtime.value.StringValue
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
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
|
||||
@@ -91,7 +95,7 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
|
||||
if (isDying) {
|
||||
val model = (currentModel ?: return) as PokemonPoseableModel
|
||||
val animation = try {
|
||||
model.getFaintAnimation(currentEntity, this)
|
||||
model.getAnimation(this, "faint", runtime)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
@@ -222,6 +226,27 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
|
||||
override fun initialize(entity: PokemonEntity) {
|
||||
this.currentEntity = entity
|
||||
this.age = entity.age
|
||||
|
||||
this.runtime.environment.getQueryStruct().addFunctions(mapOf(
|
||||
"in_battle" to java.util.function.Function {
|
||||
return@Function DoubleValue(currentEntity.isBattling)
|
||||
},
|
||||
"shiny" to java.util.function.Function {
|
||||
return@Function DoubleValue(currentEntity.pokemon.shiny)
|
||||
},
|
||||
"form" to java.util.function.Function {
|
||||
return@Function StringValue(currentEntity.pokemon.form.name)
|
||||
},
|
||||
"width" to java.util.function.Function {
|
||||
return@Function DoubleValue(currentEntity.boundingBox.xLength)
|
||||
},
|
||||
"height" to java.util.function.Function {
|
||||
return@Function DoubleValue(currentEntity.boundingBox.yLength)
|
||||
},
|
||||
"weight" to java.util.function.Function {
|
||||
return@Function DoubleValue(currentEntity.pokemon.species.weight.toDouble())
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
override fun tick(entity: PokemonEntity) {
|
||||
@@ -248,12 +273,16 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
|
||||
fun cry() {
|
||||
val model = currentModel ?: return
|
||||
if (model is PokemonPoseableModel) {
|
||||
if (cryAnimation != null && cryAnimation in statefulAnimations) {
|
||||
if (cryAnimation != null && (cryAnimation in statefulAnimations || cryAnimation == primaryAnimation)) {
|
||||
return
|
||||
}
|
||||
|
||||
val animation = model.cryAnimation(currentEntity, this) ?: return
|
||||
statefulAnimations.add(animation)
|
||||
if (animation is PrimaryAnimation) {
|
||||
addPrimaryAnimation(animation)
|
||||
} else {
|
||||
statefulAnimations.add(animation)
|
||||
}
|
||||
cryAnimation = animation
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +138,8 @@ abstract class BattleGimmickButton(gimmick: ShowdownMoveset.Gimmick, val x: Floa
|
||||
pp = gimmickTemplate?.pp ?: moveTemplate.pp,
|
||||
priority = gimmickTemplate?.priority ?: moveTemplate.priority,
|
||||
critRatio = gimmickTemplate?.critRatio ?: moveTemplate.critRatio,
|
||||
effectChances = gimmickTemplate?.effectChances ?: moveTemplate.effectChances
|
||||
effectChances = gimmickTemplate?.effectChances ?: moveTemplate.effectChances,
|
||||
actionEffect = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.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.util.resolve
|
||||
import net.minecraft.client.MinecraftClient
|
||||
|
||||
object PlayPoseableAnimationHandler : ClientNetworkPacketHandler<PlayPoseableAnimationPacket> {
|
||||
override fun handle(packet: PlayPoseableAnimationPacket, client: MinecraftClient) {
|
||||
val entity = client.world?.getEntityById(packet.entityId) ?: return
|
||||
if (entity is Poseable) {
|
||||
val delegate = entity.delegate
|
||||
if (delegate is PoseableEntityState<*>) {
|
||||
for (expr in packet.expressions) {
|
||||
delegate.runtime.resolve(expr.asExpression())
|
||||
}
|
||||
delegate.addFirstAnimation(packet.animation)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.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.net.messages.client.effect.RunPosableMoLangPacket
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.resolve
|
||||
import net.minecraft.client.MinecraftClient
|
||||
|
||||
object RunPosableMoLangHandler : ClientNetworkPacketHandler<RunPosableMoLangPacket> {
|
||||
override fun handle(packet: RunPosableMoLangPacket, client: MinecraftClient) {
|
||||
val entity = client.world?.getEntityById(packet.entityId) ?: return
|
||||
if (entity is Poseable) {
|
||||
val state = entity.delegate as? PoseableEntityState<*> ?: return
|
||||
for (expression in packet.expressions.map { it.asExpression() }) {
|
||||
state.runtime.resolve(expression)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.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.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.entity.Entity
|
||||
|
||||
object SpawnSnowstormEntityParticleHandler : ClientNetworkPacketHandler<SpawnSnowstormEntityParticlePacket> {
|
||||
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
|
||||
entity as Entity
|
||||
val state = entity.delegate as PoseableEntityState<*>
|
||||
val matrixWrapper = state.locatorStates[packet.locator] ?: state.locatorStates["root"]!!
|
||||
|
||||
val particleRuntime = MoLangRuntime().setup().setupClient()
|
||||
particleRuntime.environment.getQueryStruct().addFunction("entity") { state.runtime.environment.getQueryStruct() }
|
||||
|
||||
val storm = ParticleStorm(
|
||||
effect = effect,
|
||||
matrixWrapper = matrixWrapper,
|
||||
world = world,
|
||||
runtime = particleRuntime,
|
||||
sourceVelocity = { entity.velocity },
|
||||
sourceAlive = { !entity.isRemoved },
|
||||
sourceVisible = { !entity.isInvisible }
|
||||
)
|
||||
|
||||
storm.spawn()
|
||||
}
|
||||
}
|
||||
@@ -1,25 +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.net.sound
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.net.messages.client.sound.PokemonCryPacket
|
||||
import net.minecraft.client.MinecraftClient
|
||||
|
||||
object PokemonCryHandler : ClientNetworkPacketHandler<PokemonCryPacket> {
|
||||
override fun handle(packet: PokemonCryPacket, client: MinecraftClient) {
|
||||
val entity = client.world?.getEntityById(packet.entityId) ?: return
|
||||
if (entity is PokemonEntity) {
|
||||
val delegate = entity.delegate as PokemonClientDelegate
|
||||
delegate.cry()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.ModAPI
|
||||
import com.cobblemon.mod.common.api.snowstorm.ParticleMaterial
|
||||
import com.cobblemon.mod.common.api.snowstorm.ParticleMaterials
|
||||
import com.cobblemon.mod.common.api.snowstorm.UVDetails
|
||||
import com.cobblemon.mod.common.client.particle.ParticleStorm
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
@@ -24,7 +23,8 @@ import kotlin.math.abs
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.particle.Particle
|
||||
import net.minecraft.client.particle.ParticleTextureSheet
|
||||
import net.minecraft.client.particle.ParticleTextureSheet.*
|
||||
import net.minecraft.client.particle.ParticleTextureSheet.NO_RENDER
|
||||
import net.minecraft.client.particle.ParticleTextureSheet.PARTICLE_SHEET_TRANSLUCENT
|
||||
import net.minecraft.client.render.BufferBuilder
|
||||
import net.minecraft.client.render.Camera
|
||||
import net.minecraft.client.render.VertexConsumer
|
||||
|
||||
@@ -36,6 +36,6 @@ object BedrockAnimationReferenceFactory : AnimationReferenceFactory {
|
||||
return model.bedrockStateful(
|
||||
animationGroup = split[0],
|
||||
animation = split[1]
|
||||
).setPreventsIdle(if (split.size == 3) split[2].toBoolean() else JsonPokemonPoseableModel.StatefulAnimationAdapter.preventsIdleDefault)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -8,16 +8,33 @@
|
||||
|
||||
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.frame.HeadedFrame
|
||||
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
|
||||
import com.cobblemon.mod.common.util.resolveObject
|
||||
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) {
|
||||
val poseName = json.get("poseName").asString
|
||||
class JsonPoseTransition(val from: String, val to: String, val animation: ExpressionLike)
|
||||
|
||||
val runtime = MoLangRuntime().setup().setupClient().also {
|
||||
it.environment.getQueryStruct().addFunctions(model.functions.functions)
|
||||
}
|
||||
|
||||
val poseName = json.get("poseName")?.asString ?: "pose"
|
||||
val poseTypes = (json.get("poseTypes")?.asJsonArray?.map { name ->
|
||||
PoseType.values().find { it.name.lowercase() == name.asString.lowercase() }
|
||||
?: throw IllegalArgumentException("Unknown pose type ${name.asString}")
|
||||
@@ -47,18 +64,33 @@ class JsonPose<T : Entity>(model: PoseableEntityModel<T>, json: JsonObject) {
|
||||
} 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, *>
|
||||
} catch (exception: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
return@mapNotNull null
|
||||
}.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>
|
||||
}
|
||||
|
||||
json as JsonObject
|
||||
val name = json.get("name").asString
|
||||
val animations: (state: PoseableEntityState<T>) -> List<StatefulAnimation<T, *>> = { _ ->
|
||||
(json.get("animations")?.asJsonArray ?: JsonArray()).map { animJson ->
|
||||
val split =
|
||||
animJson.asString.replace("bedrock(", "").replace(")", "").split(",").map(String::trim)
|
||||
model.bedrockStateful(animationGroup = split[0], animation = split[1]).setPreventsIdle(false)
|
||||
try {
|
||||
val expr = animJson.asString.asExpressionLike()
|
||||
runtime.resolveObject(expr).obj as StatefulAnimation<T, *>
|
||||
} catch (e: Exception) {
|
||||
val split =
|
||||
animJson.asString.replace("bedrock(", "").replace(")", "").split(",").map(String::trim)
|
||||
model.bedrockStateful(animationGroup = split[0], animation = split[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,11 +99,26 @@ class JsonPose<T : Entity>(model: PoseableEntityModel<T>, json: JsonObject) {
|
||||
val maxSeconds = json.get("maxSecondsBetweenOccurrences")?.asFloat ?: 30F
|
||||
|
||||
model.quirkMultiple(
|
||||
name = name,
|
||||
secondsBetweenOccurrences = minSeconds to maxSeconds,
|
||||
condition = { true },
|
||||
loopTimes = 1..loopTimes,
|
||||
animations = animations
|
||||
)
|
||||
}
|
||||
|
||||
val animations = json.get("namedAnimations")?.takeIf { it is JsonObject }?.asJsonObject?.let {
|
||||
val map = mutableMapOf<String, ExpressionLike>()
|
||||
for ((key, value) in it.entrySet()) {
|
||||
map[key] = value.asString.asExpressionLike()
|
||||
}
|
||||
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)
|
||||
} ?: emptyList()
|
||||
}
|
||||
@@ -87,7 +87,14 @@ abstract class JsonPoseableEntityModel<T : Entity>(override val rootPart: Bone)
|
||||
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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,10 +71,6 @@ class LocatorAccess(
|
||||
fun update(matrixStack: MatrixStack, state: MutableMap<String, MatrixWrapper>) {
|
||||
matrixStack.push()
|
||||
joint.transform(matrixStack)
|
||||
matrixStack.push()
|
||||
matrixStack.scale(1F, -1F, 1F)
|
||||
state.getOrPut("root") { MatrixWrapper() }.updateMatrix(matrixStack.peek().positionMatrix)
|
||||
matrixStack.pop()
|
||||
|
||||
for ((name, locator) in locators) {
|
||||
matrixStack.push()
|
||||
|
||||
@@ -8,20 +8,28 @@
|
||||
|
||||
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.LOGGER
|
||||
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.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.api.scheduling.afterOnClient
|
||||
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
|
||||
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
|
||||
import com.cobblemon.mod.common.client.render.MatrixWrapper
|
||||
import com.cobblemon.mod.common.client.render.ModelLayer
|
||||
import com.cobblemon.mod.common.client.render.layer.CobblemonRenderLayers
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PoseTransitionAnimation
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PrimaryAnimation
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.animation.RotationFunctionStatelessAnimation
|
||||
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.TranslationFunctionStatelessAnimation
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.animation.*
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockAnimationRepository
|
||||
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.pokemon.PokemonPoseableModel
|
||||
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
|
||||
@@ -29,11 +37,15 @@ 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.PoseType
|
||||
import com.cobblemon.mod.common.entity.Poseable
|
||||
import com.cobblemon.mod.common.entity.generic.GenericBedrockEntity
|
||||
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 net.minecraft.client.model.ModelPart
|
||||
import net.minecraft.client.render.OverlayTexture
|
||||
import net.minecraft.client.render.RenderLayer
|
||||
@@ -82,6 +94,9 @@ abstract class PoseableEntityModel<T : Entity>(
|
||||
@Transient
|
||||
var currentState: PoseableEntityState<T>? = null
|
||||
|
||||
val animations = mutableMapOf<String, ExpressionLike>()
|
||||
val transitions = mutableMapOf<String, ExpressionLike>()
|
||||
|
||||
/**
|
||||
* A list of [ModelPartTransformation] that record the original
|
||||
* This allows the original rotations to be reset.
|
||||
@@ -91,12 +106,218 @@ abstract class PoseableEntityModel<T : Entity>(
|
||||
val relevantParts = mutableListOf<ModelPart>()
|
||||
val relevantPartsByName = mutableMapOf<String, ModelPart>()
|
||||
|
||||
@Transient
|
||||
val functions = QueryStruct(hashMapOf())
|
||||
.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<*>) {
|
||||
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<T>(
|
||||
frame = this,
|
||||
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<T>(
|
||||
frame = this,
|
||||
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<T>(
|
||||
frame = this,
|
||||
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<T>(
|
||||
frame = this,
|
||||
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<T>(
|
||||
frame = this,
|
||||
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()) }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Transient
|
||||
val runtime = MoLangRuntime().setup().setupClient().also { it.environment.getQueryStruct().addFunctions(functions.functions) }
|
||||
|
||||
/** Registers the different poses this model is capable of ahead of time. Should use [registerPose] religiously. */
|
||||
abstract fun registerPoses()
|
||||
|
||||
/** Gets the [PoseableEntityState] for an entity. */
|
||||
abstract fun getState(entity: T): PoseableEntityState<T>
|
||||
|
||||
fun getAnimation(state: PoseableEntityState<*>, name: String, runtime: MoLangRuntime): StatefulAnimation<T, *>? {
|
||||
val poseAnimations = state.currentPose?.let(this::getPose)?.animations ?: mapOf()
|
||||
val animation = resolveFromAnimationMap(poseAnimations, name, runtime)
|
||||
?: resolveFromAnimationMap(animations, name, runtime)
|
||||
?: if (name == "cry" && this is PokemonPoseableModel) {
|
||||
cryAnimation.invoke(state.getEntity() as PokemonEntity, state as PoseableEntityState<PokemonEntity>) as? StatefulAnimation<T, *>
|
||||
} else if (name == "faint" && this is PokemonPoseableModel) {
|
||||
getFaintAnimation(state.getEntity() as PokemonEntity, state as PoseableEntityState<PokemonEntity>) as? StatefulAnimation<T, *>
|
||||
} else {
|
||||
try {
|
||||
name.asExpressionLike().resolveObject(runtime).obj as StatefulAnimation<T, *>
|
||||
} 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): StatefulAnimation<T, *>? {
|
||||
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(BedrockStatefulAnimation(animation))
|
||||
} else {
|
||||
BedrockStatefulAnimation(animation)
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveFromAnimationMap(map: Map<String, ExpressionLike>, name: String, runtime: MoLangRuntime): StatefulAnimation<T, *>? {
|
||||
val animationExpression = map[name] ?: return null
|
||||
return try {
|
||||
animationExpression.resolveObject(runtime).obj as StatefulAnimation<T, *>
|
||||
} catch (e: Exception) {
|
||||
LOGGER.error("Failed to create animation by name $name, most likely something wrong in the MoLang")
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun withLayerContext(
|
||||
buffer: VertexConsumerProvider,
|
||||
state: PoseableEntityState<T>?,
|
||||
@@ -145,6 +366,7 @@ abstract class PoseableEntityModel<T : Entity>(
|
||||
poseType: PoseType,
|
||||
condition: ((T) -> Boolean)? = null,
|
||||
transformTicks: Int = 10,
|
||||
animations: MutableMap<String, ExpressionLike> = mutableMapOf(),
|
||||
onTransitionedInto: (PoseableEntityState<T>?) -> Unit = {},
|
||||
idleAnimations: Array<StatelessAnimation<T, out F>> = emptyArray(),
|
||||
transformedParts: Array<ModelPartTransformation> = emptyArray(),
|
||||
@@ -156,6 +378,7 @@ abstract class PoseableEntityModel<T : Entity>(
|
||||
condition,
|
||||
onTransitionedInto,
|
||||
transformTicks,
|
||||
animations,
|
||||
idleAnimations,
|
||||
transformedParts,
|
||||
quirks
|
||||
@@ -169,6 +392,7 @@ abstract class PoseableEntityModel<T : Entity>(
|
||||
poseTypes: Set<PoseType>,
|
||||
condition: ((T) -> Boolean)? = null,
|
||||
transformTicks: Int = 10,
|
||||
animations: MutableMap<String, ExpressionLike> = mutableMapOf(),
|
||||
onTransitionedInto: (PoseableEntityState<T>?) -> Unit = {},
|
||||
idleAnimations: Array<StatelessAnimation<T, out F>> = emptyArray(),
|
||||
transformedParts: Array<ModelPartTransformation> = emptyArray(),
|
||||
@@ -180,6 +404,7 @@ abstract class PoseableEntityModel<T : Entity>(
|
||||
condition,
|
||||
onTransitionedInto,
|
||||
transformTicks,
|
||||
animations,
|
||||
idleAnimations,
|
||||
transformedParts,
|
||||
quirks
|
||||
@@ -193,6 +418,7 @@ abstract class PoseableEntityModel<T : Entity>(
|
||||
poseType: PoseType,
|
||||
condition: ((T) -> Boolean)? = null,
|
||||
transformTicks: Int = 10,
|
||||
animations: MutableMap<String, ExpressionLike> = mutableMapOf(),
|
||||
onTransitionedInto: (PoseableEntityState<T>?) -> Unit = {},
|
||||
idleAnimations: Array<StatelessAnimation<T, out F>> = emptyArray(),
|
||||
transformedParts: Array<ModelPartTransformation> = emptyArray(),
|
||||
@@ -204,6 +430,7 @@ abstract class PoseableEntityModel<T : Entity>(
|
||||
condition,
|
||||
onTransitionedInto,
|
||||
transformTicks,
|
||||
animations,
|
||||
idleAnimations,
|
||||
transformedParts,
|
||||
quirks
|
||||
@@ -436,7 +663,7 @@ abstract class PoseableEntityModel<T : Entity>(
|
||||
val desirablePose = poses.values.firstOrNull {
|
||||
(entityPoseType == null || entityPoseType in it.poseTypes) && it.isSuitable(entity)
|
||||
}
|
||||
?: Pose("none", setOf(PoseType.NONE), null, {}, 0, emptyArray(), emptyArray(), emptyArray())
|
||||
?: Pose("none", setOf(PoseType.NONE), null, {}, 0, mutableMapOf(), emptyArray(), emptyArray(), emptyArray())
|
||||
|
||||
// If this condition matches then it just no longer fits this pose
|
||||
if (pose != null && poseName != null) {
|
||||
@@ -467,7 +694,8 @@ abstract class PoseableEntityModel<T : Entity>(
|
||||
}
|
||||
|
||||
if (primaryAnimation != null) {
|
||||
state.primaryOverridePortion = 1 - primaryAnimation.curve((state.animationSeconds - primaryAnimation.started) / primaryAnimation.duration)
|
||||
val portion = (state.animationSeconds - primaryAnimation.started) / primaryAnimation.duration
|
||||
state.primaryOverridePortion = 1 - primaryAnimation.curve(portion)
|
||||
if (!primaryAnimation.run(entity, this, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch, 1 - state.primaryOverridePortion)) {
|
||||
state.primaryAnimation = null
|
||||
state.primaryOverridePortion = 1F
|
||||
@@ -478,7 +706,7 @@ abstract class PoseableEntityModel<T : Entity>(
|
||||
.filterNot { it.run(entity, this, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch, 1F) }
|
||||
state.statefulAnimations.removeAll(removedStatefuls)
|
||||
state.currentPose?.let { getPose(it) }
|
||||
?.idleStateful(entity, this, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch, state.primaryOverridePortion)
|
||||
?.idleStateful(entity, this, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch)
|
||||
if (entity != null) {
|
||||
updateLocators(state)
|
||||
}
|
||||
@@ -506,7 +734,7 @@ abstract class PoseableEntityModel<T : Entity>(
|
||||
val desirablePoseType = desirablePose.poseTypes.first()
|
||||
|
||||
if (state.statefulAnimations.none { it.isTransform }) {
|
||||
val transition = previousPose.transitions[desirablePose]
|
||||
val transition = previousPose.transitions[desirablePose.poseName]
|
||||
if (transition == null && previousPose.transformTicks > 0) {
|
||||
val primaryAnimation = PrimaryAnimation(
|
||||
PoseTransitionAnimation(
|
||||
@@ -543,18 +771,21 @@ abstract class PoseableEntityModel<T : Entity>(
|
||||
fun updateLocators(state: PoseableEntityState<T>) {
|
||||
val entity = context.request(RenderContext.ENTITY) ?: return
|
||||
val matrixStack = MatrixStack()
|
||||
var scale = 1F
|
||||
// We could improve this to be generalized for other entities
|
||||
if (entity is PokemonEntity) {
|
||||
matrixStack.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(180 - entity.bodyYaw))
|
||||
matrixStack.push()
|
||||
matrixStack.scale(-1F, -1F, 1F)
|
||||
val scale = entity.pokemon.form.baseScale * entity.pokemon.scaleModifier * (entity.delegate as PokemonClientDelegate).entityScaleModifier
|
||||
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)
|
||||
matrixStack.scale(0.7F, 0.7F, 0.7F)
|
||||
scale = 0.7F
|
||||
matrixStack.scale(scale, scale, scale)
|
||||
} else if (entity is GenericBedrockEntity) {
|
||||
matrixStack.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(entity.yaw))
|
||||
matrixStack.push()
|
||||
@@ -562,12 +793,27 @@ abstract class PoseableEntityModel<T : Entity>(
|
||||
matrixStack.scale(1F, -1F, 1F)
|
||||
}
|
||||
|
||||
matrixStack.push()
|
||||
matrixStack.scale(1F, -1F, 1F)
|
||||
val states = state.locatorStates
|
||||
states.getOrPut("root") { MatrixWrapper() }.updateMatrix(matrixStack.peek().positionMatrix)
|
||||
matrixStack.pop()
|
||||
|
||||
if (isForLivingEntityRenderer) {
|
||||
// Standard living entity offset, only God knows why Mojang did this.
|
||||
matrixStack.translate(0.0, -1.5, 0.0)
|
||||
}
|
||||
|
||||
locatorAccess.update(matrixStack, state.locatorStates)
|
||||
// If we have the entity, put in an approximation of the target locator. If the model has one defined,
|
||||
// this will be overridden.
|
||||
matrixStack.push()
|
||||
matrixStack.translate(0.0, -entity.boundingBox.yLength / 2.0 / scale + 1.5F, -entity.width * 0.6 / scale)
|
||||
matrixStack.scale(1F, -1F, 1F)
|
||||
states.getOrPut("target") { MatrixWrapper() }.updateMatrix(matrixStack.peek().positionMatrix)
|
||||
states.getOrPut("special_attack") { MatrixWrapper() }.updateMatrix(matrixStack.peek().positionMatrix)
|
||||
matrixStack.pop()
|
||||
|
||||
locatorAccess.update(matrixStack, states)
|
||||
}
|
||||
|
||||
fun ModelPart.translation(
|
||||
@@ -606,21 +852,17 @@ abstract class PoseableEntityModel<T : Entity>(
|
||||
fun bedrockStateful(
|
||||
animationGroup: String,
|
||||
animation: String,
|
||||
animationPrefix: String = "animation.$animationGroup",
|
||||
preventsIdleCheck: (T?, PoseableEntityState<T>, StatelessAnimation<T, *>) -> Boolean = { _, _, _ -> true }
|
||||
) = BedrockStatefulAnimation(
|
||||
animationPrefix: String = "animation.$animationGroup"
|
||||
) = BedrockStatefulAnimation<T>(
|
||||
BedrockAnimationRepository.getAnimation(animationGroup, "$animationPrefix.$animation"),
|
||||
preventsIdleCheck
|
||||
)
|
||||
|
||||
fun quirk(
|
||||
name: String,
|
||||
secondsBetweenOccurrences: Pair<Float, Float> = 8F to 30F,
|
||||
loopTimes: IntRange = 1..1,
|
||||
condition: (state: PoseableEntityState<T>) -> Boolean = { true },
|
||||
animation: (state: PoseableEntityState<T>) -> StatefulAnimation<T, *>
|
||||
) = SimpleQuirk(
|
||||
name = name,
|
||||
secondsBetweenOccurrences = secondsBetweenOccurrences,
|
||||
loopTimes = loopTimes,
|
||||
condition = condition,
|
||||
@@ -628,16 +870,33 @@ abstract class PoseableEntityModel<T : Entity>(
|
||||
)
|
||||
|
||||
fun quirkMultiple(
|
||||
name: String,
|
||||
secondsBetweenOccurrences: Pair<Float, Float> = 8F to 30F,
|
||||
loopTimes: IntRange = 1..1,
|
||||
condition: (state: PoseableEntityState<T>) -> Boolean = { true },
|
||||
animations: (state: PoseableEntityState<T>) -> List<StatefulAnimation<T, *>>
|
||||
) = SimpleQuirk(
|
||||
name = name,
|
||||
secondsBetweenOccurrences = secondsBetweenOccurrences,
|
||||
loopTimes = loopTimes,
|
||||
condition = condition,
|
||||
animations = { animations(it) }
|
||||
)
|
||||
|
||||
val dummyAnimation = object : StatefulAnimation<T, ModelFrame> {
|
||||
override val isTransform = false
|
||||
override val duration: Float = 1F
|
||||
|
||||
override fun run(
|
||||
entity: T?,
|
||||
model: PoseableEntityModel<T>,
|
||||
state: PoseableEntityState<T>,
|
||||
limbSwing: Float,
|
||||
limbSwingAmount: Float,
|
||||
ageInTicks: Float,
|
||||
headYaw: Float,
|
||||
headPitch: Float,
|
||||
intensity: Float
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,20 +9,46 @@
|
||||
package com.cobblemon.mod.common.client.render.models.blockbench
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.struct.VariableStruct
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
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.addFunction
|
||||
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.ObjectValue
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.client.particle.BedrockParticleEffectRepository
|
||||
import com.cobblemon.mod.common.client.particle.ParticleStorm
|
||||
import com.cobblemon.mod.common.client.entity.GenericBedrockClientDelegate
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
|
||||
import com.cobblemon.mod.common.api.scheduling.Schedulable
|
||||
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
|
||||
import com.cobblemon.mod.common.client.render.MatrixWrapper
|
||||
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.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.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.client.render.models.blockbench.wavefunction.WaveFunction
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunctions
|
||||
import com.cobblemon.mod.common.entity.Poseable
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.cobblemon.mod.common.util.resolve
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.sound.PositionedSoundInstance
|
||||
import net.minecraft.client.world.ClientWorld
|
||||
import net.minecraft.entity.Entity
|
||||
import net.minecraft.sound.SoundCategory
|
||||
import net.minecraft.sound.SoundEvent
|
||||
import net.minecraft.util.math.Vec3d
|
||||
|
||||
/**
|
||||
@@ -35,13 +61,90 @@ import net.minecraft.util.math.Vec3d
|
||||
*/
|
||||
abstract class PoseableEntityState<T : Entity> : Schedulable {
|
||||
var currentModel: PoseableEntityModel<T>? = null
|
||||
set(value) {
|
||||
field = value
|
||||
runtime.environment.getQueryStruct().addFunctions(value?.functions?.functions ?: hashMapOf())
|
||||
}
|
||||
var currentPose: String? = null
|
||||
var primaryAnimation: PrimaryAnimation<T>? = null
|
||||
val statefulAnimations: MutableList<StatefulAnimation<T, *>> = mutableListOf()
|
||||
val quirks = mutableMapOf<ModelQuirk<T, *>, QuirkData<T>>()
|
||||
val poseParticles = mutableListOf<BedrockParticleKeyframe>()
|
||||
val runtime = MoLangRuntime().setup().also {
|
||||
it.environment.structs["query"] = it.environment.structs["variable"]
|
||||
val runtime = MoLangRuntime().setup().setupClient().also { runtime ->
|
||||
val reusableAnimTime = DoubleValue(0.0) // This gets called 500 million times so use a mutable value for runtime
|
||||
runtime.environment.getQueryStruct().addFunctions(mapOf(
|
||||
"anim_time" to java.util.function.Function { return@Function reusableAnimTime.also { it.value = animationSeconds.toDouble() } },
|
||||
"pose_type" to java.util.function.Function { return@Function StringValue((getEntity() as Poseable).getCurrentPoseType().name) },
|
||||
"pose" to java.util.function.Function { _ -> return@Function StringValue(currentPose ?: "") },
|
||||
"say" to java.util.function.Function { params -> MinecraftClient.getInstance().player?.sendMessage(params.getString(0).text()) ?: Unit },
|
||||
"sound" to java.util.function.Function { params ->
|
||||
val entity = getEntity() ?: return@Function Unit
|
||||
if (params.get<MoValue>(0) !is StringValue) {
|
||||
return@Function Unit
|
||||
}
|
||||
val soundEvent = SoundEvent.of(params.getString(0).asIdentifierDefaultingNamespace())
|
||||
if (soundEvent != null) {
|
||||
val volume = if (params.contains(1)) params.getDouble(1).toFloat() else 1F
|
||||
val pitch = if (params.contains(2)) params.getDouble(2).toFloat() else 1F
|
||||
MinecraftClient.getInstance().soundManager.play(
|
||||
PositionedSoundInstance(soundEvent, SoundCategory.NEUTRAL, volume, pitch, entity.world.random, entity.x, entity.y, entity.z)
|
||||
)
|
||||
}
|
||||
},
|
||||
"play_animation" to java.util.function.Function { params ->
|
||||
val animationParameter = params.get<MoValue>(0)
|
||||
val animation = if (animationParameter is ObjectValue<*>) {
|
||||
animationParameter.obj as BedrockStatefulAnimation<T>
|
||||
} else {
|
||||
currentModel?.getAnimation(this, animationParameter.asString(), runtime)
|
||||
}
|
||||
if (animation != null) {
|
||||
if (animation is PrimaryAnimation<T>) {
|
||||
addPrimaryAnimation(animation)
|
||||
} else {
|
||||
addStatefulAnimation(animation)
|
||||
}
|
||||
}
|
||||
return@Function Unit
|
||||
},
|
||||
"particle" to java.util.function.Function { params ->
|
||||
val particlesParam = params.get<MoValue>(0)
|
||||
val particles = mutableListOf<String>()
|
||||
when (particlesParam) {
|
||||
is StringValue -> particles.add(particlesParam.value)
|
||||
is VariableStruct -> particles.addAll(particlesParam.map.values.map { it.asString() })
|
||||
else -> return@Function Unit
|
||||
}
|
||||
|
||||
val effectIds = particles.map { it.asIdentifierDefaultingNamespace() }
|
||||
for (effectId in effectIds) {
|
||||
val locator = if (params.params.size > 1) params.getString(1) else "root"
|
||||
val effect = BedrockParticleEffectRepository.getEffect(effectId) ?: run {
|
||||
LOGGER.error("Unable to find a particle effect with id $effectId")
|
||||
return@Function Unit
|
||||
}
|
||||
|
||||
val entity = getEntity() ?: return@Function Unit
|
||||
val world = entity.world as ClientWorld
|
||||
val matrixWrapper = locatorStates[locator] ?: locatorStates["root"]!!
|
||||
|
||||
val particleRuntime = MoLangRuntime().setup().setupClient()
|
||||
particleRuntime.environment.getQueryStruct().addFunction("entity") { runtime.environment.getQueryStruct() }
|
||||
|
||||
val storm = ParticleStorm(
|
||||
effect = effect,
|
||||
matrixWrapper = matrixWrapper,
|
||||
world = world,
|
||||
runtime = particleRuntime,
|
||||
sourceVelocity = { entity.velocity },
|
||||
sourceAlive = { !entity.isRemoved },
|
||||
sourceVisible = { !entity.isInvisible }
|
||||
)
|
||||
|
||||
storm.spawn()
|
||||
}
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
val allStatefulAnimations: List<StatefulAnimation<T, *>> get() = statefulAnimations + quirks.flatMap { it.value.animations }
|
||||
@@ -76,6 +179,23 @@ abstract class PoseableEntityState<T : Entity> : Schedulable {
|
||||
|
||||
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
|
||||
* where detailed animations exist they will be used and where they don't there is still a fallback.
|
||||
*
|
||||
* E.g. ['thunderbolt', 'electric', 'special']
|
||||
*/
|
||||
fun addFirstAnimation(animation: Set<String>) {
|
||||
val model = currentModel ?: return
|
||||
val animation = animation.firstNotNullOfOrNull { model.getAnimation(this, it, runtime) } ?: return
|
||||
if (animation is PrimaryAnimation<T>) {
|
||||
addPrimaryAnimation(animation)
|
||||
} else {
|
||||
addStatefulAnimation(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 }
|
||||
|
||||
@@ -155,7 +275,7 @@ abstract class PoseableEntityState<T : Entity> : Schedulable {
|
||||
fun shouldIdleRun(idleAnimation: StatelessAnimation<T, *>, requiredIntensity: Float): Boolean {
|
||||
val primaryAnimation = primaryAnimation
|
||||
return if (primaryAnimation != null) {
|
||||
!primaryAnimation.prevents(idleAnimation) && this.primaryOverridePortion >= requiredIntensity
|
||||
!primaryAnimation.prevents(idleAnimation) || this.primaryOverridePortion > requiredIntensity
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -10,7 +10,12 @@ 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.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 net.minecraft.util.math.MathHelper
|
||||
|
||||
@@ -22,22 +27,36 @@ import net.minecraft.util.math.MathHelper
|
||||
* @since December 21st, 2021
|
||||
*/
|
||||
class BimanualSwingAnimation<T : Entity>(
|
||||
frame: BimanualFrame,
|
||||
frame: ModelFrame,
|
||||
/** 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
|
||||
) : StatelessAnimation<T, BimanualFrame>(frame) {
|
||||
override val targetFrame: Class<BimanualFrame> = BimanualFrame::class.java
|
||||
val amplitudeMultiplier: Float = 1F,
|
||||
val leftArm: Bone?,
|
||||
val rightArm: Bone?
|
||||
) : StatelessAnimation<T, ModelFrame>(frame) {
|
||||
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) {
|
||||
// Movement swing
|
||||
frame.rightArm.yaw += MathHelper.cos(limbSwing * swingPeriodMultiplier) * limbSwingAmount * amplitudeMultiplier * intensity
|
||||
frame.leftArm.yaw += MathHelper.cos(limbSwing * swingPeriodMultiplier) * limbSwingAmount * amplitudeMultiplier * intensity
|
||||
rightArm?.addRotation(Y_AXIS, MathHelper.cos(limbSwing * swingPeriodMultiplier) * limbSwingAmount * amplitudeMultiplier * intensity)
|
||||
leftArm?.addRotation(Y_AXIS, MathHelper.cos(limbSwing * swingPeriodMultiplier) * limbSwingAmount * amplitudeMultiplier * intensity)
|
||||
|
||||
// Idle sway
|
||||
frame.rightArm.roll += 1.0f * (MathHelper.cos(ageInTicks * 0.09f) * 0.05f + 0.05f) * intensity
|
||||
frame.rightArm.yaw += 1.0f * MathHelper.sin(ageInTicks * 0.067f) * 0.05f * intensity
|
||||
frame.leftArm.roll += -1.0f * (MathHelper.cos(ageInTicks * 0.09f) * 0.05f + 0.05f) * intensity
|
||||
frame.leftArm.yaw += -1.0f * MathHelper.sin(ageInTicks * 0.067f) * 0.05f * intensity
|
||||
rightArm?.addRotation(Z_AXIS, 1.0f * (MathHelper.cos(ageInTicks * 0.09f) * 0.05f + 0.05f) * intensity)
|
||||
rightArm?.addRotation(Y_AXIS, MathHelper.sin(ageInTicks * 0.067f) * 0.05f * intensity)
|
||||
leftArm?.addRotation(Z_AXIS, -1.0f * (MathHelper.cos(ageInTicks * 0.09f) * 0.05f + 0.05f) * intensity)
|
||||
leftArm?.addRotation(Y_AXIS, -1.0f * MathHelper.sin(ageInTicks * 0.067f) * 0.05f * intensity)
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,11 @@ 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.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 net.minecraft.util.math.MathHelper
|
||||
|
||||
@@ -23,15 +27,29 @@ import net.minecraft.util.math.MathHelper
|
||||
* @since December 21st, 2021
|
||||
*/
|
||||
class BipedWalkAnimation<T : Entity>(
|
||||
frame: BipedFrame,
|
||||
frame: ModelFrame,
|
||||
/** 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
|
||||
) : StatelessAnimation<T, BipedFrame>(frame) {
|
||||
override val targetFrame: Class<BipedFrame> = BipedFrame::class.java
|
||||
val amplitudeMultiplier: Float = 1.4F,
|
||||
val leftLeg: Bone?,
|
||||
val rightLeg: Bone?
|
||||
) : StatelessAnimation<T, ModelFrame>(frame) {
|
||||
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) {
|
||||
frame.rightLeg.pitch += MathHelper.cos(limbSwing * periodMultiplier + Math.PI.toFloat()) * limbSwingAmount * amplitudeMultiplier * intensity
|
||||
frame.leftLeg.pitch += MathHelper.cos(limbSwing * periodMultiplier) * limbSwingAmount * amplitudeMultiplier * intensity
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ 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.wavefunction.WaveFunction
|
||||
import net.minecraft.entity.Entity
|
||||
|
||||
@@ -34,11 +35,12 @@ class PrimaryAnimation<T : Entity>(
|
||||
}
|
||||
},
|
||||
val excludedLabels: Set<String> = emptySet(),
|
||||
) {
|
||||
val duration = animation.duration
|
||||
override val isTransform: Boolean = false
|
||||
): StatefulAnimation<T, ModelFrame> {
|
||||
var started = -1F
|
||||
override val duration: Float = animation.duration
|
||||
|
||||
fun run(
|
||||
override fun run(
|
||||
entity: T?,
|
||||
model: PoseableEntityModel<T>,
|
||||
state: PoseableEntityState<T>,
|
||||
@@ -47,9 +49,9 @@ class PrimaryAnimation<T : Entity>(
|
||||
ageInTicks: Float,
|
||||
headYaw: Float,
|
||||
headPitch: Float,
|
||||
severity: Float
|
||||
intensity: Float
|
||||
): Boolean {
|
||||
return animation.run(entity, model, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch, severity)
|
||||
return animation.run(entity, model, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch, intensity)
|
||||
}
|
||||
|
||||
fun prevents(idleAnimation: StatelessAnimation<T, *>) = idleAnimation.labels.intersect(excludedLabels).isEmpty() && "all" !in excludedLabels
|
||||
|
||||
@@ -10,7 +10,11 @@ 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.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 net.minecraft.util.math.MathHelper
|
||||
|
||||
@@ -23,17 +27,40 @@ import net.minecraft.util.math.MathHelper
|
||||
* @since December 4th, 2021
|
||||
*/
|
||||
class QuadrupedWalkAnimation<T : Entity>(
|
||||
frame: QuadrupedFrame,
|
||||
frame: ModelFrame,
|
||||
val legFrontLeft: Bone?,
|
||||
val legFrontRight: Bone?,
|
||||
val legBackLeft: Bone?,
|
||||
val legBackRight: Bone?,
|
||||
/** 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
|
||||
) : StatelessAnimation<T, QuadrupedFrame>(frame) {
|
||||
override val targetFrame: Class<QuadrupedFrame> = QuadrupedFrame::class.java
|
||||
) : StatelessAnimation<T, ModelFrame>(frame) {
|
||||
constructor(
|
||||
frame: QuadrupedFrame,
|
||||
periodMultiplier: Float = 0.6662F,
|
||||
amplitudeMultiplier: Float = 1.4F
|
||||
): this(
|
||||
frame = frame,
|
||||
legFrontLeft = frame.foreLeftLeg,
|
||||
legFrontRight = frame.foreRightLeg,
|
||||
legBackLeft = frame.hindLeftLeg,
|
||||
legBackRight = frame.hindRightLeg,
|
||||
periodMultiplier = periodMultiplier,
|
||||
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) {
|
||||
frame.hindRightLeg.pitch += MathHelper.cos(limbSwing * periodMultiplier) * limbSwingAmount * amplitudeMultiplier * intensity
|
||||
frame.hindLeftLeg.pitch += MathHelper.cos(limbSwing * periodMultiplier + Math.PI.toFloat()) * limbSwingAmount * amplitudeMultiplier * intensity
|
||||
frame.foreRightLeg.pitch += MathHelper.cos(limbSwing * periodMultiplier + Math.PI.toFloat()) * limbSwingAmount * amplitudeMultiplier * intensity
|
||||
frame.foreLeftLeg.pitch += MathHelper.cos(limbSwing * periodMultiplier) * limbSwingAmount * amplitudeMultiplier * intensity
|
||||
val hindRightLeg = legBackRight ?: return
|
||||
val hindLeftLeg = legBackLeft ?: return
|
||||
val foreRightLeg = legFrontRight ?: return
|
||||
val foreLeftLeg = legFrontLeft ?: return
|
||||
|
||||
hindRightLeg.addRotation(X_AXIS, MathHelper.cos(limbSwing * periodMultiplier) * limbSwingAmount * amplitudeMultiplier * intensity)
|
||||
hindLeftLeg.addRotation(X_AXIS, MathHelper.cos(limbSwing * periodMultiplier + Math.PI.toFloat()) * limbSwingAmount * amplitudeMultiplier * intensity)
|
||||
foreRightLeg.addRotation(X_AXIS, MathHelper.cos(limbSwing * periodMultiplier + Math.PI.toFloat()) * limbSwingAmount * amplitudeMultiplier * intensity)
|
||||
foreLeftLeg.addRotation(X_AXIS, MathHelper.cos(limbSwing * periodMultiplier) * limbSwingAmount * amplitudeMultiplier * intensity)
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityMo
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
|
||||
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.util.math.geometry.toRadians
|
||||
@@ -25,14 +27,39 @@ import net.minecraft.entity.Entity
|
||||
* @author Hiroku
|
||||
* @since December 5th, 2021
|
||||
*/
|
||||
class SingleBoneLookAnimation<T : Entity>(frame: HeadedFrame, val invertX: Boolean, val invertY: Boolean, val disableX: Boolean, val disableY: Boolean) : StatelessAnimation<T, HeadedFrame>(frame) {
|
||||
override val targetFrame: Class<HeadedFrame> = HeadedFrame::class.java
|
||||
class SingleBoneLookAnimation<T : Entity>(
|
||||
frame: ModelFrame,
|
||||
val bone: Bone?,
|
||||
val pitchMultiplier: Float = 1F,
|
||||
val yawMultiplier: Float = 1F,
|
||||
val maxPitch: Float = 70F,
|
||||
val minPitch: Float = 45F,
|
||||
val maxYaw: Float = 45F,
|
||||
) : StatelessAnimation<T, ModelFrame>(frame) {
|
||||
constructor(
|
||||
frame: HeadedFrame,
|
||||
invertX: Boolean,
|
||||
invertY: Boolean,
|
||||
disableX: Boolean,
|
||||
disableY: Boolean
|
||||
): this(
|
||||
frame = frame,
|
||||
bone = frame.head,
|
||||
pitchMultiplier = if (disableX) 0F else if (invertX) -1F else 1F,
|
||||
yawMultiplier = if (disableY) 0F else if (invertY) -1F else 1F,
|
||||
maxPitch = if (invertX) -45F else 45F,
|
||||
minPitch = if (invertX) -70F else -45F,
|
||||
maxYaw = if (invertY) -45F else 45F,
|
||||
)
|
||||
|
||||
|
||||
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) {
|
||||
val pitch = (if (invertX) -1 else 1) * headPitch.coerceIn(-45F, 70F)
|
||||
val yaw = (if (invertY) -1 else 1) * headYaw.coerceIn(-45F, 45F)
|
||||
|
||||
(if (!disableX) frame.head.addRotation(X_AXIS, pitch.toRadians() * intensity))
|
||||
(if (!disableY) frame.head.addRotation(Y_AXIS, yaw.toRadians() * intensity))
|
||||
val head = bone ?: return
|
||||
val pitch = pitchMultiplier * headPitch.coerceIn(minPitch, maxPitch)
|
||||
val yaw = yawMultiplier * headYaw.coerceIn(-maxYaw, maxYaw)
|
||||
head.addRotation(X_AXIS, pitch.toRadians() * intensity)
|
||||
head.addRotation(Y_AXIS, yaw.toRadians() * intensity)
|
||||
}
|
||||
}
|
||||
@@ -12,19 +12,40 @@ import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityMo
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
|
||||
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.wavefunction.WaveFunction
|
||||
import net.minecraft.entity.Entity
|
||||
import net.minecraft.item.BoneMealItem
|
||||
|
||||
class WingFlapIdleAnimation<T : Entity>(
|
||||
frame: BiWingedFrame,
|
||||
val flapFunction: WaveFunction,
|
||||
val timeVariable: (state: PoseableEntityState<T>?, limbSwing: Float, ageInTicks: Float) -> Float?,
|
||||
frame: ModelFrame,
|
||||
val leftWing: Bone?,
|
||||
val rightWing: Bone?,
|
||||
val rotation: WaveFunction,
|
||||
val timeVariable: (state: PoseableEntityState<T>?, limbSwing: Float, ageInTicks: Float) -> Float? = { state, _, _ -> state?.animationSeconds ?: 0F },
|
||||
val axis: Int
|
||||
) : StatelessAnimation<T, BiWingedFrame>(frame) {
|
||||
override val targetFrame = BiWingedFrame::class.java
|
||||
) : StatelessAnimation<T, ModelFrame>(frame) {
|
||||
constructor(
|
||||
frame: BiWingedFrame,
|
||||
flapFunction: WaveFunction,
|
||||
timeVariable: (state: PoseableEntityState<T>?, limbSwing: Float, ageInTicks: Float) -> Float?,
|
||||
axis: Int
|
||||
): this(
|
||||
frame = frame,
|
||||
leftWing = frame.leftWing,
|
||||
rightWing = frame.rightWing,
|
||||
rotation = flapFunction,
|
||||
timeVariable = timeVariable,
|
||||
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) {
|
||||
val time = timeVariable(state, limbSwing, ageInTicks) ?: 0F
|
||||
frame.leftWing.addRotation(axis, flapFunction(time) * intensity)
|
||||
frame.rightWing.addRotation(axis, -flapFunction(time) * intensity)
|
||||
val angle = rotation(time)
|
||||
leftWing?.addRotation(axis, angle)
|
||||
rightWing?.addRotation(axis, -angle)
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@ 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.MoLangFunctions.addFunctions
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
|
||||
import com.cobblemon.mod.common.api.snowstorm.BedrockParticleEffect
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.client.particle.ParticleStorm
|
||||
@@ -22,9 +24,11 @@ import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityMo
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
|
||||
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 net.minecraft.client.MinecraftClient
|
||||
@@ -76,10 +80,16 @@ class BedrockParticleKeyframe(
|
||||
return
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
val storm = ParticleStorm(
|
||||
effect = effect,
|
||||
matrixWrapper = matrixWrapper,
|
||||
world = world,
|
||||
runtime = particleRuntime,
|
||||
sourceVelocity = { entity.velocity },
|
||||
sourceAlive = { !entity.isRemoved && this in state.poseParticles },
|
||||
sourceVisible = { !entity.isInvisible },
|
||||
@@ -121,7 +131,7 @@ class BedrockInstructionKeyframe(
|
||||
val expressions: List<Expression>
|
||||
): BedrockEffectKeyframe(seconds) {
|
||||
override fun <T : Entity> run(entity: T, state: PoseableEntityState<T>) {
|
||||
expressions.forEach { expression -> BedrockAnimation.runInstruction(entity, state, expression) }
|
||||
expressions.forEach { expression -> state.runtime.resolve(expression) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,64 +142,11 @@ data class BedrockAnimation(
|
||||
val boneTimelines: Map<String, BedrockBoneTimeline>
|
||||
) {
|
||||
companion object {
|
||||
val functionMappings = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
|
||||
val sharedRuntime = MoLangRuntime().also {
|
||||
it.environment.structs["query"] = it.environment.structs["variable"]
|
||||
it.environment.structs["script"] = QueryStruct(functionMappings)
|
||||
}
|
||||
|
||||
var context: InstructionContext? = null
|
||||
|
||||
inline fun <reified T : Entity> registerInstruction(name: String, crossinline function: (entity: T, state: PoseableEntityState<T>, params: MoParams) -> Any) {
|
||||
functionMappings[name] = java.util.function.Function { args ->
|
||||
val ctx = context ?: return@Function Unit
|
||||
if (ctx.entity is T) {
|
||||
val ret = function(ctx.entity, ctx.state as PoseableEntityState<T>, args)
|
||||
if (ret is Unit) {
|
||||
return@Function 0.0
|
||||
} else {
|
||||
return@Function ret
|
||||
}
|
||||
}
|
||||
Unit
|
||||
}
|
||||
}
|
||||
|
||||
class InstructionContext(val entity: Entity, val state: PoseableEntityState<*>)
|
||||
|
||||
fun <T : Entity> runInstruction(entity: T, state: PoseableEntityState<T>, expression: Expression) {
|
||||
val ctx = InstructionContext(entity, state)
|
||||
context = ctx
|
||||
expression.evaluate(MoScope(), sharedRuntime.environment)
|
||||
context = null
|
||||
}
|
||||
|
||||
init {
|
||||
registerInstruction<Entity>("say") { entity, _, params ->
|
||||
MinecraftClient.getInstance().player?.sendMessage(params.getString(0).text())
|
||||
Unit
|
||||
}
|
||||
registerInstruction<Entity>("sound") { entity, _, params ->
|
||||
// Means we don't need to setup a sound registry entry for every single thing
|
||||
val soundEvent = SoundEvent.of(params.getString(0).asIdentifierDefaultingNamespace())
|
||||
if (soundEvent != null) {
|
||||
val volume = if (params.contains(1)) params.getDouble(1).toFloat() else 1F
|
||||
val pitch = if (params.contains(2)) params.getDouble(2).toFloat() else 1F
|
||||
MinecraftClient.getInstance().soundManager.play(
|
||||
PositionedSoundInstance(soundEvent, SoundCategory.NEUTRAL, volume, pitch, entity.world.random, entity.x, entity.y, entity.z)
|
||||
)
|
||||
}
|
||||
Unit
|
||||
}
|
||||
registerInstruction<Entity>("random") { entity, _, params ->
|
||||
val options = mutableListOf<MoValue>()
|
||||
var index = 0
|
||||
while (params.contains(index)) {
|
||||
options.add(params.get(index))
|
||||
index++
|
||||
}
|
||||
return@registerInstruction options.random() // Can throw an exception if they specified no args. They'd be idiots though.
|
||||
}
|
||||
val zero = DoubleValue(0.0)
|
||||
it.environment.getQueryStruct().addFunctions(mapOf(
|
||||
"anim_time" to java.util.function.Function { return@Function zero }
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,11 @@ object BedrockAnimationRepository {
|
||||
LOGGER.info("Loaded $animationCount animations from ${animationGroups.size} animation groups")
|
||||
}
|
||||
|
||||
fun tryGetAnimation(fileName: String, animationName: String): BedrockAnimation? {
|
||||
val animationGroup = animationGroups[fileName] ?: return null
|
||||
return animationGroup.animations[animationName]
|
||||
}
|
||||
|
||||
fun getAnimation(fileName: String, animationName: String): BedrockAnimation {
|
||||
val animationGroup = animationGroups[fileName]
|
||||
?: throw IllegalArgumentException("Unknown animation group: $fileName")
|
||||
|
||||
@@ -24,13 +24,7 @@ import net.minecraft.entity.Entity
|
||||
*/
|
||||
open class BedrockStatefulAnimation<T : Entity>(
|
||||
val animation: BedrockAnimation,
|
||||
var preventsIdleCheck: (T?, PoseableEntityState<T>, StatelessAnimation<T, *>) -> Boolean
|
||||
) : StatefulAnimation<T, ModelFrame> {
|
||||
fun setPreventsIdle(preventsIdle: Boolean): BedrockStatefulAnimation<T> {
|
||||
this.preventsIdleCheck = { _, _, _ -> preventsIdle }
|
||||
return this
|
||||
}
|
||||
|
||||
var startedSeconds = -1F
|
||||
var isTransformAnimation = false
|
||||
override val duration = animation.animationLength.toFloat()
|
||||
@@ -39,10 +33,6 @@ open class BedrockStatefulAnimation<T : Entity>(
|
||||
override val isTransform: Boolean
|
||||
get() = isTransformAnimation
|
||||
|
||||
fun isTransformAnimation(value: Boolean) = this.also {
|
||||
it.isTransformAnimation = value
|
||||
}
|
||||
|
||||
fun andThen(action: (entity: T, PoseableEntityState<T>) -> Unit) = this.also {
|
||||
it.afterAction = action
|
||||
}
|
||||
|
||||
@@ -29,12 +29,12 @@ class FossilModel(root: Bone) : PoseableEntityModel<Entity>() {
|
||||
var yGrowthPoint = 0F
|
||||
var maxScale = 1F
|
||||
var yTranslation = 0F // Offset inside the tank
|
||||
var animations: Array<StatelessAnimation<Entity, out ModelFrame>> = emptyArray()
|
||||
var tankAnimations: Array<StatelessAnimation<Entity, out ModelFrame>> = emptyArray()
|
||||
var tankQuirks: Array<ModelQuirk<Entity, *>> = emptyArray()
|
||||
override fun registerPoses() {
|
||||
registerPose(
|
||||
poseType = PoseType.SLEEP,
|
||||
idleAnimations = animations,
|
||||
idleAnimations = tankAnimations,
|
||||
quirks = tankQuirks
|
||||
)
|
||||
}
|
||||
|
||||
@@ -51,9 +51,9 @@ class AncientPokeBallModel(root: ModelPart) : PokeBallModel(root), PokeBallFrame
|
||||
transformTicks = 0
|
||||
)
|
||||
|
||||
shut.transitions[open] = { _, _ -> bedrockStateful("ancient_poke_ball", "open").andThen { entity, state -> state.setPose(open.poseName) } }
|
||||
open.transitions[shut] = { _, _ -> bedrockStateful("ancient_poke_ball", "shut").andThen { entity, state -> state.setPose(shut.poseName) } }
|
||||
midair.transitions[open] = shut.transitions[open]!!
|
||||
shut.transitions[open.poseName] = { _, _ -> bedrockStateful("ancient_poke_ball", "open").andThen { entity, state -> state.setPose(open.poseName) } }
|
||||
open.transitions[shut.poseName] = { _, _ -> bedrockStateful("ancient_poke_ball", "shut").andThen { entity, state -> state.setPose(shut.poseName) } }
|
||||
midair.transitions[open.poseName] = shut.transitions[open.poseName]!!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,9 +52,9 @@ open class PokeBallModel(root: ModelPart) : PoseableEntityModel<EmptyPokeBallEnt
|
||||
transformTicks = 0
|
||||
)
|
||||
|
||||
shut.transitions[open] = { _, _ -> bedrockStateful("poke_ball", "open").andThen { entity, state -> state.setPose(open.poseName) } }
|
||||
open.transitions[shut] = { _, _ -> bedrockStateful("poke_ball", "shut").andThen { entity, state -> state.setPose(shut.poseName) } }
|
||||
midair.transitions[open] = shut.transitions[open]!!
|
||||
shut.transitions[open.poseName] = { _, _ -> bedrockStateful("poke_ball", "open") }
|
||||
open.transitions[shut.poseName] = { _, _ -> bedrockStateful("poke_ball", "shut") }
|
||||
midair.transitions[open.poseName] = shut.transitions[open.poseName]!!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,19 +8,27 @@
|
||||
|
||||
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon
|
||||
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.JsonPose
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.JsonPoseableEntityModel
|
||||
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.bedrock.animation.BedrockStatefulAnimation
|
||||
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
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.Vec3dAdapter
|
||||
import com.google.gson.*
|
||||
import com.google.gson.ExclusionStrategy
|
||||
import com.google.gson.FieldAttributes
|
||||
import com.google.gson.GsonBuilder
|
||||
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 com.google.gson.annotations.SerializedName
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import java.lang.reflect.Type
|
||||
@@ -53,6 +61,7 @@ class JsonPokemonPoseableModel(override val rootPart: Bone) : PokemonPoseableMod
|
||||
).type,
|
||||
JsonPoseableEntityModel.StatefulAnimationAdapter { JsonPokemonPoseableModelAdapter.model!! }
|
||||
)
|
||||
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
|
||||
.registerTypeAdapter(Pose::class.java, PoseAdapter)
|
||||
.registerTypeAdapter(JsonPokemonPoseableModel::class.java, JsonPokemonPoseableModelAdapter)
|
||||
.create()
|
||||
@@ -93,12 +102,11 @@ class JsonPokemonPoseableModel(override val rootPart: Bone) : PokemonPoseableMod
|
||||
get() = _profileTranslation ?: Vec3d(0.0, 0.0, 0.0)
|
||||
|
||||
|
||||
|
||||
val faint: Supplier<StatefulAnimation<PokemonEntity, ModelFrame>>? = null
|
||||
val cry: Supplier<StatefulAnimation<PokemonEntity, ModelFrame>>? = null
|
||||
|
||||
override fun getFaintAnimation(pokemonEntity: PokemonEntity, state: PoseableEntityState<PokemonEntity>) = faint?.get()
|
||||
override val cryAnimation = CryProvider { _, _ -> cry?.get()?.also { if (it is BedrockStatefulAnimation) it.setPreventsIdle(false) } }
|
||||
override val cryAnimation = CryProvider { _, _ -> cry?.get() }
|
||||
|
||||
object JsonModelExclusion: ExclusionStrategy {
|
||||
override fun shouldSkipField(f: FieldAttributes): Boolean {
|
||||
@@ -116,7 +124,6 @@ class JsonPokemonPoseableModel(override val rootPart: Bone) : PokemonPoseableMod
|
||||
}
|
||||
|
||||
object StatefulAnimationAdapter : JsonDeserializer<Supplier<StatefulAnimation<PokemonEntity, ModelFrame>>> {
|
||||
var preventsIdleDefault = true
|
||||
override fun deserialize(json: JsonElement, type: Type, ctx: JsonDeserializationContext): Supplier<StatefulAnimation<PokemonEntity, ModelFrame>> {
|
||||
json as JsonPrimitive
|
||||
val animString = json.asString
|
||||
@@ -135,6 +142,7 @@ class JsonPokemonPoseableModel(override val rootPart: Bone) : PokemonPoseableMod
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object PoseAdapter : JsonDeserializer<Pose<PokemonEntity, ModelFrame>> {
|
||||
override fun deserialize(json: JsonElement, type: Type, ctx: JsonDeserializationContext): Pose<PokemonEntity, ModelFrame> {
|
||||
val model = JsonPokemonPoseableModelAdapter.model!!
|
||||
@@ -142,6 +150,7 @@ class JsonPokemonPoseableModel(override val rootPart: Bone) : PokemonPoseableMod
|
||||
val pose = JsonPose(model, obj)
|
||||
|
||||
val conditionsList = mutableListOf<(PokemonEntity) -> Boolean>()
|
||||
|
||||
val mustBeInBattle = json.get("isBattle")?.asBoolean
|
||||
if (mustBeInBattle != null) {
|
||||
conditionsList.add { mustBeInBattle == it.isBattling }
|
||||
@@ -153,73 +162,23 @@ class JsonPokemonPoseableModel(override val rootPart: Bone) : PokemonPoseableMod
|
||||
|
||||
val poseCondition: ((PokemonEntity) -> Boolean)? = if (conditionsList.isEmpty()) null else conditionsList.reduce { acc, function -> { acc(it) && function(it) } }
|
||||
|
||||
val transformedParts = obj.get("transformedParts")?.asJsonArray?.map {
|
||||
it as JsonObject
|
||||
val partName = it.get("part").asString
|
||||
val part = model.getPart(partName)
|
||||
val transformation = ModelPartTransformation(part)
|
||||
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 transformation.withPosition(position.x, position.y, position.z).withRotationDegrees(rotation.x, rotation.y, rotation.z).withVisibility(isVisible)
|
||||
}?.toTypedArray() ?: arrayOf()
|
||||
|
||||
val idleAnimations = (obj.get("animations")?.asJsonArray ?: JsonArray()).asJsonArray.mapNotNull {
|
||||
val animString = it.asString
|
||||
if (animString == "look") {
|
||||
return@mapNotNull JsonPokemonPoseableModelAdapter.model!!.singleBoneLook<PokemonEntity>()
|
||||
} else {
|
||||
val anim = animString.substringBefore("(")
|
||||
|
||||
if(ANIMATION_FACTORIES.contains(anim)) {
|
||||
return@mapNotNull ANIMATION_FACTORIES[anim]?.stateless(model, animString)
|
||||
}
|
||||
}
|
||||
return@mapNotNull null
|
||||
}.toTypedArray()
|
||||
|
||||
val quirks = (obj.get("quirks")?.asJsonArray ?: JsonArray()).map { json ->
|
||||
json as JsonObject
|
||||
val name = json.get("name").asString
|
||||
val animations: (state: PoseableEntityState<PokemonEntity>) -> List<StatefulAnimation<PokemonEntity, *>> = { _ ->
|
||||
(json.get("animations")?.asJsonArray ?: JsonArray()).map { animJson ->
|
||||
val animString = animJson.asString
|
||||
|
||||
val anim = animString.substringBefore("(")
|
||||
|
||||
StatefulAnimationAdapter.preventsIdleDefault = false
|
||||
val animation = if(ANIMATION_FACTORIES.contains(anim)) {
|
||||
ANIMATION_FACTORIES[anim]?.stateful(model, animString)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
StatefulAnimationAdapter.preventsIdleDefault = true
|
||||
animation
|
||||
}.filterNotNull()
|
||||
}
|
||||
|
||||
val loopTimes = json.get("loopTimes")?.asInt ?: 1
|
||||
val minSeconds = json.get("minSecondsBetweenOccurrences")?.asFloat ?: 8F
|
||||
val maxSeconds = json.get("maxSecondsBetweenOccurrences")?.asFloat ?: 30F
|
||||
|
||||
model.quirkMultiple(
|
||||
name = name,
|
||||
secondsBetweenOccurrences = minSeconds to maxSeconds,
|
||||
condition = { true },
|
||||
loopTimes = 1..loopTimes,
|
||||
animations = animations
|
||||
)
|
||||
}
|
||||
|
||||
return Pose(
|
||||
poseName = pose.poseName,
|
||||
poseTypes = pose.poseTypes.toSet(),
|
||||
condition = poseCondition,
|
||||
animations = pose.animations,
|
||||
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<PokemonEntity, out ModelFrame>, Pose<PokemonEntity, out ModelFrame>) -> StatefulAnimation<PokemonEntity, ModelFrame>>> {
|
||||
it.to to { _, _ -> it.animation.resolveObject(model.runtime).obj as StatefulAnimation<PokemonEntity, ModelFrame> }
|
||||
}.toMap()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,11 +87,6 @@ abstract class PokemonPoseableModel : PoseableEntityModel<PokemonEntity>() {
|
||||
}
|
||||
|
||||
open val cryAnimation: CryProvider = CryProvider { _, _ -> null }
|
||||
//
|
||||
// open fun getCryAnimation(
|
||||
// pokemonEntity: PokemonEntity,
|
||||
// state: PoseableEntityState<PokemonEntity>
|
||||
// ): StatefulAnimation<PokemonEntity, ModelFrame>? = null
|
||||
|
||||
override fun setupEntityTypeContext(entity: PokemonEntity?) {
|
||||
entity?.let {
|
||||
|
||||
@@ -33,7 +33,7 @@ class AerodactylModel(root: ModelPart) : PokemonPoseableModel(), HeadedFrame {
|
||||
lateinit var fly: PokemonPose
|
||||
|
||||
override fun registerPoses() {
|
||||
val blink = quirk("blink") { bedrockStateful("aerodactyl", "blink").setPreventsIdle(false)}
|
||||
val blink = quirk { bedrockStateful("aerodactyl", "blink")}
|
||||
sleep = registerPose(
|
||||
poseType = PoseType.SLEEP,
|
||||
idleAnimations = arrayOf(bedrock("aerodactyl", "sleep"))
|
||||
|
||||
@@ -41,7 +41,7 @@ class AlakazamModel(root: ModelPart) : PokemonPoseableModel(), HeadedFrame, Bipe
|
||||
lateinit var walk: PokemonPose
|
||||
|
||||
override fun registerPoses() {
|
||||
val blink = quirk("blink") { bedrockStateful("alakazam", "blink").setPreventsIdle(false)}
|
||||
val blink = quirk { bedrockStateful("alakazam", "blink")}
|
||||
standing = registerPose(
|
||||
poseName = "standing",
|
||||
poseTypes = STATIONARY_POSES + UI_POSES,
|
||||
|
||||
@@ -51,10 +51,10 @@ class ArbokModel(root: ModelPart) : PokemonPoseableModel(), HeadedFrame {
|
||||
val tail4WaveSegment = WaveSegment(modelPart = tail4, length = 11F)
|
||||
val tail5WaveSegment = WaveSegment(modelPart = tail5, length = 11F)
|
||||
|
||||
override val cryAnimation = CryProvider { _, _ -> bedrockStateful("arbok", "cry").setPreventsIdle(false) }
|
||||
override val cryAnimation = CryProvider { _, _ -> bedrockStateful("arbok", "cry") }
|
||||
|
||||
override fun registerPoses() {
|
||||
val blink = quirk("blink") { bedrockStateful("arbok", "blink").setPreventsIdle(false) }
|
||||
val blink = quirk { bedrockStateful("arbok", "blink") }
|
||||
// TODO tongue_flick
|
||||
|
||||
val wave = WaveAnimation<PokemonEntity>(
|
||||
|
||||
@@ -38,7 +38,7 @@ class BeedrillModel(root: ModelPart) : PokemonPoseableModel(), HeadedFrame, BiWi
|
||||
lateinit var standing: PokemonPose
|
||||
lateinit var walk: PokemonPose
|
||||
|
||||
override val cryAnimation = CryProvider { _, _ -> bedrockStateful("beedrill", "cry").setPreventsIdle(false) }
|
||||
override val cryAnimation = CryProvider { _, _ -> bedrockStateful("beedrill", "cry") }
|
||||
|
||||
override fun registerPoses() {
|
||||
sleep = registerPose(
|
||||
|
||||
@@ -1,109 +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.pokemon.gen1
|
||||
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.EarJoint
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.RangeOfMotion
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BimanualFrame
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.frame.EaredFrame
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPose
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPoseableModel
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.math.geometry.toRadians
|
||||
import net.minecraft.client.model.ModelPart
|
||||
import net.minecraft.util.math.Vec3d
|
||||
class BlastoiseModel(root: ModelPart) : PokemonPoseableModel(), HeadedFrame, BipedFrame, BimanualFrame, EaredFrame {
|
||||
override val rootPart = root.registerChildWithAllChildren("blastoise")
|
||||
override val head = getPart("head_ai")
|
||||
override val rightArm = getPart("arm_right")
|
||||
override val leftArm = getPart("arm_left")
|
||||
override val rightLeg = getPart("leg_right")
|
||||
override val leftLeg = getPart("leg_left")
|
||||
private val rightEar = getPart("ear_right")
|
||||
private val leftEar = getPart("ear_left")
|
||||
override val leftEarJoint = EarJoint(leftEar, ModelPartTransformation.Z_AXIS, RangeOfMotion(50F.toRadians(), 0F))
|
||||
override val rightEarJoint = EarJoint(rightEar, ModelPartTransformation.Z_AXIS, RangeOfMotion((-50F).toRadians(), 0F))
|
||||
|
||||
override val portraitScale = 1.7F
|
||||
override val portraitTranslation = Vec3d(-0.5, 1.4, 0.0)
|
||||
|
||||
override val profileScale = 0.63F
|
||||
override val profileTranslation = Vec3d(0.0, 0.8, 0.0)
|
||||
|
||||
override val cryAnimation = CryProvider { _, _ -> bedrockStateful("blastoise", "cry").setPreventsIdle(false) }
|
||||
|
||||
lateinit var sleep: PokemonPose
|
||||
lateinit var standing: PokemonPose
|
||||
lateinit var swimIdle: PokemonPose
|
||||
lateinit var swimMove: PokemonPose
|
||||
lateinit var walk: PokemonPose
|
||||
|
||||
override fun registerPoses() {
|
||||
val blink = quirk("blink") { bedrockStateful("blastoise", "blink").setPreventsIdle(false)}
|
||||
sleep = registerPose(
|
||||
poseType = PoseType.SLEEP,
|
||||
transformTicks = 10,
|
||||
idleAnimations = arrayOf(bedrock("blastoise", "sleep"))
|
||||
)
|
||||
|
||||
standing = registerPose(
|
||||
poseName = "standing",
|
||||
poseTypes = setOf(PoseType.NONE, PoseType.PROFILE, PoseType.PORTRAIT, PoseType.STAND),
|
||||
transformTicks = 10,
|
||||
quirks = arrayOf(blink),
|
||||
idleAnimations = arrayOf(
|
||||
singleBoneLook(),
|
||||
bedrock("blastoise", "ground_idle")
|
||||
)
|
||||
)
|
||||
|
||||
swimIdle = registerPose(
|
||||
poseName = "swim_idle",
|
||||
poseTypes = setOf(PoseType.FLOAT),
|
||||
transformTicks = 10,
|
||||
quirks = arrayOf(blink),
|
||||
idleAnimations = arrayOf(
|
||||
bedrock("blastoise", "water_idle"),
|
||||
singleBoneLook()
|
||||
)
|
||||
)
|
||||
|
||||
swimMove = registerPose(
|
||||
poseName = "swim_move",
|
||||
poseTypes = setOf(PoseType.SWIM),
|
||||
transformTicks = 10,
|
||||
quirks = arrayOf(blink),
|
||||
idleAnimations = arrayOf(
|
||||
singleBoneLook(),
|
||||
bedrock("blastoise", "water_swim")
|
||||
)
|
||||
)
|
||||
|
||||
walk = registerPose(
|
||||
poseType = PoseType.WALK,
|
||||
transformTicks = 10,
|
||||
quirks = arrayOf(blink),
|
||||
idleAnimations = arrayOf(
|
||||
singleBoneLook(),
|
||||
bedrock("blastoise", "ground_walk")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun getFaintAnimation(
|
||||
pokemonEntity: PokemonEntity,
|
||||
state: PoseableEntityState<PokemonEntity>
|
||||
) = if (state.isPosedIn(standing, walk, sleep)) bedrockStateful("blastoise", "faint") else null
|
||||
}
|
||||
@@ -36,10 +36,10 @@ class BulbasaurModel(root: ModelPart) : PokemonPoseableModel(), HeadedFrame, Qua
|
||||
lateinit var standing: PokemonPose
|
||||
lateinit var walk: PokemonPose
|
||||
|
||||
override val cryAnimation = CryProvider { _, _ -> bedrockStateful("bulbasaur", "cry").setPreventsIdle(false) }
|
||||
override val cryAnimation = CryProvider { _, _ -> bedrockStateful("bulbasaur", "cry") }
|
||||
|
||||
override fun registerPoses() {
|
||||
val blink = quirk("blink") { bedrockStateful("bulbasaur", "blink").setPreventsIdle(false) }
|
||||
val blink = quirk { bedrockStateful("bulbasaur", "blink") }
|
||||
|
||||
sleep = registerPose(
|
||||
poseType = PoseType.SLEEP,
|
||||
|
||||
@@ -35,7 +35,7 @@ class ButterfreeModel(root: ModelPart) : PokemonPoseableModel(), HeadedFrame, Bi
|
||||
|
||||
lateinit var sleep: PokemonPose
|
||||
|
||||
override val cryAnimation = CryProvider { _, _ -> bedrockStateful("butterfree", "cry").setPreventsIdle(false) }
|
||||
override val cryAnimation = CryProvider { _, _ -> bedrockStateful("butterfree", "cry") }
|
||||
|
||||
override fun registerPoses() {
|
||||
sleep = registerPose(
|
||||
|
||||
@@ -34,10 +34,10 @@ class CaterpieModel(root: ModelPart) : PokemonPoseableModel(), HeadedFrame {
|
||||
lateinit var standing: PokemonPose
|
||||
lateinit var walk: PokemonPose
|
||||
|
||||
override val cryAnimation = CryProvider { _, _ -> bedrockStateful("caterpie", "cry").setPreventsIdle(false) }
|
||||
override val cryAnimation = CryProvider { _, _ -> bedrockStateful("caterpie", "cry") }
|
||||
|
||||
override fun registerPoses() {
|
||||
val blink = quirk("blink") { bedrockStateful("caterpie", "blink").setPreventsIdle(false)}
|
||||
val blink = quirk { bedrockStateful("caterpie", "blink")}
|
||||
sleep = registerPose(
|
||||
poseType = PoseType.SLEEP,
|
||||
transformTicks = 10,
|
||||
|
||||
@@ -33,7 +33,7 @@ class ChanseyModel(root: ModelPart) : PokemonPoseableModel(), BipedFrame {
|
||||
lateinit var walk: PokemonPose
|
||||
|
||||
override fun registerPoses() {
|
||||
val blink = quirk("blink") { bedrockStateful("chansey", "blink").setPreventsIdle(false)}
|
||||
val blink = quirk { bedrockStateful("chansey", "blink")}
|
||||
standing = registerPose(
|
||||
poseName = "standing",
|
||||
poseTypes = STATIONARY_POSES + UI_POSES,
|
||||
|
||||
@@ -8,13 +8,11 @@
|
||||
|
||||
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen1
|
||||
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.createTransformation
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BiWingedFrame
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BimanualFrame
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPose
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPoseableModel
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation.Companion.Y_AXIS
|
||||
@@ -22,9 +20,10 @@ import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.PoseType.Companion.MOVING_POSES
|
||||
import com.cobblemon.mod.common.entity.PoseType.Companion.STATIONARY_POSES
|
||||
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpressionLike
|
||||
import net.minecraft.client.model.ModelPart
|
||||
import net.minecraft.util.math.Vec3d
|
||||
|
||||
class CharizardModel(root: ModelPart) : PokemonPoseableModel(), HeadedFrame, BipedFrame, BimanualFrame, BiWingedFrame {
|
||||
override val rootPart = root.registerChildWithAllChildren("charizard")
|
||||
override val head = getPart("head_ai")
|
||||
@@ -47,13 +46,20 @@ class CharizardModel(root: ModelPart) : PokemonPoseableModel(), HeadedFrame, Bip
|
||||
lateinit var flyIdle: PokemonPose
|
||||
lateinit var fly: PokemonPose
|
||||
|
||||
override val cryAnimation = CryProvider { _, _ -> bedrockStateful("charizard", "cry").setPreventsIdle(false) }
|
||||
|
||||
override fun registerPoses() {
|
||||
val blink = quirk("blink") { bedrockStateful("charizard", "blink").setPreventsIdle(false)}
|
||||
animations["physical"] = "q.bedrock_primary('charizard', 'physical', 'look', q.curve('symmetrical_wide'))".asExpressionLike()
|
||||
animations["special"] = "q.bedrock_primary('charizard', 'special', 'look', q.curve('symmetrical_wide'))".asExpressionLike()
|
||||
animations["status"] = "q.bedrock_primary('charizard', 'status', q.curve('symmetrical_wide'))".asExpressionLike()
|
||||
animations["recoil"] = "q.bedrock_stateful('charizard', 'recoil')".asExpressionLike()
|
||||
animations["cry"] = "q.bedrock_stateful('charizard', 'cry')".asExpressionLike()
|
||||
|
||||
val faint = "q.bedrock_primary('charizard', 'faint', q.curve('one'))".asExpressionLike()
|
||||
|
||||
val blink = quirk { bedrockStateful("charizard", "blink") }
|
||||
sleep = registerPose(
|
||||
poseType = PoseType.SLEEP,
|
||||
quirks = arrayOf(blink),
|
||||
animations = mutableMapOf("faint" to faint),
|
||||
idleAnimations = arrayOf(bedrock("charizard", "sleep"))
|
||||
)
|
||||
|
||||
@@ -61,16 +67,30 @@ class CharizardModel(root: ModelPart) : PokemonPoseableModel(), HeadedFrame, Bip
|
||||
poseName = "standing",
|
||||
poseTypes = STATIONARY_POSES - PoseType.HOVER + UI_POSES,
|
||||
quirks = arrayOf(blink),
|
||||
condition = { !it.isBattling },
|
||||
animations = mutableMapOf("faint" to faint),
|
||||
idleAnimations = arrayOf(
|
||||
singleBoneLook(),
|
||||
bedrock("charizard", "ground_idle")
|
||||
)
|
||||
)
|
||||
|
||||
registerPose(
|
||||
poseTypes = setOf(PoseType.STAND),
|
||||
poseName = "battle_standing",
|
||||
condition = { it.isBattling },
|
||||
animations = mutableMapOf("faint" to faint),
|
||||
idleAnimations = arrayOf(
|
||||
singleBoneLook(),
|
||||
bedrock("charizard", "battle_idle")
|
||||
)
|
||||
)
|
||||
|
||||
walk = registerPose(
|
||||
poseName = "walk",
|
||||
poseTypes = MOVING_POSES - PoseType.FLY,
|
||||
quirks = arrayOf(blink),
|
||||
animations = mutableMapOf("faint" to faint),
|
||||
idleAnimations = arrayOf(
|
||||
singleBoneLook(),
|
||||
bedrock("charizard", "ground_idle"),
|
||||
@@ -100,9 +120,4 @@ class CharizardModel(root: ModelPart) : PokemonPoseableModel(), HeadedFrame, Bip
|
||||
transformedParts = arrayOf(rootPart.createTransformation().addPosition(Y_AXIS, 6F))
|
||||
)
|
||||
}
|
||||
|
||||
override fun getFaintAnimation(
|
||||
pokemonEntity: PokemonEntity,
|
||||
state: PoseableEntityState<PokemonEntity>
|
||||
) = if (state.isPosedIn(standing, walk, sleep)) bedrockStateful("charizard", "faint") else null
|
||||
}
|
||||
@@ -41,7 +41,7 @@ class CharmanderModel(root: ModelPart) : PokemonPoseableModel(), HeadedFrame, Bi
|
||||
lateinit var standing: PokemonPose
|
||||
lateinit var walk: PokemonPose
|
||||
|
||||
override val cryAnimation = CryProvider { _, _ -> bedrockStateful("charmander", "cry").setPreventsIdle(false) }
|
||||
override val cryAnimation = CryProvider { _, _ -> bedrockStateful("charmander", "cry") }
|
||||
|
||||
override fun registerPoses() {
|
||||
sleep = registerPose(
|
||||
@@ -49,7 +49,7 @@ class CharmanderModel(root: ModelPart) : PokemonPoseableModel(), HeadedFrame, Bi
|
||||
idleAnimations = arrayOf(bedrock("charmander", "sleep"))
|
||||
)
|
||||
|
||||
val blink = quirk("blink") { bedrockStateful("charmander", "blink").setPreventsIdle(false) }
|
||||
val blink = quirk { bedrockStateful("charmander", "blink") }
|
||||
|
||||
standing = registerPose(
|
||||
poseName = "standing",
|
||||
|
||||
@@ -39,10 +39,10 @@ class CharmeleonModel(root: ModelPart) : PokemonPoseableModel(), HeadedFrame, Bi
|
||||
lateinit var standing: PokemonPose
|
||||
lateinit var walk: PokemonPose
|
||||
|
||||
override val cryAnimation = CryProvider { _, _ -> bedrockStateful("charmeleon", "cry").setPreventsIdle(false) }
|
||||
override val cryAnimation = CryProvider { _, _ -> bedrockStateful("charmeleon", "cry") }
|
||||
|
||||
override fun registerPoses() {
|
||||
val blink = quirk("blink") { bedrockStateful("charmeleon", "blink").setPreventsIdle(false)}
|
||||
val blink = quirk { bedrockStateful("charmeleon", "blink")}
|
||||
sleep = registerPose(
|
||||
poseType = PoseType.SLEEP,
|
||||
transformTicks = 10,
|
||||
|
||||
@@ -33,10 +33,10 @@ class ClefableModel(root: ModelPart) : PokemonPoseableModel() {
|
||||
lateinit var standing: PokemonPose
|
||||
lateinit var walk: PokemonPose
|
||||
|
||||
override val cryAnimation = CryProvider { _, _ -> bedrockStateful("clefable", "cry").setPreventsIdle(false) }
|
||||
override val cryAnimation = CryProvider { _, _ -> bedrockStateful("clefable", "cry") }
|
||||
|
||||
override fun registerPoses() {
|
||||
val blink = quirk("blink") { bedrockStateful("clefable", "blink").setPreventsIdle(false)}
|
||||
val blink = quirk { bedrockStateful("clefable", "blink")}
|
||||
standing = registerPose(
|
||||
poseName = "standing",
|
||||
poseTypes = STATIONARY_POSES + UI_POSES,
|
||||
|
||||
@@ -33,10 +33,10 @@ class ClefairyModel(root: ModelPart) : PokemonPoseableModel() {
|
||||
lateinit var standing: PokemonPose
|
||||
lateinit var walk: PokemonPose
|
||||
|
||||
override val cryAnimation = CryProvider { _, _ -> bedrockStateful("clefairy", "cry").setPreventsIdle(false) }
|
||||
override val cryAnimation = CryProvider { _, _ -> bedrockStateful("clefairy", "cry") }
|
||||
|
||||
override fun registerPoses() {
|
||||
val blink = quirk("blink") { bedrockStateful("clefairy", "blink").setPreventsIdle(false)}
|
||||
val blink = quirk { bedrockStateful("clefairy", "blink")}
|
||||
standing = registerPose(
|
||||
poseName = "standing",
|
||||
poseTypes = STATIONARY_POSES + UI_POSES,
|
||||
|
||||
@@ -32,7 +32,7 @@ class DiglettModel(root: ModelPart) : PokemonPoseableModel() {
|
||||
lateinit var walk: PokemonPose
|
||||
lateinit var sleep: PokemonPose
|
||||
override fun registerPoses() {
|
||||
val blink = quirk("blink") { bedrockStateful("diglett", "blink").setPreventsIdle(false)}
|
||||
val blink = quirk { bedrockStateful("diglett", "blink")}
|
||||
stand = registerPose(
|
||||
poseName = "stand",
|
||||
poseTypes = STATIONARY_POSES + UI_POSES,
|
||||
|
||||
@@ -33,10 +33,10 @@ class DittoModel(root: ModelPart) : PokemonPoseableModel() {
|
||||
lateinit var walk: PokemonPose
|
||||
lateinit var sleep: PokemonPose
|
||||
|
||||
override val cryAnimation = CryProvider { _, _ -> bedrockStateful("ditto", "cry").setPreventsIdle(false) }
|
||||
override val cryAnimation = CryProvider { _, _ -> bedrockStateful("ditto", "cry") }
|
||||
|
||||
override fun registerPoses() {
|
||||
val blink = quirk("blink") { bedrockStateful("ditto", "blink").setPreventsIdle(false) }
|
||||
val blink = quirk { bedrockStateful("ditto", "blink") }
|
||||
standing = registerPose(
|
||||
poseName = "standing",
|
||||
poseTypes = STATIONARY_POSES + UI_POSES,
|
||||
|
||||
@@ -32,9 +32,9 @@ class DodrioModel (root: ModelPart) : PokemonPoseableModel() {
|
||||
lateinit var battleidle: PokemonPose
|
||||
|
||||
override fun registerPoses() {
|
||||
val blink1 = quirk("blink1") { bedrockStateful("dodrio", "blink1").setPreventsIdle(false) }
|
||||
val blink2 = quirk("blink2") { bedrockStateful("dodrio", "blink2").setPreventsIdle(false) }
|
||||
val blink3 = quirk("blink3") { bedrockStateful("dodrio", "blink3").setPreventsIdle(false) }
|
||||
val blink1 = quirk { bedrockStateful("dodrio", "blink1") }
|
||||
val blink2 = quirk { bedrockStateful("dodrio", "blink2") }
|
||||
val blink3 = quirk { bedrockStateful("dodrio", "blink3") }
|
||||
sleep = registerPose(
|
||||
poseType = PoseType.SLEEP,
|
||||
idleAnimations = arrayOf(bedrock("dodrio", "sleep"))
|
||||
|
||||
@@ -32,10 +32,10 @@ class DoduoModel (root: ModelPart) : PokemonPoseableModel() {
|
||||
lateinit var battleidle: PokemonPose
|
||||
|
||||
override fun registerPoses() {
|
||||
val blink = quirk("blink") { bedrockStateful("doduo", "blink1").setPreventsIdle(false) }
|
||||
val blink2 = quirk("blink2") { bedrockStateful("doduo", "blink2").setPreventsIdle(false) }
|
||||
val bite = quirk("bite") { bedrockStateful("doduo", "bite_quirk1").setPreventsIdle(false) }
|
||||
val bite2 = quirk("bite2") { bedrockStateful("doduo", "bite_quirk2").setPreventsIdle(false) }
|
||||
val blink = quirk { bedrockStateful("doduo", "blink1") }
|
||||
val blink2 = quirk { bedrockStateful("doduo", "blink2") }
|
||||
val bite = quirk { bedrockStateful("doduo", "bite_quirk1") }
|
||||
val bite2 = quirk { bedrockStateful("doduo", "bite_quirk2") }
|
||||
|
||||
sleep = registerPose(
|
||||
poseType = PoseType.SLEEP,
|
||||
|
||||
@@ -32,7 +32,7 @@ class DragonairModel(root: ModelPart) : PokemonPoseableModel(), HeadedFrame {
|
||||
// TODO dragonair swish
|
||||
|
||||
override fun registerPoses() {
|
||||
val blink = quirk("blink") { bedrockStateful("dragonair", "blink").setPreventsIdle(false)}
|
||||
val blink = quirk { bedrockStateful("dragonair", "blink")}
|
||||
standing = registerPose(
|
||||
poseName = "standing",
|
||||
poseTypes = STATIONARY_POSES + MOVING_POSES + UI_POSES,
|
||||
|
||||
@@ -47,7 +47,7 @@ class DragoniteModel(root: ModelPart) : PokemonPoseableModel(), HeadedFrame, BiW
|
||||
lateinit var fly: PokemonPose
|
||||
|
||||
override fun registerPoses() {
|
||||
val blink = quirk("blink") { bedrockStateful("dragonite", "blink").setPreventsIdle(false)}
|
||||
val blink = quirk { bedrockStateful("dragonite", "blink")}
|
||||
standing = registerPose(
|
||||
poseName = "standing",
|
||||
poseTypes = UI_POSES + PoseType.STAND,
|
||||
|
||||
@@ -30,7 +30,7 @@ class DratiniModel(root: ModelPart) : PokemonPoseableModel(), HeadedFrame {
|
||||
lateinit var standing: PokemonPose
|
||||
|
||||
override fun registerPoses() {
|
||||
val blink = quirk("blink") { bedrockStateful("dratini", "blink").setPreventsIdle(false)}
|
||||
val blink = quirk { bedrockStateful("dratini", "blink")}
|
||||
standing = registerPose(
|
||||
poseName = "standing",
|
||||
poseTypes = STATIONARY_POSES + MOVING_POSES + UI_POSES,
|
||||
|
||||
@@ -38,7 +38,7 @@ class DrowzeeModel(root: ModelPart) : PokemonPoseableModel(), HeadedFrame, Biped
|
||||
lateinit var battleidle: PokemonPose
|
||||
|
||||
override fun registerPoses() {
|
||||
val blink = quirk("blink") { bedrockStateful("drowzee", "blink").setPreventsIdle(false)}
|
||||
val blink = quirk { bedrockStateful("drowzee", "blink")}
|
||||
sleep = registerPose(
|
||||
poseType = PoseType.SLEEP,
|
||||
idleAnimations = arrayOf(bedrock("drowzee", "sleep"))
|
||||
|
||||