Compare commits

...
Author SHA1 Message Date
JacksonB 3061ad2cfd Add current rots and simplify names for riding animation molang. 2025-03-30 11:43:44 -05:00
JacksonB 5e04465970 Add a lookback buffer to the spring class to allow riding molang to determine how far back they want to pull their animation influencing ride data from. 2025-03-29 02:07:54 -05:00
JacksonB 9f0afd05ea Implement damped spring class for updating dynamic riding animation data. 2025-03-28 12:44:14 -05:00
JacksonB 702a670028 quick fix due to merge 2025-03-25 22:05:10 -05:00
JacksonB b9a8ed15d0 Merge branch 'main' into jackowes/riding/04 2025-03-24 22:05:26 -05:00
Hiroku 2413981f6f Merge branch 'charizardmarketing' into 'main'
Updated Charizard with the latest model and animations to be used for marketing on the main branch.

See merge request cable-mc/cobblemon!1572
2025-03-24 01:23:40 +00:00
Tyzillion 4ac244003a -6+ ?????? 2025-03-23 21:22:47 -04:00
Tyzillion 9ac92a084f Updated Charizard with new locators. 2025-03-23 21:18:31 -04:00
Apion aacbcb1f28 Merge branch 'jackowes/concrete_bird' into 'main'
Overhaul of bird and fix of jet velocity bug

See merge request cable-mc/cobblemon!1563
2025-03-23 23:37:47 +00:00
Jackson BowesandApion 39ff7e418c Overhaul of bird and fix of jet velocity bug 2025-03-23 23:37:47 +00:00
Hiroku 58498a091d Merge branch 'josh/npc-item-rendering' into 'main'
Molang for rendering items during animations.

See merge request cable-mc/cobblemon!1570
2025-03-23 23:12:06 +00:00
Hiroku a806f77aff Merge branch 'entity-scale' into 'main'
Pokemon hitboxes scale with attribute generic.scale

Closes #1410

See merge request cable-mc/cobblemon!1529
2025-03-23 22:05:38 +00:00
JordanandHiroku 60ae9e3a55 Pokemon hitboxes scale with attribute generic.scale 2025-03-23 22:05:37 +00:00
JacksonB c8f110cce8 didn't want to stash this 2025-03-23 15:13:43 -05:00
JacksonB 4910350fd8 WIP addition of molang functions for dynamic riding animations. Addition of Molang query-able alt poses during riding to initiate animations such as gliding, sprinting, braking, and jumping. Is made to be expandable if further alt poses are needed. Also almost full land controller implementation. And decoupling of minecraft travel logic once again. 2025-03-23 15:07:30 -05:00
Josh 4bfe147730 Merge branch 'main' into josh/npc-item-rendering 2025-03-22 12:41:31 -04:00
Josh 963614f5fa Updated CHANGELOG.md 2025-03-21 11:51:18 -04:00
Josh 7fdb70e3c3 Updated trainer npcs for testing 2025-03-21 11:20:24 -04:00
JacksonB f316dc7f6f Merge branch 'jackowes/concrete_bird' into jackowes/riding/04 2025-03-20 23:57:16 -05:00
JacksonB 1d412b2656 Add test animation friendly molang stuff 2025-03-20 23:54:55 -05:00
JacksonB 53d93d1c7c Give the land controller the red dead redemption treatment. 2025-03-20 19:02:37 -05:00
Josh 37e8f0eab6 multiple items can render on a model per locator and the render_item() molang function now supports items with components 2025-03-20 16:14:16 -04:00
Josh 05edafa5c4 NPC Trainers send out/return animation are able to show the correct ball. 2025-03-19 22:35:00 -04:00
Josh cea600617e Added render_item() molang function for PosableState. Allows rendering dummy items for animations. 2025-03-19 18:47:11 -04:00
JacksonB 41ea22f58f Add ability to queuery an alternate pose. Useful for sprints and glides on rides 2025-03-15 21:09:10 -05:00
JacksonB 666f339561 need this gone 2025-03-15 20:57:33 -05:00
JacksonB 104eb4d825 Overhaul bird and fix erroneous upwards velocity on jet. 2025-03-15 16:29:37 -05:00
JacksonB 8a15770f33 Bring personal ride branch up to date with main. 2025-03-15 16:06:55 -05:00
JacksonB 9e29b4f4f8 Fix unwanted upwards movement on Jet during level out 2025-03-15 11:51:02 -05:00
JacksonB 308c9d61c6 Practically an overhaul of bird. Also everything but altitude implemented. 2025-03-14 21:11:02 -05:00
JacksonB 07ad22608d Migrate bird logic over to rideVectors and add lateral and vertical movement to bird 2025-03-14 12:58:02 -05:00
JacksonB b50f924ce0 Jet fully transferred to velocity based calculations rather than speed. A few tweaks to generic land as well. 2025-03-13 23:43:04 -05:00
JacksonB c0b55ebb6f Further dirty this branch before cleaning :( 2025-03-13 20:18:05 -05:00
JacksonB cdc0a790fb Simplification of data driven function to control rideControllers base stat interpolation. Min and max are now inside the function 2025-03-03 22:51:49 -06:00
JacksonB 13e8084f0d merge and fixes to merge of controller work 2025-03-02 12:39:06 -06:00
JacksonB 3daa3d31a3 initial fixes 2025-03-02 12:02:29 -06:00
JacksonB 585371bb08 initial commit 2 2025-03-01 22:35:38 -06:00
JacksonB aee97ff96e initial commit 2025-03-01 22:35:20 -06:00
JacksonB fbbd450d0d Address MR comments. Also implement a seperate jet flight composite controller to handle transition from jetstate data to groundstate data. 2025-02-25 19:24:23 -06:00
JacksonB 83612b50fc Initial cleanup before MR 2025-02-22 19:07:51 -06:00
JacksonB 434faea210 Merge branch 'jackowes/ride_controllers' into jackowes/ride_cleanup 2025-02-22 17:40:24 -06:00
JacksonB 6e24ee6b4e last commit before cleanup and MR 2025-02-22 17:29:18 -06:00
JacksonB 3d6a3fbfec Fix Jet sensitivity issues 2025-02-20 20:12:54 -06:00
JacksonB 708b810e6b Set up begginings of stamina using vanilla jump bar and change jet turning to accumulated input method. 2025-02-20 00:25:02 -06:00
JacksonB 509016e133 Changes to how ride handling is handled and jet controls 2025-02-19 15:07:19 -06:00
JacksonB 690183b994 Squashed commit of the following:
commit 9497175627
Author: JacksonB <78700961+jackowes@users.noreply.github.com>
Date:   Thu Feb 13 00:05:03 2025 -0600

    mk2 bird flight controller

commit 0defd2614f
Author: JacksonB <78700961+jackowes@users.noreply.github.com>
Date:   Wed Feb 12 20:01:57 2025 -0600

    Initial bird controller changes and refactoring of how riding controllers control rotation mouse+rotVel

commit 181af6847d
Author: JacksonB <78700961+jackowes@users.noreply.github.com>
Date:   Wed Feb 12 12:29:35 2025 -0600

    Add updated version of Helicopter ride controller. Tie in some static pokemonentity functions to be riding configurable. Modify the cameramixins, currently there is a temp change to them on this branch.

commit ac1cdb0ba6
Author: JacksonB <78700961+jackowes@users.noreply.github.com>
Date:   Sat Feb 8 03:02:35 2025 -0600

    Add angular velocity constant modifier (roll can now influence yaw so the dolphin can turn). Spruce up dolphin swim controller.

commit 59190209a2
Author: JacksonB <78700961+jackowes@users.noreply.github.com>
Date:   Wed Feb 5 19:30:21 2025 -0600

    initial commit with a bunch of poking around code
2025-02-18 18:20:31 -06:00
JacksonB 33d56124f5 Controller fixes due to some refactoring and velocity handling changes 2025-02-17 18:11:51 -06:00
JacksonB a5a65abe2b Changes to how velocity is handled and some fixes to jet and bird. 2025-02-17 02:01:20 -06:00
JacksonB f73ab3b001 Jet implementation and intermediate velocity and speed handling changes( this commit has broken ride controllers due to this) 2025-02-16 13:14:56 -06:00
JacksonB 9a571984db Rebase off of main transport branch. Make some tweaks to bird and helicopter controls as well as adding a WIP jet controller. 2025-02-14 16:08:13 -06:00
54 changed files with 2154 additions and 904 deletions
+2
View File
@@ -17,6 +17,7 @@
- Substantially optimised spawning checks mainly by front-loading biome filtering.
- When using the `cobblemon` or `generation_9` capture calculators a critical capture with a single shake will always play for successful captures when you've already registered the Pokémon as caught in your Pokédex.
- Improved the performance of saving Pokédex and player data.
- Pokémon hitbox now scales with entity attribute `generic.scale`.
### Fixes
- Fixed Particles sometimes facing the wrong direction (looking at you, Swords Dance)
@@ -71,6 +72,7 @@
- Added `interpolate` boolean property to animated textures to allow gradual colour changes between frames.
- Fixed species additions not being capable of changing implemented status.
- Added support for action effects that are triggered by `|-activate|` Showdown instructions. `activate_{effect_id}` is the syntax.
- Added Molang functions for rendering items `render_item(item_id, locator_name)` and `clear_items()`.
## [1.6.1 (January 26th, 2025)](#1-6-1)
@@ -41,6 +41,7 @@ import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.controller.posing.RidingAltPose
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
@@ -71,6 +72,8 @@ import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.LevelUpEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.TradeEvolution
import com.cobblemon.mod.common.pokemon.riding.controllers.BirdAirController
import com.cobblemon.mod.common.pokemon.riding.states.BirdAirState
import com.cobblemon.mod.common.util.*
import com.mojang.datafixers.util.Either
import java.util.UUID
@@ -506,6 +509,7 @@ object MoLangFunctions {
map.put("world") { _ -> entity.level().worldRegistry.wrapAsHolder(entity.level()).asWorldMoLangValue() }
map.put("biome") { _ -> entity.level().getBiome(entity.blockPosition()).asBiomeMoLangValue() }
map.put("is_passenger") { DoubleValue(entity.isPassenger) }
map.put("has_passenger") { DoubleValue(entity.passengers.size > 0) }
map.put("find_nearby_block") { params ->
val type = params.getString(0).asIdentifierDefaultingNamespace(namespace = "minecraft")
val isTag = type.path.startsWith("#")
@@ -786,6 +790,17 @@ object MoLangFunctions {
map.put("in_battle") { DoubleValue(pokemonEntity.isBattling) }
map.put("is_moving") { DoubleValue((pokemonEntity.moveControl as? PokemonMoveControl)?.hasWanted() == true) }
map.put("is_flying") { DoubleValue(pokemonEntity.getBehaviourFlag(PokemonBehaviourFlag.FLYING)) }
map.put("riding_alt_pose") { params ->
val poseName = params.getString(0).uppercase()
val targetPose = RidingAltPose.fromString(poseName)
if (targetPose == null) {
//Invalid pose name
DoubleValue(0.0)
} else {
val currentPose = pokemonEntity.getActiveRidingAltPose()
DoubleValue(if (currentPose == targetPose) 1.0 else 0.0)
}
}
map.put("has_aspect") { DoubleValue(it.getString(0) in pokemonEntity.aspects) }
map.put("is_pokemon") { DoubleValue.ONE }
map.put("is_holding_item") { DoubleValue(!pokemonEntity.entityData.get(PokemonEntity.SHOWN_HELD_ITEM).isEmpty) }
@@ -13,13 +13,17 @@ import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.controller.posing.RidingAltPose
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.riding.util.Vec3Spring
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.toProperties
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.authlib.minecraft.client.MinecraftClient
import net.minecraft.ChatFormatting
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
@@ -32,6 +36,16 @@ data class RidingManager(val entity: PokemonEntity) {
var lastSpeed = 0F
var states: MutableMap<ResourceLocation, RidingState> = mutableMapOf()
var deltaRotation = Vec2.ZERO
var deltaRotationPerSec = Vec2.ZERO
//Riding animation data
var prevRotation = Vec3.ZERO
var rotSpring = Vec3Spring()
var rotDeltaSpring = Vec3Spring()
var velSpring = Vec3Spring()
val stiffness = 90.0
val damping = 19.0
val runtime: MoLangRuntime by lazy {
MoLangRuntime()
@@ -77,12 +91,28 @@ data class RidingManager(val entity: PokemonEntity) {
val pose = controller.pose(entity)
entity.entityData.set(PokemonEntity.POSE_TYPE, pose)
//val speedPlayer = driver.deltaMovement.horizontalDistance()
//driver.displayClientMessage(Component.literal("Speed player: ").withStyle { it.withColor(ChatFormatting.GREEN) }.append(Component.literal("$speedPlayer b/t")), true)
//val speedEntity = entity.deltaMovement.length() //* 20 * 60 * 60) / ( 1000 )
//println(speedEntity)
//driver.displayClientMessage(Component.literal("Speed: ").withStyle { it.withColor(ChatFormatting.GREEN) }.append(Component.literal("${String.format("%.2f", speedEntity)} km/h")), true)
// maybe here I do the required updates for riding animation stuff.
val rollable = driver as? Rollable ?: return
val r = this.shouldRoll(entity)
val currYaw = if(r) rollable.yaw else entity.yRot
val currPitch = if(r) rollable.pitch else entity.xRot
val currRoll = rollable.roll
val angDelta = Vec3(
Mth.degreesDifference(currPitch.toFloat(), prevRotation.x.toFloat()).toDouble(),
Mth.degreesDifference(currYaw.toFloat(), prevRotation.y.toFloat()).toDouble(),
Mth.degreesDifference(currRoll.toFloat(), prevRotation.z.toFloat()).toDouble()
)
rotSpring.update(Vec3(currPitch.toDouble(), currYaw.toDouble(), currRoll.toDouble()), stiffness, damping)
rotDeltaSpring.update( angDelta, stiffness, damping)
prevRotation = Vec3(currPitch.toDouble(), currYaw .toDouble(), currRoll.toDouble())
}
fun speed(entity: PokemonEntity, driver: Player): Float {
@@ -96,6 +126,8 @@ data class RidingManager(val entity: PokemonEntity) {
val previousRotation = entity.rotationVector
val rotation = controller.rotation(entity, driver)
this.deltaRotation = Vec2(rotation.x - previousRotation.x, rotation.y - previousRotation.y)
this.deltaRotationPerSec = deltaRotation.scale(20.0f)
return rotation
}
@@ -111,6 +143,8 @@ data class RidingManager(val entity: PokemonEntity) {
fun velocity(entity: PokemonEntity, driver: Player, input: Vec3): Vec3 {
val controller = getController(entity) ?: return Vec3.ZERO
this.velSpring.update(entity.deltaMovement, stiffness, damping)
return controller.velocity(entity, driver, input)
}
@@ -134,6 +168,11 @@ data class RidingManager(val entity: PokemonEntity) {
return controller.jumpForce(entity, driver, jumpStrength)
}
fun getActiveRidingAltPose(entity: PokemonEntity, driver: Player): RidingAltPose {
val controller = getController(entity) ?: return RidingAltPose.NONE
return controller.getActiveRidingAltPose(entity, driver)
}
/**
* Used to supply the delta for the lerp() between velocity
* vectors. Called inertia for now as the way it feels when
@@ -8,6 +8,8 @@
package com.cobblemon.mod.common.api.riding
import net.minecraft.world.phys.Vec3
/**
* Honestly having a base interface is probably unnecessary I'm just considering whether there's going to be some
* kind of shared state later.
@@ -15,4 +17,7 @@ package com.cobblemon.mod.common.api.riding
* @author Hiroku
* @since October 6th, 2024
*/
interface RidingState
interface RidingState {
var rideVel: Vec3
var stamina: Float
}
@@ -8,14 +8,18 @@
package com.cobblemon.mod.common.api.riding.controller
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.net.Decodable
import com.cobblemon.mod.common.api.net.Encodable
import com.cobblemon.mod.common.api.riding.RidingState
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.controller.posing.RidingAltPose
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.writeIdentifier
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
@@ -54,6 +58,14 @@ interface RideController : Encodable, Decodable {
*/
val condition: (PokemonEntity) -> Boolean
/**
* Returns the evaluation of the current stat when mapped to the given molang expression
* that the controller implements. For composite controllers the active controller will
* be determined rather than it evaluating its own expression.
*/
fun getStat(entity: PokemonEntity, stat: RidingStat): Double
/**
* Specifies the pose the ridden mount should be positioned in. This queries the pose provider using
* the entity as a means of determining the conditions the entity is currently under. This allows for
@@ -111,6 +123,8 @@ interface RideController : Encodable, Decodable {
fun useAngVelSmoothing(entity: PokemonEntity): Boolean = false
fun getActiveRidingAltPose(entity: PokemonEntity, driver: Player): RidingAltPose = RidingAltPose.NONE
//If function is not overwritten by controllers then just perform the defualt
//rolling function which is roll on mousex and pitch on mousey
fun rotationOnMouseXY(
@@ -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.api.riding.controller.posing
/**
* The pose that will be used to determine if an alternate pose should be used while riding.
*
* @author Jackowes
* @since March 23rd, 2025
*/
enum class RidingAltPose {
NONE,
GLIDING,
SPRINTING,
BRAKING,
JUMPING;
companion object {
fun fromString(name: String): RidingAltPose? {
return entries.firstOrNull { it.name.equals(name, ignoreCase = true) }
}
}
}
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.util
import net.minecraft.world.phys.Vec3
import kotlin.math.min
/**
* A spring-based smoother for Vec3 values, designed for applying damped spring motion to riding data.
* Stores the previous and current values, along with a history buffer for lookback-based interpolation.
*
* @author Jackowes
* @since March 27th, 2025
*/
class Vec3Spring(
var value: Vec3 = Vec3.ZERO,
var velocity: Vec3 = Vec3.ZERO
) {
val history = ArrayDeque<Vec3>()
//Hold up to a seconds worth of values
val lookBackSize = 20
fun update(target: Vec3, stiffness: Double, damping: Double) {
//Hooke's Law f = kx where:
//f is force
//k is the spring constant or stiffness of this spring
//x is the distance from rest and in this case the difference between the most recent
//rotation delta ( the target ) and the springs current value for delta rot
val force = target.subtract(value).scale(stiffness)
//Now calculate acceleration and apply damping
val accel = force.subtract(velocity.scale(damping))
velocity = velocity.add(accel.scale(1.0/20.0))
value = value.add(velocity.scale(1.0/20.0))
history.addFirst(value)
//Adding one since if we want to check max look back point it needs something to interp with.
if( history.size > lookBackSize + 1) {
history.removeLast()
}
}
fun getInterpolated(partialTick: Double, lookBackTick: Int): Vec3 {
if (history.size < 2) {
return value.scale(20.0) // or Vec3.ZERO if you prefer
}
val tick = min(lookBackTick, history.size - 2)
val sprev = history.get(tick + 1).scale(20.0)
val svalue = history.get(tick).scale(20.0)
return sprev.add(svalue.subtract(sprev).scale(partialTick))
}
}
@@ -18,6 +18,7 @@ import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
import org.joml.Quaternionf
import org.joml.Vector3f
@@ -115,9 +116,10 @@ class ModelWidget(
heldItemRenderer.renderOnModel(
state.currentModel!!,
heldItem?: ItemStack.EMPTY,
state.locatorStates,
state,
matrices,
context.bufferSource()
context.bufferSource(),
Vec3(0.0,-90.0,-90.0)
)
matrices.popPose()
@@ -11,24 +11,24 @@ package com.cobblemon.mod.common.client.render.item
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.api.tags.CobblemonItemTags.WEARABLE_FACE_ITEMS
import com.cobblemon.mod.common.api.tags.CobblemonItemTags.WEARABLE_HAT_ITEMS
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.client.render.MatrixWrapper
import com.cobblemon.mod.common.client.render.models.blockbench.NullObjectParser
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.math.Axis
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.LightTexture
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.entity.ItemRenderer
import net.minecraft.client.renderer.texture.OverlayTexture
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.item.ItemDisplayContext
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
class HeldItemRenderer() {
private val itemRenderer = Minecraft.getInstance().itemRenderer
private var displayContext = ItemDisplayContext.FIXED
private var scale = 1.0f
class HeldItemRenderer {
private val itemRenderer: ItemRenderer = Minecraft.getInstance().itemRenderer
private var displayContext: ItemDisplayContext = ItemDisplayContext.FIXED
companion object {
const val ITEM_FACE = "item_face"
@@ -37,99 +37,130 @@ class HeldItemRenderer() {
}
fun render(
entity: PokemonEntity?,
entity: LivingEntity?,
model: PosableModel,
item: ItemStack,
locators: Map<String, MatrixWrapper>,
poseStack: PoseStack,
buffer: MultiBufferSource,
light: Int,
seed: Int
seed: Int,
rotation: Vec3
) {
if (item.isEmpty) return
displayContext = ItemDisplayContext.FIXED
poseStack.pushPose()
when {
//item_face locator
(locators.containsKey(ITEM_FACE) && item.`is`(WEARABLE_FACE_ITEMS)) -> {
displayContext = model.getLocatorDisplayContext(ITEM_FACE) ?: ItemDisplayContext.HEAD
poseStack.mulPose(locators[ITEM_FACE]!!.matrix)
poseStack.translate(0f, 0f, .28f * scale)
poseStack.scale(0.7f * scale, 0.7f * scale, 0.7f * scale)
poseStack.translate(0f, 0f, .28f)
poseStack.scale(0.7f, 0.7f, 0.7f)
}
(locators.containsKey("item_hat") && item.`is`(WEARABLE_HAT_ITEMS)) -> {
//item_hat locator
(locators.containsKey(ITEM_HAT) && item.`is`(WEARABLE_HAT_ITEMS)) -> {
displayContext = model.getLocatorDisplayContext(ITEM_HAT) ?: ItemDisplayContext.HEAD
poseStack.mulPose(locators["item_hat"]!!.matrix)
poseStack.translate(0f, -0.26f * scale, 0f)
poseStack.scale(.68f * scale, .68f * scale, .68f * scale)
poseStack.mulPose(locators[ITEM_HAT]!!.matrix)
poseStack.translate(0f, -0.26f, 0f)
poseStack.scale(.68f, .68f, .68f)
}
(locators.containsKey("item")) -> {
updateModifiers("item",locators)
poseStack.mulPose(locators["item"]!!.matrix)
//item locator
(locators.containsKey(ITEM)) -> {
displayContext = model.getLocatorDisplayContext(ITEM) ?: ItemDisplayContext.FIXED
when (displayContext) {
ItemDisplayContext.FIXED -> {
poseStack.translate(0f, 0.01666f * scale, 0f)
poseStack.scale(.5f * scale, .5f * scale, .5f * scale)
poseStack.mulPose(Axis.XP.rotationDegrees(90f))
}
ItemDisplayContext.THIRD_PERSON_RIGHT_HAND -> {
poseStack.translate(0.075f* scale, 0f * scale, -0.05f* scale)
poseStack.scale(scale, scale, scale)
poseStack.mulPose(Axis.XP.rotationDegrees(-90f))
poseStack.mulPose(Axis.YP.rotationDegrees(-90f))
}
else -> {
poseStack.scale(scale, scale, scale)
}
}
poseStack.mulPose(locators[ITEM]!!.matrix)
applyContextTransformation(poseStack, rotation)
}
else -> { // Don't render any item
// Don't render any item
else -> {
poseStack.popPose()
return
}
}
itemRenderer.renderStatic(entity, item, displayContext, false, poseStack, buffer, null, light, OverlayTexture.NO_OVERLAY, seed)
itemRenderer.renderStatic(entity, item, displayContext, (displayContext==ItemDisplayContext.THIRD_PERSON_LEFT_HAND), poseStack, buffer, null, light, OverlayTexture.NO_OVERLAY, seed)
poseStack.popPose()
}
private fun renderAnimationItem(
model: PosableModel,
state: PosableState,
locators: Map<String, MatrixWrapper>,
poseStack: PoseStack,
buffer: MultiBufferSource,
light: Int,
entity: LivingEntity?,
rotation: Vec3
) {
state.itemRenderingLocations.forEach { (locatorName, item) ->
poseStack.pushPose()
if (locators.containsKey(locatorName)) {
displayContext = model.getLocatorDisplayContext(locatorName)?: ItemDisplayContext.FIXED
poseStack.mulPose(locators[locatorName]!!.matrix)
applyContextTransformation(poseStack, rotation)
}
itemRenderer.renderStatic(entity, item, displayContext, (displayContext==ItemDisplayContext.THIRD_PERSON_LEFT_HAND), poseStack, buffer, null, light, OverlayTexture.NO_OVERLAY, 0)
poseStack.popPose()
}
}
fun renderOnModel(
model: PosableModel,
item: ItemStack,
locators: Map<String, MatrixWrapper>,
state: PosableState,
poseStack: PoseStack,
buffer: MultiBufferSource,
light: Int = LightTexture.pack(11, 7)
){
if (!item.`is`(CobblemonItemTags.HIDDEN_ITEMS)) render(null, model, item, locators, poseStack, buffer, light, 0)
rotation: Vec3 = Vec3(0.0,0.0,0.0),
light: Int = LightTexture.pack(11, 7),
) {
val locators: Map<String, MatrixWrapper> = state.locatorStates
if (state.itemRenderingLocations.isNotEmpty()) {
renderAnimationItem(model, state, locators, poseStack, buffer, light, null, rotation)
}
else if (!item.`is`(CobblemonItemTags.HIDDEN_ITEMS)) {
render(null, model, item, locators, poseStack, buffer, light, 0, rotation)
}
}
fun renderOnEntity(
entity: PokemonEntity,
delegate: PokemonClientDelegate,
entity: LivingEntity,
item: ItemStack,
model: PosableModel,
state: PosableState,
poseStack: PoseStack,
buffer: MultiBufferSource,
light: Int
light: Int,
rotation: Vec3 = Vec3(0.0,0.0,0.0)
) {
val shownItem = entity.shownItem
val locators: Map<String, MatrixWrapper> = delegate.locatorStates
render(entity, model, shownItem, locators, poseStack, buffer, light, 0)
val locators: Map<String, MatrixWrapper> = state.locatorStates
if (state.itemRenderingLocations.isNotEmpty()) {
renderAnimationItem(model, state, locators, poseStack, buffer, light, entity, rotation)
}
else {
render(entity, model, item, locators, poseStack, buffer, light, 0, rotation)
}
}
private fun updateModifiers(name: String, locators: Map<String, MatrixWrapper>) {
locators.forEach { (locator: String, m: MatrixWrapper) ->
val modifiers: Map<String, Float>
if (locator.startsWith("_null_$name[")) {
modifiers = NullObjectParser.parseNullObject(locator).modifiers
if (modifiers.containsKey("scale")) scale = modifiers["scale"]!!
if (modifiers.containsKey("mode")) displayContext = ItemDisplayContext.entries[((modifiers["mode"]!!).toInt()) % 9]
return
private fun applyContextTransformation(poseStack: PoseStack, rotation: Vec3) {
when (displayContext) {
ItemDisplayContext.FIXED -> {
poseStack.mulPose(Axis.XP.rotationDegrees(90.0f))
poseStack.scale(.5f, .5f, .5f )
poseStack.translate(0f, 0.01666f, 0f)
}
ItemDisplayContext.THIRD_PERSON_RIGHT_HAND, ItemDisplayContext.THIRD_PERSON_LEFT_HAND -> {
poseStack.mulPose(Axis.XP.rotationDegrees((rotation.x).toFloat()))
poseStack.mulPose(Axis.YP.rotationDegrees((rotation.y).toFloat()))
poseStack.mulPose(Axis.ZP.rotationDegrees((rotation.z).toFloat()))
poseStack.translate(0.025f * if (displayContext==ItemDisplayContext.THIRD_PERSON_LEFT_HAND) 1 else -1 , 0.0f, 0.0f)
}
ItemDisplayContext.HEAD -> {
poseStack.mulPose(Axis.XP.rotationDegrees(90.0f))
poseStack.scale(.6f, .6f, .6f)
poseStack.translate(0f, -0.6f, 0f)
}
else -> {}
}
}
}
@@ -37,6 +37,7 @@ import net.minecraft.nbt.Tag
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
class PokemonOnShoulderRenderer<T : Player>(renderLayerParent: RenderLayerParent<T, PlayerModel<T>>) : RenderLayer<T, PlayerModel<T>>(renderLayerParent) {
@@ -163,9 +164,10 @@ class PokemonOnShoulderRenderer<T : Player>(renderLayerParent: RenderLayerParent
heldItemRenderer.renderOnModel(
model,
shoulderData.shownItem,
state.locatorStates,
state,
matrixStack,
buffer
buffer,
Vec3(0.0,-90.0,-90.0)
)
model.setDefault()
@@ -1,50 +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
/**
* Uses the name of null objects in a model to get modifier values for a locator.
* @author Josh
*/
object NullObjectParser {
fun parseNullObject(input: String): NullObjectModifier {
// Extract modifiedLocator
val modifiedLocator = input.replaceFirst("^_null_".toRegex(), "").replace("\\[.*\\]$".toRegex(), "")
// Extract modifiers
val modifiers: MutableMap<String, Float> = HashMap()
val start = input.indexOf('[')
val end = input.indexOf(']')
if (start != -1 && end != -1 && end > start) {
val modifierPart = input.substring(start + 1, end)
val modifierPairs = modifierPart.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
for (pair in modifierPairs) {
val keyValue = pair.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if (keyValue.size == 2) {
val key = keyValue[0].trim { it <= ' ' }
try {
val value = keyValue[1].trim { it <= ' ' }.toFloat()
modifiers[key] = value
} catch (e: NumberFormatException) {
System.err.println("Invalid number format for: " + keyValue[1])
}
}
}
}
return NullObjectModifier(modifiedLocator, modifiers)
}
/**
* Stores modifiers for a particular locator.
*/
class NullObjectModifier(val locatorName: String, val modifiers: Map<String, Float>)
}
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.client.ClientMoLangFunctions.animationFunctions
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
import com.cobblemon.mod.common.client.entity.NPCClientDelegate
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.client.render.AnimatedModelTextureSupplier
import com.cobblemon.mod.common.client.render.ModelLayer
@@ -56,6 +57,7 @@ import net.minecraft.client.renderer.texture.DynamicTexture
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.item.ItemDisplayContext
import net.minecraft.world.phys.Vec3
import org.joml.Matrix4f
@@ -19,6 +19,7 @@ import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.scheduling.Schedulable
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
import com.cobblemon.mod.common.client.particle.BedrockParticleOptionsRepository
@@ -35,15 +36,20 @@ import com.cobblemon.mod.common.client.render.models.blockbench.quirk.ModelQuirk
import com.cobblemon.mod.common.client.render.models.blockbench.quirk.QuirkData
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.mojang.brigadier.StringReader
import net.minecraft.client.Minecraft
import net.minecraft.client.multiplayer.ClientLevel
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.commands.arguments.item.ItemParser
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundSource
import net.minecraft.world.entity.Entity
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
import org.joml.Matrix4f
import java.util.*
import java.util.concurrent.ConcurrentLinkedQueue
/**
@@ -124,12 +130,107 @@ abstract class PosableState : Schedulable {
/** This gets called 500 million times so use a mutable value for runtime */
private val reusableAnimTime = DoubleValue(0.0)
val itemRenderingLocations = mutableMapOf<String, ItemStack>()
// var itemRenderingLocation = ""
// var animationItem : ItemStack? = null
/** All of the MoLang functions that can be applied to something with this state. */
val functions = QueryStruct(hashMapOf())
.addFunction("anim_time") {
reusableAnimTime.value = animationSeconds.toDouble()
reusableAnimTime
}
.addFunction("r_pitch") { params ->
val lookBackTick = params.getInt(0)
val pokemon = getEntity() as? PokemonEntity ?: return@addFunction DoubleValue(0.0)
val partialTickPitch = pokemon.riding.rotSpring.getInterpolated(currentPartialTicks.toDouble(), lookBackTick).x
val maxPitch = 90.0
return@addFunction DoubleValue((partialTickPitch / maxPitch).coerceIn(-1.0,1.0))
}
.addFunction("r_yaw") { params ->
val lookBackTick = params.getInt(0)
val pokemon = getEntity() as? PokemonEntity ?: return@addFunction DoubleValue(0.0)
val partialTickYaw = pokemon.riding.rotSpring.getInterpolated(currentPartialTicks.toDouble(), lookBackTick).y
val maxYaw = 180.0
return@addFunction DoubleValue((partialTickYaw / maxYaw).coerceIn(-1.0,1.0))
}
.addFunction("r_roll") { params ->
val lookBackTick = params.getInt(0)
val pokemon = getEntity() as? PokemonEntity ?: return@addFunction DoubleValue(0.0)
val partialTickRoll = pokemon.riding.rotSpring.getInterpolated(currentPartialTicks.toDouble(), lookBackTick).z
val maxRoll = 180.0
return@addFunction DoubleValue((partialTickRoll / maxRoll).coerceIn(-1.0,1.0))
}
.addFunction("r_yaw_change") { params ->
val lookBackTick = params.getInt(0)
val pokemon = getEntity() as? PokemonEntity ?: return@addFunction DoubleValue(0.0)
val partialTickYawDelt = pokemon.riding.rotDeltaSpring.getInterpolated(currentPartialTicks.toDouble(), lookBackTick).y
val maxRotRate = pokemon.riding.getController(pokemon)?.getStat(pokemon, RidingStat.SKILL)
?: return@addFunction DoubleValue(0.0)
return@addFunction DoubleValue((partialTickYawDelt / maxRotRate).coerceIn(-1.0,1.0))
}
.addFunction("r_pitch_change") { params ->
val lookBackTick = params.getInt(0)
val pokemon = getEntity() as? PokemonEntity ?: return@addFunction DoubleValue(0.0)
val partialTickPitchDelt = pokemon.riding.rotDeltaSpring.getInterpolated(currentPartialTicks.toDouble(), lookBackTick).x
val maxRotRate = pokemon.riding.getController(pokemon)?.getStat(pokemon, RidingStat.SKILL)
?: return@addFunction DoubleValue(0.0)
return@addFunction DoubleValue((partialTickPitchDelt / maxRotRate).coerceIn(-1.0,1.0))
}
.addFunction(" r_roll_change") { params ->
val lookBackTick = params.getInt(0)
val pokemon = getEntity() as? PokemonEntity ?: return@addFunction DoubleValue(0.0)
val partialTickRollDelt = pokemon.riding.rotDeltaSpring.getInterpolated(currentPartialTicks.toDouble(), lookBackTick).z
val maxRotRate = pokemon.riding.getController(pokemon)?.getStat(pokemon, RidingStat.SKILL)
?: return@addFunction DoubleValue(0.0)
return@addFunction DoubleValue((partialTickRollDelt / maxRotRate).coerceIn(-1.0,1.0))
}
.addFunction("r_speed") { params ->
val lookBackTick = params.getInt(0)
val pokemon = getEntity() as? PokemonEntity ?: return@addFunction DoubleValue(0.0)
val partialTickSpeed = pokemon.riding.velSpring.getInterpolated(currentPartialTicks.toDouble(), lookBackTick).length()
val topSpeed = pokemon.riding.getController(pokemon)?.getStat(pokemon, RidingStat.SPEED)
?: return@addFunction DoubleValue(0.0)
return@addFunction DoubleValue((partialTickSpeed / topSpeed).coerceIn(-1.0,1.0))
}
.addFunction("r_speed_x") { params ->
val lookBackTick = params.getInt(0)
val pokemon = getEntity() as? PokemonEntity ?: return@addFunction DoubleValue(0.0)
val partialTickXVel = pokemon.riding.velSpring.getInterpolated(currentPartialTicks.toDouble(), lookBackTick).x
val topSpeed = pokemon.riding.getController(pokemon)?.getStat(pokemon, RidingStat.SPEED)
?: return@addFunction DoubleValue(0.0)
return@addFunction DoubleValue((partialTickXVel / topSpeed).coerceIn(-1.0,1.0))
}
.addFunction("r_speed_y") { params ->
val lookBackTick = params.getInt(0)
val pokemon = getEntity() as? PokemonEntity ?: return@addFunction DoubleValue(0.0)
val partialTickYVel = pokemon.riding.velSpring.getInterpolated(currentPartialTicks.toDouble(), lookBackTick).y
val topSpeed = pokemon.riding.getController(pokemon)?.getStat(pokemon, RidingStat.SPEED)
?: return@addFunction DoubleValue(0.0)
return@addFunction DoubleValue((partialTickYVel / topSpeed).coerceIn(-1.0,1.0))
}
.addFunction("r_speed_z") { params ->
val lookBackTick = params.getInt(0)
val pokemon = getEntity() as? PokemonEntity ?: return@addFunction DoubleValue(0.0)
val partialTickZVel = pokemon.riding.velSpring.getInterpolated(currentPartialTicks.toDouble(), lookBackTick).z
val topSpeed = pokemon.riding.getController(pokemon)?.getStat(pokemon, RidingStat.SPEED)
?: return@addFunction DoubleValue(0.0)
return@addFunction DoubleValue((partialTickZVel / topSpeed).coerceIn(-1.0,1.0))
}
.addFunction("current_aspects") { ArrayStruct(currentAspects.mapIndexed { index, s -> "$index" to StringValue(s)}.toMap())}
.addFunction("has_aspect") { params -> DoubleValue(params.get<MoValue>(0).asString() in currentAspects) }
.addFunction("has_entity") { DoubleValue(getEntity() != null) }
@@ -148,6 +249,24 @@ abstract class PosableState : Schedulable {
)
}
}
.addFunction("render_item") { params ->
if (params.get<MoValue>(0) !is StringValue) return@addFunction Unit
val client = Minecraft.getInstance().connection ?: return@addFunction Unit
val result = ItemParser(client.registryAccess()).parse(StringReader(params.getString(0)))
val item = ItemStack(result.item)
item.applyComponents(result.components)
val renderLocation = if (params.contains(1)) params.getString(1) else "item"
if (!item.isEmpty && locatorStates.containsKey(renderLocation)) {
itemRenderingLocations.put(renderLocation, item)
} else {
if (itemRenderingLocations.containsKey(renderLocation)) itemRenderingLocations.remove(renderLocation)
return@addFunction Unit
}
}
.addFunction("clear_items") { itemRenderingLocations.clear() }
.addFunction("play_animation") { params ->
val animationParameter = params.get<MoValue>(0)
val animation = if (animationParameter is ObjectValue<*>) {
@@ -1,76 +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.gen7
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.QuadrupedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class MudsdaleModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame, QuadrupedFrame {
override val rootPart = root.registerChildWithAllChildren("mudsdale")
override val head = getPart("neck")
override val foreLeftLeg = getPart("leg_front_left")
override val foreRightLeg = getPart("leg_front_right")
override val hindLeftLeg = getPart("leg_back_left")
override val hindRightLeg = getPart("leg_back_right")
override var portraitScale = 1.5F
override var portraitTranslation = Vec3(-1.0, 1.76, 0.0)
override var profileScale = 0.5F
override var profileTranslation = Vec3(0.0, 1.0, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
lateinit var sleep: Pose
override val cryAnimation = CryProvider { bedrockStateful("mudsdale", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("mudsdale", "blink") }
sleep = registerPose(
poseType = PoseType.SLEEP,
animations = arrayOf(bedrock("mudsdale", "sleep"))
)
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.STATIONARY_POSES + PoseType.UI_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(disableY = true),
bedrock("mudsdale", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = PoseType.MOVING_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(disableY = true),
bedrock("mudsdale", "ground_walk")
)
)
}
// override fun getFaintAnimation(
// pokemonEntity: PokemonEntity,
// state: PosableState<PokemonEntity>
// ) = if (state.isPosedIn(standing, walk)) bedrockStateful("mudsdale", "faint") else null
}
@@ -497,7 +497,6 @@ object VaryingModelRepository {
inbuilt("taillow", ::TaillowModel)
inbuilt("swellow", ::SwellowModel)
inbuilt("mudbray", ::MudbrayModel)
inbuilt("mudsdale", ::MudsdaleModel)
inbuilt("comfey", ::ComfeyModel)
inbuilt("tandemaus", ::TandemausModel)
inbuilt("maushold", ::MausholdModel)
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.client.render.npc
import com.cobblemon.mod.common.client.entity.NPCClientDelegate
import com.cobblemon.mod.common.client.render.item.HeldItemRenderer
import com.cobblemon.mod.common.client.render.models.blockbench.PosableEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.npc.PosableNPCModel
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
@@ -19,6 +20,7 @@ import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.entity.EntityRendererProvider.Context
import net.minecraft.client.renderer.entity.LivingEntityRenderer
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.phys.Vec3
import kotlin.math.min
class NPCRenderer(context: Context) : LivingEntityRenderer<NPCEntity, PosableEntityModel<NPCEntity>>(context, PosableNPCModel(), 0.5f) {
@@ -26,6 +28,8 @@ class NPCRenderer(context: Context) : LivingEntityRenderer<NPCEntity, PosableEnt
return VaryingModelRepository.getTexture(entity.npc.resourceIdentifier, (entity.delegate as NPCClientDelegate))
}
private val heldItemRenderer = HeldItemRenderer()
override fun render(
entity: NPCEntity,
entityYaw: Float,
@@ -56,6 +60,20 @@ class NPCRenderer(context: Context) : LivingEntityRenderer<NPCEntity, PosableEnt
model.blue = 1F
model.resetLayerContext()
if (entity.deathTime < 1){
//Render Held Item
heldItemRenderer.renderOnEntity(
entity,
entity.mainHandItem,
model,
clientDelegate,
poseMatrix,
buffer,
packedLight,
Vec3(-90.0,0.0,0.0)
)
}
// if (this.shouldRenderLabel(entity)) {
// this.renderLabelIfPresent(entity, entity.displayName, poseMatrix, buffer, packedLight)
// }
@@ -101,7 +101,7 @@ class PokemonRenderer(
packedLight: Int
) {
val clientDelegate = entity.delegate as PokemonClientDelegate
shadowRadius = min((entity.boundingBox.maxX - entity.boundingBox.minX), (entity.boundingBox.maxZ) - (entity.boundingBox.minZ)).toFloat() / 1.5F * (entity.delegate as PokemonClientDelegate).entityScaleModifier
shadowRadius = (min((entity.boundingBox.maxX - entity.boundingBox.minX), (entity.boundingBox.maxZ) - (entity.boundingBox.minZ)).toFloat() / 1.5F * (entity.delegate as PokemonClientDelegate).entityScaleModifier)/entity.scale
model.posableModel = VaryingModelRepository.getPoser(entity.pokemon.species.resourceIdentifier, clientDelegate)
model.posableModel.context = model.context
model.setupEntityTypeContext(entity)
@@ -160,11 +160,13 @@ class PokemonRenderer(
//Render Held Item
heldItemRenderer.renderOnEntity(
entity,
clientDelegate,
entity.shownItem,
model.posableModel,
clientDelegate,
poseMatrix,
buffer,
packedLight
packedLight,
Vec3(0.0,-90.0,90.0)
)
}
@@ -557,10 +557,10 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
private fun pokemonExpressions(pokemon: Pokemon): List<String> {
return listOf(
"v.actioning_pokemon_name=\"${pokemon.species.name}\";",
"v.actioning_pokemon_name=\'${pokemon.species.name}\';",
"v.actioning_pokemon_level=${pokemon.level};",
"v.actioning_pokemon_ball=\"${pokemon.caughtBall.name}\";",
"v.actioning_pokemon_shiny=\"${pokemon.shiny}\";",
"v.actioning_pokemon_ball=\'${pokemon.caughtBall.name}\';",
"v.actioning_pokemon_shiny=\'${pokemon.shiny}\';",
)
}
@@ -10,8 +10,6 @@ package com.cobblemon.mod.common.entity.pokemon
import com.cobblemon.mod.common.*
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.drop.DropTable
import com.cobblemon.mod.common.api.entity.Despawner
import com.cobblemon.mod.common.api.entity.PokemonSender
@@ -38,6 +36,7 @@ import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.reactive.ObservableSubscription
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.riding.*
import com.cobblemon.mod.common.api.riding.controller.posing.RidingAltPose
import com.cobblemon.mod.common.api.riding.events.SelectDriverEvent
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.scheduling.Schedulable
@@ -86,11 +85,6 @@ import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
import com.mojang.serialization.Codec
import java.util.EnumSet
import java.util.Optional
import java.util.UUID
import java.util.concurrent.CompletableFuture
import kotlin.math.PI
import net.minecraft.core.BlockPos
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
@@ -143,6 +137,9 @@ import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import org.joml.Matrix3f
import org.joml.Vector3f
import java.util.*
import java.util.concurrent.CompletableFuture
import kotlin.math.PI
@Suppress("unused")
open class PokemonEntity(
@@ -959,6 +956,7 @@ open class PokemonEntity(
val scale = effects.mockEffect?.scale ?: (form.baseScale * pokemon.scaleModifier)
var result = this.exposedForm.hitbox.scale(scale)
result = result.withEyeHeight(this.exposedForm.eyeHeight(this) * result.height)
result = result.scale(this.scale)
return result
}
@@ -1310,7 +1308,7 @@ open class PokemonEntity(
}
} else if (form.behaviour.moving.swim.canBreatheUnderwater && !form.behaviour.moving.swim.canWalkOnWater) {
// Use half hitbox height for swimmers
val halfHeight = form.hitbox.height * form.baseScale / 2.0
val halfHeight = getDimensions(this.pose).height / 2.0
for (i in 1..halfHeight.toInt()) {
blockPos = blockPos.below()
if (!this.level().isWaterAt(blockPos) || !this.level().getBlockState(blockPos).getCollisionShape(this.level(), blockPos).isEmpty) {
@@ -1409,11 +1407,22 @@ open class PokemonEntity(
if (riders.isEmpty()) {
super.handleRelativeFrictionAndCalculateMovement(deltaMovement, friction)
} else {
//val scale =
//Handle ridden pokemon differently to allow vector lerp instead of simple addition.
val v = Entity.getInputVector(riding.velocity(this, this.controllingPassenger as Player, Vec3.ZERO), 1.0f, this.getYRot());
//val v = Entity.getInputVector(riding.velocity(this, this.controllingPassenger as Player, Vec3.ZERO), 1.0f, this.getYRot());
val v = Entity.getInputVector(riding.velocity(this, this.controllingPassenger as Player, Vec3.ZERO), 1.0f, this.getYRot()).scale(this.speed.toDouble())
//changing this will give the ride more or less inertia/handling/drift
this.deltaMovement = this.deltaMovement.lerp(v, riding.inertia(this));
this.move(MoverType.SELF, this.deltaMovement.scale(this.speed.toDouble()))
//val smoothingFactor = 0.5
//val t = 1.0 - exp(-smoothingFactor)
val diff = v.subtract(this.deltaMovement)
this.deltaMovement = this.deltaMovement.add( diff.scale(0.1) )
//this.deltaMovement = this.deltaMovement.lerp(v, riding.inertia(this))
//this.deltaMovement = this.deltaMovement.scale(this.speed.toDouble()).lerp(v, riding.inertia(this));
this.move(MoverType.SELF, this.deltaMovement)
}
return this.deltaMovement
@@ -1435,14 +1444,36 @@ open class PokemonEntity(
if (beamMode != 3) { // Don't let Pokémon move during recall
//Prevent current travel logic when riding a pokemon.
/*
val riders = this.passengers.filterIsInstance<LivingEntity>()
if ( riders.isEmpty() ) {
super.travel(movementInput)
}
*/
else {
//val v = Entity.getInputVector(riding.velocity(this, this.controllingPassenger as Player, Vec3.ZERO), this.speed.toFloat(), this.getYRot())
val inp = riding.velocity(this, this.controllingPassenger as Player, Vec3.ZERO)
val f = Mth.sin(this.getYRot() * 0.017453292f)
val g = Mth.cos(this.getYRot() * 0.017453292f)
val v = Vec3(
inp.x * g.toDouble() - inp.z * f.toDouble(),
inp.y,
inp.z * g.toDouble() + inp.x * f.toDouble()
)
//val smoothingFactor = 0.5
//val t = 1.0 - exp(-smoothingFactor)
val diff = v.subtract(this.deltaMovement)
this.deltaMovement = this.deltaMovement.add( diff.scale(riding.inertia(this)) )
//this.deltaMovement = this.deltaMovement.lerp(v, riding.inertia(this))
//this.deltaMovement = this.deltaMovement.scale(this.speed.toDouble()).lerp(v, riding.inertia(this));
this.move(MoverType.SELF, this.deltaMovement)
}
super.travel(movementInput)
this.updateBlocksTraveled(prevBlockPos)
}
@@ -1812,6 +1843,11 @@ open class PokemonEntity(
return this.riding.speed(this, controller)
}
fun getActiveRidingAltPose(): RidingAltPose {
val driver = this.controllingPassenger as? Player ?: return RidingAltPose.NONE
return this.riding.getActiveRidingAltPose(this, driver)
}
var jumpInputStrength: Int = 0 // move this
override fun onPlayerJump(strength: Int) {
// See if this controls the hot bar element
@@ -9,16 +9,19 @@
package com.cobblemon.mod.common.pokemon.riding.controllers
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.controller.posing.PoseOption
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.controller.posing.RidingAltPose
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.riding.states.BirdAirState
import com.cobblemon.mod.common.util.*
import net.minecraft.client.Minecraft
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
@@ -32,81 +35,37 @@ class BirdAirController : RideController {
override val poseProvider = PoseProvider(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) {
val player = it.passengers.firstOrNull() as? Player ?: return@PoseOption false
it.deltaMovement.length() > 0.3 })
/*
.with(PoseOption(PoseType.FLY) {
val player = it.passengers.firstOrNull() as? Player ?: return@PoseOption false
player.zza > 0.0f // Checks if the player is actively attempting to move forward
})
*/
it.deltaMovement.length() > 0.5 })
override val condition: (PokemonEntity) -> Boolean = { true }
var jumpVector = listOf("0".asExpression(), "0.3".asExpression(), "0".asExpression())
private val statExpressions: Map<RidingStat, Expression> = mapOf(
RidingStat.SKILL to "q.get_ride_stats('SKILL', 'AIR', 135.0, 45.0)".asExpression(),
RidingStat.SPEED to "q.get_ride_stats('SPEED', 'AIR', 1.0, 0.35)".asExpression(),
// Max accel is a whole 1.0 in 3 seconds. The conversion in the function below is to convert seconds to ticks
RidingStat.ACCELERATION to "q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 3.0)), (1.0 / (20.0 * 8.0)))".asExpression(),
//Seconds self propelled flight (glide is not self propelled in this case)
RidingStat.STAMINA to "q.get_ride_stats('STAMINA', 'AIR', 120.0, 20.0)".asExpression(),
//max y level for the ride
RidingStat.JUMP to "q.get_ride_stats('JUMP', 'AIR', 200.0, 128.0)".asExpression()
)
var infiniteStamina: Expression = "false".asExpression()
private set
var gravity: Expression = "0".asExpression()
private set
var horizontalAcceleration: Expression = "0.1".asExpression()
private set
var verticalAcceleration: Expression = "0.1".asExpression()
var infiniteAltitude: Expression = "false".asExpression()
private set
// Where 90 is the width of the range and 45 is the offset.
// So the handling interpolates between 135 and 45
var handlingExpr: Expression = "(q.get_ride_stats('SKILL', 'AIR') / 100) * 90 + 45".asExpression()
private set
var topSpeedExpr: Expression = "(q.get_ride_stats('SPEED', 'AIR') / 100) * (1.0 - 0.35) + 0.35".asExpression()
private set
//Reaches max top speed from a standstill at minimum 8 seconds and max 3 seconds.
//This is a bit of a confusing expression and likely should be cleaned up to be more readable
var accelExpr: Expression = "(q.get_ride_stats('ACCELERATION', 'AIR') / 100) * (0.01666666666 - 0.00625) + 0.00625".asExpression()
private set
override fun getStat(entity: PokemonEntity, stat: RidingStat): Double {
val expr = statExpressions[stat] ?: return 0.0
return getRuntime(entity).resolveDouble(expr)
}
override fun speed(entity: PokemonEntity, driver: Player): Float {
//Retrieve the current bird controller state
//val state = entity.riding.getState(KEY) { e -> BirdAirControllerState(e) }
//Retrieve the current bird controller state and stats
val state = getState(entity, ::BirdAirState)
//retrieve stats
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
val accel = getRuntime(entity).resolveDouble(accelExpr)
val maxGlideSpeed = topSpeed * 2
var glideSpeedChange = 0.00
//speed up and slow down based on input
if (driver.zza > 0.0 && state.currSpeed < topSpeed) {
state.currSpeed = min(state.currSpeed + accel , topSpeed)
} else if (driver.zza < 0.0 && state.currSpeed > 0.0) {
//Decelerate is currently always a constant half of max acceleration.
state.currSpeed = max(state.currSpeed - (0.0166 / 2), 0.0)
}
val rollable = driver as? Rollable
if (rollable != null) {
glideSpeedChange = sin(Math.toRadians(rollable.pitch.toDouble()))
glideSpeedChange = glideSpeedChange.pow(3)
//TODO: Possibly create a deadzone around parallel where speed isn't taken away from?
if (glideSpeedChange <= 0.0) {
//Ensures that a propelling force is still able to be applied when
//climbing in height
if (driver.zza <= 0) {
//speed decrease should be 2x speed increase?
state.currSpeed = max(state.currSpeed + (0.0166) * glideSpeedChange, 0.0 )
}
} else {
// only add to the speed if it hasn't exceeded the current
//glide angles maximum amount of speed that it can give.
state.currSpeed = min(state.currSpeed + ((0.0166 * 2) * glideSpeedChange), maxGlideSpeed)
}
}
//air resistance
state.currSpeed = max(state.currSpeed - 0.005, 0.0)
return state.currSpeed.toFloat()
return state.rideVel.length().toFloat()
}
override fun rotation(entity: PokemonEntity, driver: LivingEntity): Vec2 {
@@ -118,62 +77,60 @@ class BirdAirController : RideController {
var leftForce = 0.0
var upForce = 0.0
var forwardForce = 0.0
val state = getState(entity, ::BirdAirState)
//Perform ride velocity update
calculateRideSpaceVel(entity, driver, state)
//Translate ride space velocity to world space velocity.
val rollable = driver as? Rollable
if (rollable != null) {
//Need to deadzone this when straight up or down
// test with and without strafing added. May be wanted while moving
leftForce = -1.0 * sin(Math.toRadians(rollable.roll.toDouble()))
upForce = -1.0 * sin(Math.toRadians(rollable.pitch.toDouble()))
upForce += -1.0 * sin(Math.toRadians(rollable.pitch.toDouble())) * state.rideVel.z
forwardForce += cos(Math.toRadians(rollable.pitch.toDouble())) * state.rideVel.z
forwardForce = cos(Math.toRadians(rollable.pitch.toDouble()))
upForce += cos(Math.toRadians(rollable.pitch.toDouble())) * state.rideVel.y
forwardForce += sin(Math.toRadians(rollable.pitch.toDouble())) * state.rideVel.y
}
//TODO: Add in an upwards and downwards force when pressing space maybe?
//Leave out tap to flap right now
/*
var jumpVector = Vec3.ZERO
if (entity.jumpInputStrength > 0) {
val runtime = getRuntime(entity)
runtime.environment.query.addFunction("jump_strength") { DoubleValue(entity.jumpInputStrength.toDouble()) }
val jumpForces = this.jumpVector.map { runtime.resolveFloat(it) }
jumpVector = Vec3(jumpForces[0].toDouble(), jumpForces[1].toDouble(), jumpForces[2].toDouble())
jumpVector = jumpVector.toVector3f().mul((driver as Rollable).orientation).toVec3d()
entity.jumpInputStrength = 0
entity.addDeltaMovement(jumpVector)
//Bring the ride out of the sky when stamina is depleted.
if (state.stamina <= 0.0) {
upForce -= 0.3
}
*/
//Retrieve the current composite controller state
val state = getState(entity, ::BirdAirState)
val velocity = Vec3(0.0 , upForce * state.currSpeed, forwardForce * state.currSpeed)
val altitudeLimit = this.getStat(entity, RidingStat.JUMP)
//Only limit altitude if altitude is not infinite
if (!getRuntime(entity).resolveBoolean(infiniteAltitude)) {
//Provide a hard limit on altitude
upForce = if (entity.y >= altitudeLimit && upForce > 0) 0.0 else upForce
}
val velocity = Vec3(state.rideVel.x , upForce, forwardForce)
return velocity
}
override fun angRollVel(entity: PokemonEntity, driver: Player, deltaTime: Double): Vec3 {
val rollable = driver as? Rollable
val state = getState(entity, ::BirdAirState)
//TODO: Tie in handling
val handling = getRuntime(entity).resolveDouble(handlingExpr)
val handling = this.getStat(entity, RidingStat.SKILL)
val topSpeed = this.getStat(entity, RidingStat.SPEED)
val rotationChangeRate = 10.0
if (rollable != null) {
var yawForce = rotationChangeRate * sin(Math.toRadians(rollable.roll.toDouble()))
//for a bit of correction on the rolls limit it to a quarter the amount
var pitchForce = -0.25 * rotationChangeRate * abs(sin(Math.toRadians(rollable.roll.toDouble())))
var pitchForce = -0.35 * rotationChangeRate * abs(sin(Math.toRadians(rollable.roll.toDouble())))
//limit rotation modulation when pitched up heavily or pitched down heavily
yawForce *= abs(cos(Math.toRadians(rollable.pitch.toDouble())))
pitchForce *= abs(cos(Math.toRadians(rollable.pitch.toDouble()))) * 1.5
//Create a deadzone for easier leveling out
if (rollable.roll <= 0.0 && rollable.roll >= -0.0) {
yawForce = 0.0
pitchForce = 0.0
}
return Vec3(yawForce, pitchForce, 0.0)
}
@@ -191,42 +148,56 @@ class BirdAirController : RideController {
deltaTime: Double
): Vec3 {
if (driver !is Rollable) return Vec3.ZERO
val rollable = driver as Rollable
val handling = getRuntime(entity).resolveDouble(handlingExpr)
val state = getState(entity, ::BirdAirState)
val handling = this.getStat(entity, RidingStat.SKILL)
val topSpeed = this.getStat(entity, RidingStat.SPEED)
//Smooth out mouse input.
val smoothingSpeed = 4
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
var xInput = xMouseSmoother.getNewDeltaValue(xMouse * 0.1 * invertRoll, deltaTime * smoothingSpeed);
val YInput = yMouseSmoother.getNewDeltaValue(yMouse * 0.1 * invertPitch, deltaTime * smoothingSpeed);
val xInput = xMouseSmoother.getNewDeltaValue(xMouse * 0.1 * invertRoll, deltaTime * smoothingSpeed);
val yInput = yMouseSmoother.getNewDeltaValue(yMouse * 0.1 * invertPitch, deltaTime * smoothingSpeed);
//limit rolling based on healing
val rotLimit = handling
//limit rolling based on handling and current speed.
//modulated by speed so that when flapping idle in air you are ont wobbling around to look around
val rotMin = 15.0
var rollForce = xInput
val rotLimit = max(handling * normalizeVal(state.rideVel.length(), 0.0, topSpeed).pow(3), rotMin)
//Limit roll by non linearly decreasing inputs towards
// a rotation limit based on the current distance from
// that rotation limit
if (abs(rollable.roll + xInput) < rotLimit) {
if (sign(xInput) == sign(rollable.roll).toDouble()) {
if (abs(rollable.roll + rollForce) < rotLimit) {
if (sign(rollForce) == sign(rollable.roll).toDouble()) {
val d = abs(abs(rollable.roll) - rotLimit)
xInput *= (d.pow(2)) / (rotLimit.pow(2))
rollForce *= (d.pow(2)) / (rotLimit.pow(2))
}
} else if (sign(xInput) == sign(rollable.roll).toDouble()) {
xInput = 0.0
} else if (sign(rollForce) == sign(rollable.roll).toDouble()) {
rollForce = 0.0
}
//Give the ability to yaw with x mouse input when at low speeds.
val yawForce = xInput * ( 1.0 - normalizeVal(state.rideVel.length(), 0.0, topSpeed).pow(3))
//yaw, pitch, roll
return Vec3(0.0, YInput, xInput)
return Vec3(yawForce, yInput, rollForce)
}
override fun canJump(
entity: PokemonEntity,
driver: Player
): Boolean {
return true
return false
}
override fun setRideBar(entity: PokemonEntity, driver: Player): Float {
//Retrieve stamina from state
val state = getState(entity, ::BirdAirState)
return (state.stamina / 1.0f)
}
override fun jumpForce(
@@ -243,29 +214,182 @@ class BirdAirController : RideController {
override fun shouldRoll(entity: PokemonEntity) = true
override fun rideFovMult(entity: PokemonEntity, driver: Player): Float {
val topSpeed = this.getStat(entity, RidingStat.SPEED)
val glideTopSpeed = topSpeed * 1.5 //TODO: Should this be refined?
val state = getState(entity, ::BirdAirState)
//Must I ensure that topspeed is greater than minimum?
val normalizedGlideSpeed = normalizeVal(state.rideVel.length(), topSpeed, glideTopSpeed)
//Only ever want the fov change to be a max of 0.2 and for it to have non linear scaling.
return 1.0f + normalizedGlideSpeed.pow(2).toFloat() * 0.2f
}
override fun useAngVelSmoothing(entity: PokemonEntity): Boolean = false
override fun getActiveRidingAltPose(entity: PokemonEntity, driver: Player): RidingAltPose {
val state = getState(entity, ::BirdAirState)
val pose = if (state.gliding) RidingAltPose.GLIDING else RidingAltPose.NONE
return pose
}
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeString(gravity.toString())
buffer.writeString(horizontalAcceleration.toString())
buffer.writeString(verticalAcceleration.toString())
buffer.writeString(topSpeedExpr.toString())
buffer.writeString(accelExpr.toString())
buffer.writeString(handlingExpr.toString())
buffer.writeString(infiniteStamina.toString())
buffer.writeString(infiniteAltitude.toString())
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
gravity = buffer.readString().asExpression()
horizontalAcceleration = buffer.readString().asExpression()
verticalAcceleration = buffer.readString().asExpression()
topSpeedExpr = buffer.readString().asExpression()
accelExpr = buffer.readString().asExpression()
handlingExpr = buffer.readString().asExpression()
infiniteStamina = buffer.readString().asExpression()
infiniteAltitude = buffer.readString().asExpression()
}
companion object {
val KEY = cobblemonResource("air/bird")
}
/*
* Normalizes the given value between a min and a max.
* The result is clamped between 0.0 and 1.0, where 0.0 represents x is at or below min
* and 1.0 represents x is at or above it.
*/
fun normalizeVal(x: Double, min: Double, max: Double): Double {
require(max > min) { "max must be greater than min" }
return ((x - min) / (max - min)).coerceIn(0.0, 1.0)
}
/*
* Calculates the change in the ride space vector due to player input and ride state
*/
fun calculateRideSpaceVel(entity: PokemonEntity, driver: Player, state: BirdAirState) {
//retrieve stats
val topSpeed = this.getStat(entity, RidingStat.SPEED)
val glideTopSpeed = topSpeed * 1.5
val accel = this.getStat(entity, RidingStat.ACCELERATION)
val staminaStat = this.getStat(entity, RidingStat.STAMINA)
var glideSpeedChange = 0.00
val currSpeed = state.rideVel.length()
//Flag for determining if player is actively inputting
var activeInput = false
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina > 0.0) {
//make sure it can't exceed top speed
val forwardInput = when {
driver.zza > 0 && state.rideVel.z > topSpeed -> 0.0
driver.zza < 0 && state.rideVel.z < (-topSpeed / 3.0) -> 0.0
else -> driver.zza.sign
}
state.rideVel = Vec3(
state.rideVel.x,
state.rideVel.y,
(state.rideVel.z + (accel * forwardInput.toDouble())))
activeInput = true
}
val rollable = driver as? Rollable
if (rollable != null) {
//Base glide speed change on current pitch of the ride.
glideSpeedChange = sin(Math.toRadians(rollable.pitch.toDouble()))
glideSpeedChange = glideSpeedChange.pow(3) * 0.5
//TODO: Possibly create a deadzone around parallel where glide doesn't affect speed?
if (glideSpeedChange <= 0.0) {
//Ensures that a propelling force is still able to be applied when
//climbing in height
if (driver.zza <= 0) {
//speed decrease should be 2x speed increase?
//state.currSpeed = max(state.currSpeed + (0.0166) * glideSpeedChange, 0.0 )
state.rideVel = Vec3(
state.rideVel.x,
state.rideVel.y,
lerp( state.rideVel.z, 0.0,glideSpeedChange * -0.0166 * 2 ))
}
} else {
// only add to the speed if it hasn't exceeded the current
//glide angles maximum amount of speed that it can give.
//state.currSpeed = min(state.currSpeed + ((0.0166 * 2) * glideSpeedChange), maxGlideSpeed)
state.rideVel = Vec3(
state.rideVel.x,
state.rideVel.y,
min(state.rideVel.z + ((0.0166 * 2) * glideSpeedChange), glideTopSpeed))
}
}
//Lateral movement based on driver input.
val latTopSpeed = topSpeed / 2.0
if (driver.xxa != 0.0f && state.stamina > 0.0) {
state.rideVel = Vec3(
(state.rideVel.x + (accel * driver.xxa)).coerceIn(-latTopSpeed, latTopSpeed),
state.rideVel.y,
state.rideVel.z)
activeInput = true
}
else {
state.rideVel = Vec3(
lerp(state.rideVel.x, 0.0, latTopSpeed / 20.0),
state.rideVel.y,
state.rideVel.z)
}
//Vertical movement based on driver input.
val vertTopSpeed = topSpeed / 2.0
val vertInput = when {
Minecraft.getInstance().options.keyJump.isDown() -> 1.0
Minecraft.getInstance().options.keyShift.isDown() -> -1.0
else -> 0.0
}
if (vertInput != 0.0 && state.stamina > 0.0) {
state.rideVel = Vec3(
state.rideVel.x,
(state.rideVel.y + (accel * vertInput)).coerceIn(-vertTopSpeed, vertTopSpeed),
state.rideVel.z)
activeInput = true
}
else {
state.rideVel = Vec3(
state.rideVel.x,
lerp(state.rideVel.y, 0.0, vertTopSpeed / 20.0),
state.rideVel.z)
}
//Check if the ride should be gliding
if (activeInput && state.stamina > 0.0) {
state.gliding = false
}else {
state.gliding = true
}
//Only perform stamina logic if the ride does not have infinite stamina
if (!getRuntime(entity).resolveBoolean(infiniteStamina)) {
if (activeInput) {
state.stamina -= (0.05 / staminaStat).toFloat()
}
//Lose a base amount of stamina just for being airborne
state.stamina -= (0.01 / staminaStat).toFloat()
}
else
{
state.stamina = 1.0f
}
//air resistance
//TODO: This method is very unrefined and will need to be an active point of testing
state.rideVel = Vec3(
state.rideVel.x,
state.rideVel.y,
lerp( state.rideVel.z,0.0, topSpeed / ( 20.0 * 30.0)))
}
}
@@ -8,53 +8,37 @@
package com.cobblemon.mod.common.pokemon.riding.controllers
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.controller.posing.PoseOption
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.riding.states.DolphinLiquidState
import com.cobblemon.mod.common.util.*
import net.minecraft.client.Minecraft
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth.lerp
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
import kotlin.math.*
class DolphinController : RideController {
companion object {
val KEY: ResourceLocation = cobblemonResource("swim/dolphin")
}
var canJump = "true".asExpression()
private set
var jumpVector = listOf("0".asExpression(), "0.3".asExpression(), "0".asExpression())
private set
var speed = "1.0".asExpression()
private set
var driveFactor = "1.0".asExpression()
private set
var reverseDriveFactor = "0.25".asExpression()
private set
var strafeFactor = "0.2".asExpression()
private set
var lastVelocity = Vec3(0.0,0.0,0.0)
private set
private val rollSmoother = SmoothDouble()
override val key: ResourceLocation = KEY
override val poseProvider: PoseProvider = PoseProvider(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { it.deltaMovement.length() >= 0.05 })
/*
override val condition: (PokemonEntity) -> Boolean = { entity ->
//If there are only fluid blocks or air block below the ride
@@ -68,135 +52,332 @@ class DolphinController : RideController {
true
}
}
*/
override val condition: (PokemonEntity) -> Boolean = { true }
//TODO:Implement real values for this
private val statExpressions: Map<RidingStat, Expression> = mapOf(
RidingStat.SKILL to "q.get_ride_stats('SKILL', 'LIQUID', 135.0, 45.0)".asExpression(),
RidingStat.SPEED to "q.get_ride_stats('SPEED', 'LIQUID', 0.6, 0.2)".asExpression(),
RidingStat.ACCELERATION to "q.get_ride_stats('ACCELERATION', 'LIQUID', (1.0 / (20.0 * 3.0)), (1.0 / (20.0 * 8.0)))".asExpression(),
RidingStat.STAMINA to "q.get_ride_stats('STAMINA', 'LIQUID', 120.0, 20.0)".asExpression(),
//max y level for the ride
RidingStat.JUMP to "q.get_ride_stats('JUMP', 'LIQUID', 200.0, 128.0)".asExpression()
)
var glideTopSpeedExpr: Expression = "q.get_ride_stats('SPEED', 'LIQUID', 2.0, 1.0)".asExpression()
private set
var infiniteStamina: Expression = "false".asExpression()
private set
var infiniteAltitude: Expression = "false".asExpression()
private set
override fun getStat(entity: PokemonEntity, stat: RidingStat): Double {
val expr = statExpressions[stat] ?: return 0.0
return getRuntime(entity).resolveDouble(expr)
}
override fun speed(entity: PokemonEntity, driver: Player): Float {
return getRuntime(entity).resolveFloat(speed)
//Retrieve the current bird controller state and stats
val state = getState(entity, ::DolphinLiquidState)
return state.rideVel.length().toFloat()
}
override fun rotation(entity: PokemonEntity, driver: LivingEntity): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot )
}
override fun gravity(entity: PokemonEntity, regularGravity: Double): Double? {
return 0.0
}
override fun inertia(entity: PokemonEntity ): Double
{
return 0.05
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(entity: PokemonEntity, driver: Player, input: Vec3): Vec3 {
val runtime = getRuntime(entity)
var yVel = 0.0
var leftForce = 0.0
var upForce = 0.0
var forwardForce = 0.0
val state = getState(entity, ::DolphinLiquidState)
if (!entity.isInWater && !entity.isUnderWater)
{
lastVelocity = Vec3(lastVelocity.x, lastVelocity.y - 0.035, lastVelocity.z)
return lastVelocity
}
//Perform ride velocity update
calculateRideSpaceVel(entity, driver, state)
val driveFactor = runtime.resolveFloat(this.driveFactor)
val strafeFactor = runtime.resolveFloat(this.strafeFactor)
val f = driver.xxa * strafeFactor
var g = driver.zza * driveFactor
if (g <= 0.0f) {
g *= runtime.resolveFloat(this.reverseDriveFactor)
}
var yComp = 0.0
//Get roll to add a left and right strafe during roll
//Not sure if this is desired? Will need to mess around with this
//a bit more
var zComp = 0.0
//Translate ride space velocity to world space velocity.
val rollable = driver as? Rollable
if (rollable != null)
{
//Can be used again if I better figure out how to deadzone it near the tops?
zComp = -1.0 * g.toDouble() * sin(Math.toRadians(rollable.roll.toDouble()))
yComp = -1.0 * g.toDouble() * sin(Math.toRadians(rollable.pitch.toDouble()))
if (rollable != null) {
//Need to deadzone this when straight up or down
upForce += -1.0 * sin(Math.toRadians(rollable.pitch.toDouble())) * state.rideVel.z
forwardForce += cos(Math.toRadians(rollable.pitch.toDouble())) * state.rideVel.z
upForce += cos(Math.toRadians(rollable.pitch.toDouble())) * state.rideVel.y
forwardForce += sin(Math.toRadians(rollable.pitch.toDouble())) * state.rideVel.y
}
val currVelocity = Vec3(0.0, yComp , g.toDouble())
lastVelocity = currVelocity
return currVelocity
//Bring the ride out of the sky when stamina is depleted.
if (state.stamina <= 0.0) {
upForce -= 0.3
}
val altitudeLimit = this.getStat(entity, RidingStat.JUMP)
//Only limit altitude if altitude is not infinite
if (!getRuntime(entity).resolveBoolean(infiniteAltitude)) {
//Provide a hard limit on altitude
upForce = if (entity.y >= altitudeLimit && upForce > 0) 0.0 else upForce
}
val velocity = Vec3(state.rideVel.x , upForce, forwardForce)
return velocity
}
override fun angRollVel(entity: PokemonEntity, driver: Player, deltaTime: Double): Vec3 {
if (!entity.isInWater && !entity.isUnderWater)
{
return Vec3(0.0, 0.0, 0.0)
}
val rollable = driver as? Rollable
val state = getState(entity, ::DolphinLiquidState)
//TODO: Tie in handling
val handling = this.getStat(entity, RidingStat.SKILL)
val topSpeed = this.getStat(entity, RidingStat.SPEED)
val rotationChangeRate = 10.0
//this should be changed to be speed maybe?
val movingForce = driver.zza
if (rollable != null) {
var yawAngVel = 3 * sin(Math.toRadians(rollable.roll.toDouble())).toFloat()
var pitchAngVel = -1 * Math.abs(sin(Math.toRadians(rollable.roll.toDouble())).toFloat())
var yawForce = rotationChangeRate * sin(Math.toRadians(rollable.roll.toDouble()))
//for a bit of correction on the rolls limit it to a quarter the amount
var pitchForce = -0.35 * rotationChangeRate * abs(sin(Math.toRadians(rollable.roll.toDouble())))
//limit rotation modulation when pitched up heavily or pitched down heavily
yawAngVel *= (abs(cos(Math.toRadians(rollable.pitch.toDouble())))).toFloat()
pitchAngVel *= (abs(cos(Math.toRadians(rollable.pitch.toDouble())))).toFloat()
//if you are not pressing forward then don't turn
//yawAngVel *= movingForce
//Ignore x,y,z its angular velocity:
//yaw, pitch, roll
return Vec3(yawAngVel.toDouble(), pitchAngVel.toDouble(), 0.0)
yawForce *= abs(cos(Math.toRadians(rollable.pitch.toDouble())))
pitchForce *= abs(cos(Math.toRadians(rollable.pitch.toDouble()))) * 1.5
return Vec3(yawForce, pitchForce, 0.0)
}
return Vec3(0.0, 0.0, 0.0)
}
override fun canJump(entity: PokemonEntity, driver: Player) = getRuntime(entity).resolveBoolean(canJump)
override fun rotationOnMouseXY(
entity: PokemonEntity,
driver: Player,
yMouse: Double,
xMouse: Double,
yMouseSmoother: SmoothDouble,
xMouseSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is Rollable) return Vec3.ZERO
val rollable = driver as Rollable
override fun jumpForce(entity: PokemonEntity, driver: Player, jumpStrength: Int): Vec3 {
val runtime = getRuntime(entity)
runtime.environment.query.addFunction("jump_strength") { DoubleValue(jumpStrength.toDouble()) }
val jumpVector = jumpVector.map { runtime.resolveFloat(it) }
return Vec3(jumpVector[0].toDouble(), jumpVector[1].toDouble(), jumpVector[2].toDouble())
val state = getState(entity, ::DolphinLiquidState)
val handling = this.getStat(entity, RidingStat.SKILL)
val topSpeed = this.getStat(entity, RidingStat.SPEED)
//Smooth out mouse input.
val smoothingSpeed = 4
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
val xInput = xMouseSmoother.getNewDeltaValue(xMouse * 0.1 * invertRoll, deltaTime * smoothingSpeed);
val yInput = yMouseSmoother.getNewDeltaValue(yMouse * 0.1 * invertPitch, deltaTime * smoothingSpeed);
//limit rolling based on handling and current speed.
//modulated by speed so that when flapping idle in air you are ont wobbling around to look around
val rotMin = 15.0
var rollForce = xInput
val rotLimit = max(handling * normalizeVal(state.rideVel.length(), 0.0, topSpeed).pow(3), rotMin)
//Limit roll by non linearly decreasing inputs towards
// a rotation limit based on the current distance from
// that rotation limit
if (abs(rollable.roll + rollForce) < rotLimit) {
if (sign(rollForce) == sign(rollable.roll).toDouble()) {
val d = abs(abs(rollable.roll) - rotLimit)
rollForce *= (d.pow(2)) / (rotLimit.pow(2))
}
} else if (sign(rollForce) == sign(rollable.roll).toDouble()) {
rollForce = 0.0
}
//Give the ability to yaw with x mouse input when at low speeds.
val yawForce = xInput * ( 1.0 - normalizeVal(state.rideVel.length(), 0.0, topSpeed).pow(3))
//yaw, pitch, roll
return Vec3(yawForce, yInput, rollForce)
}
override fun canJump(
entity: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(entity: PokemonEntity, driver: Player): Float {
//Retrieve stamina from state
val state = getState(entity, ::DolphinLiquidState)
return (state.stamina / 1.0f)
}
override fun jumpForce(
entity: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(entity: PokemonEntity, regularGravity: Double): Double {
return 0.0
}
override fun turnOffOnGround(entity: PokemonEntity): Boolean {
return false
}
override fun shouldRoll(entity: PokemonEntity) = true
override fun turnOffOnGround(entity: PokemonEntity): Boolean = true
override fun rideFovMult(entity: PokemonEntity, driver: Player): Float {
val topSpeed = this.getStat(entity, RidingStat.SPEED)
val glideTopSpeed = getRuntime(entity).resolveDouble(glideTopSpeedExpr)
val state = getState(entity, ::DolphinLiquidState)
override fun dismountOnShift(entity: PokemonEntity): Boolean = false
//Must I ensure that topspeed is greater than minimum?
val normalizedGlideSpeed = normalizeVal(state.rideVel.length(), topSpeed, glideTopSpeed)
//Only ever want the fov change to be a max of 0.2 and for it to have non linear scaling.
return 1.0f + normalizedGlideSpeed.pow(2).toFloat() * 0.2f
}
override fun useAngVelSmoothing(entity: PokemonEntity): Boolean = false
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeString(this.speed.getString())
buffer.writeString(this.canJump.getString())
buffer.writeString(this.jumpVector[0].getString())
buffer.writeString(this.jumpVector[1].getString())
buffer.writeString(this.jumpVector[2].getString())
buffer.writeString(this.driveFactor.getString())
buffer.writeString(this.reverseDriveFactor.getString())
buffer.writeString(this.strafeFactor.getString())
buffer.writeDouble(this.lastVelocity.x)
buffer.writeDouble(this.lastVelocity.y)
buffer.writeDouble(this.lastVelocity.z)
buffer.writeString(glideTopSpeedExpr.toString())
buffer.writeString(infiniteStamina.toString())
buffer.writeString(infiniteAltitude.toString())
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
this.speed = buffer.readString().asExpression()
this.canJump = buffer.readString().asExpression()
this.jumpVector = listOf(
buffer.readString().asExpression(),
buffer.readString().asExpression(),
buffer.readString().asExpression()
)
this.driveFactor = buffer.readString().asExpression()
this.reverseDriveFactor = buffer.readString().asExpression()
this.strafeFactor = buffer.readString().asExpression()
this.strafeFactor = buffer.readString().asExpression()
val x = buffer.readDouble()
val y = buffer.readDouble()
val z = buffer.readDouble()
this.lastVelocity = Vec3(x, y, z)
glideTopSpeedExpr = buffer.readString().asExpression()
infiniteStamina = buffer.readString().asExpression()
infiniteAltitude = buffer.readString().asExpression()
}
/*
* Normalizes the given value between a min and a max.
* The result is clamped between 0.0 and 1.0, where 0.0 represents x is at or below min
* and 1.0 represents x is at or above it.
*/
fun normalizeVal(x: Double, min: Double, max: Double): Double {
require(max > min) { "max must be greater than min" }
return ((x - min) / (max - min)).coerceIn(0.0, 1.0)
}
/*s
* Calculates the change in the ride space vector due to player input and ride state
*/
fun calculateRideSpaceVel(entity: PokemonEntity, driver: Player, state: DolphinLiquidState) {
//retrieve stats
val topSpeed = this.getStat(entity, RidingStat.SPEED)
val glideTopSpeed = getRuntime(entity).resolveDouble(glideTopSpeedExpr)
val accel = this.getStat(entity, RidingStat.ACCELERATION)
val staminaStat = this.getStat(entity, RidingStat.STAMINA)
var glideSpeedChange = 0.00
val currSpeed = state.rideVel.length()
//Flag for determining if player is actively inputting
var activeInput = false
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina > 0.0) {
//make sure it can't exceed top speed
val forwardInput = when {
driver.zza > 0 && state.rideVel.z > topSpeed -> 0.0
driver.zza < 0 && state.rideVel.z < (-topSpeed / 3.0) -> 0.0
else -> driver.zza.sign
}
state.rideVel = Vec3(
state.rideVel.x,
state.rideVel.y,
(state.rideVel.z + (accel * forwardInput.toDouble())))
activeInput = true
}
//Lateral movement based on driver input.
val latTopSpeed = topSpeed / 2.0
if (driver.xxa != 0.0f && state.stamina > 0.0) {
state.rideVel = Vec3(
(state.rideVel.x + (accel * driver.xxa)).coerceIn(-latTopSpeed, latTopSpeed),
state.rideVel.y,
state.rideVel.z)
activeInput = true
}
else {
state.rideVel = Vec3(
lerp(state.rideVel.x, 0.0, latTopSpeed / 20.0),
state.rideVel.y,
state.rideVel.z)
}
//Vertical movement based on driver input.
val vertTopSpeed = topSpeed / 2.0
val vertInput = when {
Minecraft.getInstance().options.keyJump.isDown() -> 1.0
Minecraft.getInstance().options.keyShift.isDown() -> -1.0
else -> 0.0
}
if (vertInput != 0.0 && state.stamina > 0.0) {
state.rideVel = Vec3(
state.rideVel.x,
(state.rideVel.y + (accel * vertInput)).coerceIn(-vertTopSpeed, vertTopSpeed),
state.rideVel.z)
activeInput = true
}
else {
state.rideVel = Vec3(
state.rideVel.x,
lerp(state.rideVel.y, 0.0, vertTopSpeed / 20.0),
state.rideVel.z)
}
//Check if the ride should be gliding
if (activeInput && state.stamina > 0.0) {
state.gliding = false
}else {
state.gliding = true
}
//Only perform stamina logic if the ride does not have infinite stamina
if (!getRuntime(entity).resolveBoolean(infiniteStamina)) {
if (activeInput) {
state.stamina -= (0.05 / staminaStat).toFloat()
}
//Lose a base amount of stamina just for being airborne
state.stamina -= (0.01 / staminaStat).toFloat()
}
else
{
state.stamina = 1.0f
}
//air resistance
//TODO: This method is very unrefined and will need to be an active point of testing
/*
state.rideVel = Vec3(
state.rideVel.x,
state.rideVel.y,
lerp( state.rideVel.z,0.0, topSpeed / ( 20.0 * 30.0)))
*/
}
}
@@ -12,17 +12,13 @@ import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.controller.posing.PoseOption
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.riding.states.CompositeState
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.adapters.RideControllerAdapter
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.getString
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.writeString
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
@@ -77,6 +73,10 @@ class FallToFlightCompositeController : RideController {
}
}
override fun getStat(entity: PokemonEntity, stat: RidingStat): Double {
return getActiveController(entity).getStat(entity, stat)
}
override fun speed(
entity: PokemonEntity,
driver: Player
@@ -9,19 +9,21 @@
package com.cobblemon.mod.common.pokemon.riding.controllers
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.controller.posing.PoseOption
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.controller.posing.RidingAltPose
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.riding.states.GenericLandState
import com.cobblemon.mod.common.pokemon.riding.states.JetAirState
import com.cobblemon.mod.common.util.*
import net.minecraft.client.Minecraft
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
@@ -49,20 +51,20 @@ class GenericLandController : RideController {
private set
var strafeFactor = "0.2".asExpression()
private set
var sprintMod = "1.5".asExpression()
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 140.0, 20.0)".asExpression()
private set
var topSpeedExpr: Expression = "q.get_ride_stats('SPEED', 'LAND', 1.0, 0.3)".asExpression()
private set
// Max accel is a whole 1.0 in 1 second. The conversion in the function below is to convert seconds to ticks
var accelExpr: Expression = "q.get_ride_stats('ACCELERATION', 'LAND', (1.0 / (20.0 * 0.5)), (1.0 / (20.0 * 5.0)))".asExpression()
private set
// Between 30 seconds and 10 seconds at the lowest when at full speed.
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'LAND', 30.0, 10.0)".asExpression()
private set
//Between a one block jump and a ten block jump
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'LAND', 10.0, 1.0)".asExpression()
private set
private val statExpressions: Map<RidingStat, Expression> = mapOf(
//Degrees per second
RidingStat.SKILL to "q.get_ride_stats('SKILL', 'LAND', 135.0, 90.0)".asExpression(),
RidingStat.SPEED to "q.get_ride_stats('SPEED', 'LAND', 0.65, 0.3)".asExpression(),
// In seconds to max speed
RidingStat.ACCELERATION to "q.get_ride_stats('ACCELERATION', 'LAND', (1.0 / (20.0 * 0.5)), (1.0 / (20.0 * 5.0)))".asExpression(),
// In Seconds
RidingStat.STAMINA to "q.get_ride_stats('STAMINA', 'LAND', 30.0, 10.0)".asExpression(),
// Block Jump Height
RidingStat.JUMP to "q.get_ride_stats('JUMP', 'LAND', 10.0, 1.0)".asExpression()
)
@Transient
override val key: ResourceLocation = KEY
@@ -87,13 +89,53 @@ class GenericLandController : RideController {
}
}
override fun getStat(entity: PokemonEntity, stat: RidingStat): Double {
val expr = statExpressions[stat] ?: return 0.0
return getRuntime(entity).resolveDouble(expr)
}
override fun speed(entity: PokemonEntity, driver: Player): Float {
val state = getState(entity, ::GenericLandState)
return state.rideVel.length().toFloat()
}
override fun updatePassengerRotation(entity: PokemonEntity, driver: LivingEntity) {
//Take the inverse so that it cancels out how
//much the entity rotates.
val turnAmount = calcRotAmount(entity, driver)
val topSpeed = this.getStat(entity, RidingStat.SPEED)
val state = getState(entity, ::GenericLandState)
val maxYawDiff = 90.0f
//Normalize the current rotation diff
val rotMod = Mth.wrapDegrees(driver.yRot - entity.yRot) / maxYawDiff
val rotAmount = 10.0f
//Take the inverse so that you turn more at higher speeds
val normSpeed = 1.0f - 0.5f*normalizeVal(state.rideVel.length(), 0.0, topSpeed).toFloat()
//driver.yRot += (entity.riding.deltaRotation.y - turnAmount)
//driver.setYHeadRot(driver.yHeadRot + (entity.riding.deltaRotation.y) - turnAmount)
}
override fun clampPassengerRotation(entity: PokemonEntity, driver: LivingEntity) {
val f = Mth.wrapDegrees(driver.yRot - entity.yRot)
val runtime = getRuntime(entity)
val lookYawLimit = 90.0f
val g = Mth.clamp(f, -lookYawLimit, lookYawLimit)
driver.yRotO += g - f
driver.yRot = driver.yRot + g - f
driver.setYHeadRot(driver.yRot)
}
override fun rotation(entity: PokemonEntity, driver: LivingEntity): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
val turnAmount = calcRotAmount(entity, driver)
return Vec2(driver.xRot, entity.yRot + turnAmount )
}
override fun velocity(entity: PokemonEntity, driver: Player, input: Vec3): Vec3 {
@@ -116,7 +158,7 @@ class GenericLandController : RideController {
//Jump the thang!
if (Minecraft.getInstance().options.keyJump.isDown() && entity.onGround()) {
state.rideVel = Vec3(state.rideVel.x, 2.0, state.rideVel.z)
state.rideVel = Vec3(state.rideVel.x, 1.0, state.rideVel.z)
}
//This is cheap.
@@ -127,6 +169,8 @@ class GenericLandController : RideController {
//entity.deltaMovement = entity.deltaMovement.lerp(velocity, 1.0)
//state.currVel = state.currVel.add( f.toDouble() / 20.0, gravity / 20.0 , g.toDouble() / 20.0)
println("Z speed: %.2f".format(state.rideVel.z) + "->" + "Z speed: %.2f".format(entity.deltaMovement.horizontalDistance()))
return state.rideVel
}
@@ -177,7 +221,28 @@ class GenericLandController : RideController {
return 0.0
}
override fun inertia(entity: PokemonEntity ): Double = 0.3
override fun rideFovMult(entity: PokemonEntity, driver: Player): Float {
val topSpeed = this.getStat(entity, RidingStat.SPEED)
val sprintTopSpeed = topSpeed * getRuntime(entity).resolveDouble(sprintMod) //TODO: make it so sprint mod is global and likely molang json configurable
val state = getState(entity, ::GenericLandState)
//Must I ensure that topspeed is greater than minimum?
val normalizedSprintSpeed = normalizeVal(state.rideVel.z, topSpeed, sprintTopSpeed)
//Only ever want the fov change to be a max of 0.2 and for it to have non linear scaling.
val fovChange = 1.0f + normalizedSprintSpeed.pow(2).toFloat() * 0.2f
return fovChange
}
override fun getActiveRidingAltPose(entity: PokemonEntity, driver: Player): RidingAltPose {
//TODO: Should this be converted to a sprint flag?
val isSprinting = Minecraft.getInstance().options.keySprint.isDown
val pose = if (isSprinting) RidingAltPose.SPRINTING else RidingAltPose.NONE
return pose
}
override fun inertia(entity: PokemonEntity ): Double = 0.7
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
@@ -205,42 +270,89 @@ class GenericLandController : RideController {
}
/*
* Normalizes the current speed between minSpeed and maxSpeed.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minSpeed and 1.0 represents maxSpeed.
* Normalizes the given value between a min and a max.
* The result is clamped between 0.0 and 1.0, where 0.0 represents x is at or below min
* and 1.0 represents x is at or above it.
*/
fun normalizeSpeed(currSpeed: Double, minSpeed: Double, maxSpeed: Double): Double {
require(maxSpeed > minSpeed) { "maxSpeed must be greater than minSpeed" }
return ((currSpeed - minSpeed) / (maxSpeed - minSpeed)).coerceIn(0.0, 1.0)
fun normalizeVal(x: Double, min: Double, max: Double): Double {
require(max > min) { "max must be greater than min" }
return ((x - min) / (max - min)).coerceIn(0.0, 1.0)
}
/*
* Calculates the rotation amount given the difference between
* the current player y rot and entity y rot. This gives the
* affect of influencing a rides rotation through the mouse
* without it being instant.
*/
fun calcRotAmount(entity: PokemonEntity, driver: LivingEntity): Float {
val state = getState(entity, ::GenericLandState)
val topSpeed = this.getStat(entity, RidingStat.SPEED)
//In degrees per second
val handling = this.getStat(entity, RidingStat.SKILL)
val maxYawDiff = 90.0f
//Normalize the current rotation diff
val rotDiff = Mth.wrapDegrees(driver.yRot - entity.yRot)
val rotDiffNorm = rotDiff / maxYawDiff
//Take the square root so that the ride levels out quicker when at lower differences between entity
//y and driver y
//This influences the speed of the turn based on how far in one direction you're looking
val rotDiffMod = (sqrt(abs(rotDiffNorm)) * rotDiffNorm.sign)
//Take the inverse so that you turn more at higher speeds
val normSpeed = 1.0f // = 1.0f - 0.5f*normalizeVal(state.rideVel.length(), 0.0, topSpeed).toFloat()
//Ensure you only ever rotate as much difference as there is between the angles.
val turnSpeed = handling.toFloat() / 20.0f * rotDiffMod * normSpeed
val rotAmount = turnSpeed.coerceIn(-abs(rotDiff), abs(rotDiff))
return rotAmount
}
fun calculateRideSpaceVel( entity: PokemonEntity, driver: Player, state: GenericLandState){
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
val accel = getRuntime(entity).resolveDouble(accelExpr)
val topSpeed = this.getStat(entity, RidingStat.SPEED)
val accel = this.getStat(entity, RidingStat.ACCELERATION)
val speed = state.rideVel.length()
val minSpeed = 0.0
val reverseMod = 1.0 / 3.0
val isSprinting = Minecraft.getInstance().options.keySprint.isDown
val sprintMod = getRuntime(entity).resolveDouble(sprintMod)
//speed up and slow down based on input
if (driver.zza > 0.0 && speed <= topSpeed && state.stamina > 0.0f) {
//modify acceleration to be slower when at closer speeds to top speed
val accelMod = max((normalizeSpeed(speed, minSpeed, topSpeed) - 1).pow(2), 0.1)
state.rideVel = Vec3(state.rideVel.x, state.rideVel.y, min(state.rideVel.z + (accel * accelMod) , topSpeed))
}
else if (driver.zza >= 0.0 && state.stamina == 0.0f || speed > topSpeed) {
state.rideVel = Vec3(state.rideVel.x, state.rideVel.y, max(state.rideVel.z - ((accel) / 4) , minSpeed))
}
else if (driver.zza < 0.0 && speed > minSpeed) {
//modify deccel to be slower when at closer speeds to minimum speed
val deccelMod = max( -(normalizeSpeed(speed, minSpeed, topSpeed)) + 1, 0.0)
if ((driver.zza != 0.0f && state.rideVel.z < topSpeed && !isSprinting) ||
(driver.zza != 0.0f && state.rideVel.z < topSpeed * sprintMod && isSprinting)) {
//make sure it can't exceed top speed
val forwardInput = when {
driver.zza > 0 && state.rideVel.z > topSpeed && !isSprinting -> 0.0
driver.zza < 0 && state.rideVel.z < (-topSpeed * reverseMod) && !isSprinting -> 0.0
driver.zza > 0 && state.rideVel.z > topSpeed * sprintMod && isSprinting -> 0.0
driver.zza < 0 && state.rideVel.z < (-topSpeed * sprintMod * reverseMod) && isSprinting -> 0.0
else -> driver.zza.sign
}
//Decelerate currently always a constant half of max acceleration.
state.rideVel = Vec3(state.rideVel.x, state.rideVel.y, max( state.rideVel.z - ((accel * deccelMod) / 2) , minSpeed))
state.rideVel = Vec3(
state.rideVel.x,
state.rideVel.y,
(state.rideVel.z + (accel * forwardInput.toDouble())))
}
//Otherwise if there is no input then apply deceleration
else if (driver.zza == 0.0f) {
//Make deceleration constant no matter the stats
val decelRate = 1.0 / 20.0
state.rideVel = Vec3(
state.rideVel.x,
state.rideVel.y,
lerp( state.rideVel.z, 0.0, decelRate )
)
}
state.rideVel = Vec3(state.rideVel.x, max(state.rideVel.y - 0.2, -1.0) , state.rideVel.z)
if (entity.onGround()) {
//If on the ground then stop the increase of rideVel's gravity velocity
if (!entity.onGround()) {
state.rideVel = Vec3(state.rideVel.x, max(state.rideVel.y - 0.1, -1.0) , state.rideVel.z)
} else {
state.rideVel = Vec3(state.rideVel.x, 0.0 , state.rideVel.z)
}
@@ -8,20 +8,15 @@
package com.cobblemon.mod.common.pokemon.riding.controllers
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.controller.posing.PoseOption
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.getString
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.writeString
import com.cobblemon.mod.common.util.*
import kotlin.math.max
import kotlin.math.min
import net.minecraft.network.RegistryFriendlyByteBuf
@@ -37,6 +32,20 @@ class GenericLiquidController : RideController {
val KEY: ResourceLocation = cobblemonResource("swim/generic")
}
override val key: ResourceLocation = KEY
override val poseProvider: PoseProvider = PoseProvider(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { it.isSwimming && it.entityData.get(PokemonEntity.MOVING) })
override val condition: (PokemonEntity) -> Boolean = { entity ->
//This could be kinda weird... what if the top of the mon is in a fluid but the bottom isnt?
Shapes.create(entity.boundingBox).blockPositionsAsListRounded().any {
if (entity.isInWater || entity.isUnderWater) {
return@any true
}
val blockState = entity.level().getBlockState(it)
return@any !blockState.fluidState.isEmpty
}
}
var canJump = "true".asExpression()
private set
var jumpVector = listOf("0".asExpression(), "0.3".asExpression(), "0".asExpression())
@@ -50,18 +59,19 @@ class GenericLiquidController : RideController {
var strafeFactor = "0.2".asExpression()
private set
override val key: ResourceLocation = KEY
override val poseProvider: PoseProvider = PoseProvider(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { it.isSwimming && it.entityData.get(PokemonEntity.MOVING) })
override val condition: (PokemonEntity) -> Boolean = { entity ->
//This could be kinda weird... what if the top of the mon is in a fluid but the bottom isnt?
Shapes.create(entity.boundingBox).blockPositionsAsListRounded().any {
if (entity.isInWater || entity.isUnderWater) {
return@any true
}
val blockState = entity.level().getBlockState(it)
return@any !blockState.fluidState.isEmpty
}
//TODO: These are temp and need to be updated to real values
private val statExpressions: Map<RidingStat, Expression> = mapOf(
RidingStat.SKILL to "q.get_ride_stats('SKILL', 'LIQUID', 135.0, 45.0)".asExpression(),
RidingStat.SPEED to "q.get_ride_stats('SPEED', 'LIQUID', 0.6, 0.2)".asExpression(),
RidingStat.ACCELERATION to "q.get_ride_stats('ACCELERATION', 'LIQUID', (1.0 / (20.0 * 3.0)), (1.0 / (20.0 * 8.0)))".asExpression(),
RidingStat.STAMINA to "q.get_ride_stats('STAMINA', 'LIQUID', 120.0, 20.0)".asExpression(),
RidingStat.JUMP to "q.get_ride_stats('JUMP', 'LIQUID', 200.0, 128.0)".asExpression()
)
override fun getStat(entity: PokemonEntity, stat: RidingStat): Double {
val expr = statExpressions[stat] ?: return 0.0
return getRuntime(entity).resolveDouble(expr)
}
override fun speed(entity: PokemonEntity, driver: Player): Float {
@@ -44,6 +44,21 @@ class GliderAirController : RideController {
var canStrafe: Expression = "false".asExpression()
private set
//TODO: These are temp and need to be updated to real values
private val statExpressions: Map<RidingStat, Expression> = mapOf(
RidingStat.SKILL to "q.get_ride_stats('SKILL', 'AIR', 135.0, 45.0)".asExpression(),
RidingStat.SPEED to "q.get_ride_stats('SPEED', 'AIR', 1.0, 0.35)".asExpression(),
RidingStat.ACCELERATION to "q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 3.0)), (1.0 / (20.0 * 8.0)))".asExpression(),
RidingStat.STAMINA to "q.get_ride_stats('STAMINA', 'AIR', 120.0, 20.0)".asExpression(),
RidingStat.JUMP to "q.get_ride_stats('JUMP', 'AIR', 200.0, 128.0)".asExpression()
)
override fun getStat(entity: PokemonEntity, stat: RidingStat): Double {
val expr = statExpressions[stat] ?: return 0.0
return getRuntime(entity).resolveDouble(expr)
}
override fun speed(entity: PokemonEntity, driver: Player): Float {
return getRuntime(entity).resolveFloat(speed)
}
@@ -14,6 +14,7 @@ import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.controller.posing.PoseOption
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
@@ -63,6 +64,20 @@ class HelicopterAirController : RideController {
var speed: Expression = "1.0".asExpression()
private set
//TODO: These are temp and need to be updated to real values
private val statExpressions: Map<RidingStat, Expression> = mapOf(
RidingStat.SKILL to "q.get_ride_stats('SKILL', 'AIR', 135.0, 45.0)".asExpression(),
RidingStat.SPEED to "q.get_ride_stats('SPEED', 'AIR', 1.0, 0.35)".asExpression(),
RidingStat.ACCELERATION to "q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 3.0)), (1.0 / (20.0 * 8.0)))".asExpression(),
RidingStat.STAMINA to "q.get_ride_stats('STAMINA', 'AIR', 120.0, 20.0)".asExpression(),
RidingStat.JUMP to "q.get_ride_stats('JUMP', 'AIR', 200.0, 128.0)".asExpression()
)
override fun getStat(entity: PokemonEntity, stat: RidingStat): Double {
val expr = statExpressions[stat] ?: return 0.0
return getRuntime(entity).resolveDouble(expr)
}
override fun speed(entity: PokemonEntity, driver: Player): Float {
//Increased max speed to exaggerate movement.
@@ -9,12 +9,12 @@
package com.cobblemon.mod.common.pokemon.riding.controllers
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.controller.posing.PoseOption
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.riding.states.JetAirState
@@ -36,35 +36,36 @@ class JetAirController : RideController {
var gravity: Expression = "0".asExpression()
private set
var minSpeed: Expression = "1.2".asExpression()
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 140.0, 20.0)".asExpression()
private set
//TODO: Think about converting this to fully rely on the skillExpr evaluation
var handlingYawExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 25.0, 8.0)".asExpression()
private set
var topSpeedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 2.5, 1.0)".asExpression()
private set
// Max accel is a whole 1.0 in 1 second. The conversion in the function below is to convert seconds to ticks
var accelExpr: Expression = "q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 1.0)), (1.0 / (20.0 * 5.0)))".asExpression()
private set
// Between 60 seconds and 10 seconds at the lowest when at full speed.
var staminaExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 60.0, 10.0)".asExpression()
private set
var jumpExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 300.0, 128.0)".asExpression()
private set
// Make configurable by json
var infiniteStamina: Expression = "false".asExpression()
private set
var infiniteAltitude: Expression = "false".asExpression()
private set
private val statExpressions: Map<RidingStat, Expression> = mapOf(
RidingStat.SKILL to "q.get_ride_stats('SKILL', 'AIR', 140.0, 20.0)".asExpression(),
RidingStat.SPEED to "q.get_ride_stats('SPEED', 'AIR', 2.5, 1.0)".asExpression(),
RidingStat.ACCELERATION to "q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 1.0)), (1.0 / (20.0 * 5.0)))".asExpression(),
RidingStat.STAMINA to "q.get_ride_stats('STAMINA', 'AIR', 60.0, 10.0)".asExpression(),
RidingStat.JUMP to "q.get_ride_stats('JUMP', 'AIR', 300.0, 128.0)".asExpression()
)
override fun getStat(entity: PokemonEntity, stat: RidingStat): Double {
val expr = statExpressions[stat] ?: return 0.0
return getRuntime(entity).resolveDouble(expr)
}
override fun speed(entity: PokemonEntity, driver: Player): Float {
//retrieve stats
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
val staminaStat = getRuntime(entity).resolveDouble(staminaExpr)
val topSpeed = this.getStat(entity, RidingStat.SPEED)
val staminaStat = this.getStat(entity, RidingStat.STAMINA)
//retrieve state
val state = getState(entity, ::JetAirState)
@@ -122,15 +123,13 @@ class JetAirController : RideController {
//If stamina has run out then initiate forced glide down.
upForce = if(state.stamina > 0.0) upForce else -0.7
val altitudeLimit = getRuntime(entity).resolveDouble(jumpExpr)
val pushingHeightLimit = if(getRuntime(entity).resolveBoolean(infiniteAltitude)) false
else (entity.y >= 128 && entity.xRot <= 0)
val altitudeLimit = this.getStat(entity, RidingStat.JUMP)
//Incorporate the altitude limiter.
//Apply downwards force if limit is reached. Resist upwards force on a buffer of 30 blocks past the limit.
upForce = if(pushingHeightLimit) upForce - upForce * ((entity.y - altitudeLimit) / 30) - 0.5
else upForce
//Only limit altitude if altitude is not infinite
if (!getRuntime(entity).resolveBoolean(infiniteAltitude)) {
//Provide a hard limit on altitude
upForce = if (entity.y >= altitudeLimit && upForce > 0) 0.0 else upForce
}
val velocity = Vec3(0.0 , upForce, forwardForce )
@@ -187,7 +186,7 @@ class JetAirController : RideController {
state.currMouseYForce = (state.currMouseYForce + (0.0015 * yMouse * invertPitch)).coerceIn(-1.0, 1.0)
//Get handling in degrees per second
var handling = getRuntime(entity).resolveDouble(handlingExpr)
var handling = this.getStat(entity, RidingStat.SKILL)
//convert it to delta time
handling *= (cappedDeltaTime)
@@ -241,7 +240,7 @@ class JetAirController : RideController {
override fun shouldRoll(entity: PokemonEntity) = true
override fun rideFovMult(entity: PokemonEntity, driver: Player): Float {
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
val topSpeed = this.getStat(entity, RidingStat.SPEED)
val minSpeed = getRuntime(entity).resolveDouble(minSpeed)
val state = getState(entity, ::JetAirState)
@@ -258,12 +257,7 @@ class JetAirController : RideController {
super.encode(buffer)
buffer.writeString(gravity.toString())
buffer.writeString(minSpeed.toString())
buffer.writeString(handlingExpr.toString())
buffer.writeString(handlingYawExpr.toString())
buffer.writeString(topSpeedExpr.toString())
buffer.writeString(accelExpr.toString())
buffer.writeString(jumpExpr.toString())
buffer.writeString(staminaExpr.toString())
buffer.writeString(infiniteStamina.toString())
buffer.writeString(infiniteAltitude.toString())
}
@@ -271,12 +265,7 @@ class JetAirController : RideController {
override fun decode(buffer: RegistryFriendlyByteBuf) {
gravity = buffer.readString().asExpression()
minSpeed = buffer.readString().asExpression()
handlingExpr = buffer.readString().asExpression()
handlingYawExpr = buffer.readString().asExpression()
topSpeedExpr = buffer.readString().asExpression()
accelExpr = buffer.readString().asExpression()
jumpExpr = buffer.readString().asExpression()
staminaExpr = buffer.readString().asExpression()
infiniteStamina = buffer.readString().asExpression()
infiniteAltitude = buffer.readString().asExpression()
}
@@ -290,9 +279,9 @@ class JetAirController : RideController {
*/
fun calculateRideSpaceVel( entity: PokemonEntity, driver: Player, state: JetAirState){
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
val accel = getRuntime(entity).resolveDouble(accelExpr)
val altitudeLimit = getRuntime(entity).resolveDouble(jumpExpr)
val topSpeed = this.getStat(entity, RidingStat.SPEED)
val accel = this.getStat(entity, RidingStat.ACCELERATION)
val altitudeLimit = this.getStat(entity, RidingStat.JUMP)
val minSpeed = getRuntime(entity).resolveDouble(minSpeed)
val speed = state.rideVel.length()
@@ -12,6 +12,8 @@ import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.controller.posing.PoseOption
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.controller.posing.RidingAltPose
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -23,6 +25,7 @@ import com.cobblemon.mod.common.util.getString
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.resolveInt
import com.cobblemon.mod.common.util.writeString
import net.minecraft.client.Minecraft
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
@@ -66,20 +69,44 @@ class RunUpToFlightCompositeController : RideController {
}
}
override fun getStat(entity: PokemonEntity, stat: RidingStat): Double {
return getActiveController(entity).getStat(entity, stat)
}
override fun speed(
entity: PokemonEntity,
driver: Player
): Float {
val state = getState(entity, ::RunUpToFlightCompositeState)
if (state.activeController == flightController && entity.onGround() && state.timeTransitioned + 20 < entity.level().gameTime) {
if (state.activeController == flightController && entity.onGround() &&
state.timeTransitioned + 20 < entity.level().gameTime) {
//Pass the speed to the next state
val flightState = flightController.getState(entity, ::BirdAirState)
val groundState = landController.getState(entity, ::GenericLandState)
groundState.currSpeed = flightState.currSpeed
val flightState = flightController.getState( entity, ::BirdAirState )
val groundState = landController.getState( entity, ::GenericLandState )
groundState.rideVel = flightState.rideVel
groundState.stamina = flightState.stamina
getState(entity, ::RunUpToFlightCompositeState).activeController = landController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
getState(entity, ::RunUpToFlightCompositeState).let {
it.activeController = landController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
it.timeTransitioned = entity.level().gameTime
}
}
else if (state.activeController == landController && Minecraft.getInstance().options.keyJump.isDown &&
state.timeTransitioned + 20 < entity.level().gameTime) {
//Pass the speed to the next state
val flightState = flightController.getState( entity, ::BirdAirState )
val groundState = landController.getState( entity, ::GenericLandState )
flightState.rideVel = groundState.rideVel
flightState.stamina = groundState.stamina
getState(entity, ::RunUpToFlightCompositeState).let {
it.activeController = flightController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
it.timeTransitioned = entity.level().gameTime
}
}
return getActiveController(entity).speed(entity, driver)
@@ -89,6 +116,14 @@ class RunUpToFlightCompositeController : RideController {
return getActiveController(entity).rotation(entity, driver)
}
override fun updatePassengerRotation(entity: PokemonEntity, driver: LivingEntity) {
return getActiveController(entity).updatePassengerRotation(entity, driver)
}
override fun clampPassengerRotation(entity: PokemonEntity, driver: LivingEntity) {
return getActiveController(entity).clampPassengerRotation(entity, driver)
}
override fun velocity(
entity: PokemonEntity,
driver: Player,
@@ -101,6 +136,14 @@ class RunUpToFlightCompositeController : RideController {
return getActiveController(entity).angRollVel(entity, driver, deltaTime)
}
override fun rideFovMult(entity: PokemonEntity, driver: Player): Float {
return getActiveController(entity).rideFovMult(entity, driver)
}
override fun setRideBar(entity: PokemonEntity, driver: Player): Float {
return getActiveController(entity).setRideBar(entity, driver)
}
override fun canJump(entity: PokemonEntity, driver: Player): Boolean {
return getActiveController(entity).canJump(entity, driver)
}
@@ -111,19 +154,9 @@ class RunUpToFlightCompositeController : RideController {
jumpStrength: Int
): Vec3 {
val controller = getActiveController(entity)
if (controller == landController && jumpStrength >= getRuntime(entity).resolveInt(minimumJump)) {
val state = getState(entity, ::CompositeState)
val isJumping = Minecraft.getInstance().options.keyJump.isDown
//Pass the speed to the next state
val flightState = flightController.getState(entity, ::BirdAirState )
val groundState = landController.getState(entity, ::GenericLandState)
flightState.currSpeed = groundState.currSpeed
getState(entity, ::RunUpToFlightCompositeState).let {
it.activeController = flightController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
it.timeTransitioned = entity.level().gameTime
}
}
return controller.jumpForce(entity, driver, jumpStrength)
}
@@ -139,6 +172,10 @@ class RunUpToFlightCompositeController : RideController {
override fun useAngVelSmoothing(entity: PokemonEntity): Boolean = getActiveController(entity).useAngVelSmoothing(entity)
override fun getActiveRidingAltPose(entity: PokemonEntity, driver: Player): RidingAltPose {
return getActiveController(entity).getActiveRidingAltPose(entity, driver)
}
override fun rotationOnMouseXY
(entity: PokemonEntity,
driver: Player,
@@ -12,6 +12,7 @@ import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.controller.posing.PoseOption
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -67,6 +68,10 @@ class RunUpToJetFlightCompositeController : RideController {
}
}
override fun getStat(entity: PokemonEntity, stat: RidingStat): Double {
return getActiveController(entity).getStat(entity, stat)
}
//TODO: This composite controller needs to have the jet lift off at a certain speed.
//maybe just pressing shift or space? Unsure, will need to test to see what works best.
override fun speed(
@@ -84,6 +89,7 @@ class RunUpToJetFlightCompositeController : RideController {
val groundState = landController.getState(entity, ::GenericLandState)
groundState.currSpeed = flightState.currSpeed
groundState.stamina = flightState.stamina
groundState.rideVel = flightState.rideVel
//Clear accumulated rotation input when transferring to ground input
flightState.currMouseXForce = 0.0
@@ -108,6 +114,7 @@ class RunUpToJetFlightCompositeController : RideController {
val groundState = landController.getState(entity, ::GenericLandState)
flightState.currSpeed = groundState.currSpeed
flightState.stamina = groundState.stamina
flightState.rideVel = groundState.rideVel
getState(entity, ::RunUpToFlightCompositeState).let {
it.activeController = flightController
@@ -123,6 +130,15 @@ class RunUpToJetFlightCompositeController : RideController {
return getActiveController(entity).rotation(entity, driver)
}
override fun clampPassengerRotation(entity: PokemonEntity, driver: LivingEntity) {
return getActiveController(entity).clampPassengerRotation(entity, driver)
}
override fun updatePassengerRotation(entity: PokemonEntity, driver: LivingEntity) {
return getActiveController(entity).updatePassengerRotation(entity, driver)
}
override fun velocity(
entity: PokemonEntity,
driver: Player,
@@ -8,13 +8,17 @@
package com.cobblemon.mod.common.pokemon.riding.controllers
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.controller.posing.PoseOption
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.resolveDouble
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.LivingEntity
@@ -44,10 +48,24 @@ class SwimDashController : RideController {
}
}
//TODO: These are temp and need to be updated to real values
private val statExpressions: Map<RidingStat, Expression> = mapOf(
RidingStat.SKILL to "q.get_ride_stats('SKILL', 'LIQUID', 135.0, 45.0)".asExpression(),
RidingStat.SPEED to "q.get_ride_stats('SPEED', 'LIQUID', 0.6, 0.2)".asExpression(),
RidingStat.ACCELERATION to "q.get_ride_stats('ACCELERATION', 'LIQUID', (1.0 / (20.0 * 3.0)), (1.0 / (20.0 * 8.0)))".asExpression(),
RidingStat.STAMINA to "q.get_ride_stats('STAMINA', 'LIQUID', 120.0, 20.0)".asExpression(),
RidingStat.JUMP to "q.get_ride_stats('JUMP', 'LIQUID', 200.0, 128.0)".asExpression()
)
/** Indicates that we are currently enacting a dash, and that further movement inputs should be ignored */
private var dashing = false
private var ticks = 0
override fun getStat(entity: PokemonEntity, stat: RidingStat): Double {
val expr = statExpressions[stat] ?: return 0.0
return getRuntime(entity).resolveDouble(expr)
}
override fun speed(entity: PokemonEntity, driver: Player): Float {
if(this.dashing) {
if(this.ticks++ >= DASH_TICKS) {
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.pokemon.riding.controllers
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.controller.RideController
@@ -17,17 +18,13 @@ import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.riding.controllers.BirdAirController.Companion
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.getString
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.writeString
import com.cobblemon.mod.common.pokemon.riding.states.VehicleLandState
import com.cobblemon.mod.common.util.*
import net.minecraft.client.Minecraft
import kotlin.math.absoluteValue
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvents
import net.minecraft.util.Mth
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
@@ -36,49 +33,13 @@ import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sign
class VehicleLandController : RideController {
companion object {
val KEY: ResourceLocation = cobblemonResource("land/vehicle")
val MAXTOPSPEED = 1.0 // 20 bl/s
val MINTOPSPEED = 0.35 // 7 bl/s
//val MINSPEED = 0.25 // 5 bl/s
//Accel will lie between 1.0 second and 5.0 seconds
val MAXACCEL = (MAXTOPSPEED ) / (20*3) //3.0 second to max speed
val MINACCEL = (MAXTOPSPEED ) / (20*8) // 8 seconds to max speed
//Can rotate 90 degrees
val MAXHANDLING = 90.0
val MINHANDLING = 30.0
val MAXYAWHANDLING = 16.0
val MINYAWHANDLING = 8.0
}
var canJump = "true".asExpression()
private set
var jumpVector = listOf("0".asExpression(), "0.3".asExpression(), "0".asExpression())
private set
var speed = "0.3".asExpression()
private set
var driveFactor = "1.0".asExpression()
private set
var reverseDriveFactor = "0.25".asExpression()
private set
var minimumSpeedToTurn = "0.1".asExpression()
private set
var rotationSpeed = "45/20".asExpression()
private set
var lookYawLimit = "101".asExpression()
private set
var currSpeed = 0.0
private set
@Transient
override val key: ResourceLocation = KEY
@Transient
@@ -102,17 +63,57 @@ class VehicleLandController : RideController {
}
}
var canJump = "true".asExpression()
private set
var jumpVector = listOf("0".asExpression(), "0.3".asExpression(), "0".asExpression())
private set
var speed = "0.3".asExpression()
private set
var driveFactor = "1.0".asExpression()
private set
var reverseDriveFactor = "0.25".asExpression()
private set
var minimumSpeedToTurn = "0.1".asExpression()
private set
var rotationSpeed = "45/20".asExpression()
private set
var lookYawLimit = "101".asExpression()
private set
var currSpeed = 0.0
private set
//TODO: Replace these temp values
private val statExpressions: Map<RidingStat, Expression> = mapOf(
RidingStat.SKILL to "q.get_ride_stats('SKILL', 'LAND', 140.0, 20.0)".asExpression(),
RidingStat.SPEED to "q.get_ride_stats('SPEED', 'LAND', 1.0, 0.3)".asExpression(),
// In seconds to max speed
RidingStat.ACCELERATION to "q.get_ride_stats('ACCELERATION', 'LAND', (1.0 / (20.0 * 0.5)), (1.0 / (20.0 * 5.0)))".asExpression(),
// In Seconds
RidingStat.STAMINA to "q.get_ride_stats('STAMINA', 'LAND', 30.0, 10.0)".asExpression(),
// Block Jump Height
RidingStat.JUMP to "q.get_ride_stats('JUMP', 'LAND', 10.0, 1.0)".asExpression()
)
override fun getStat(entity: PokemonEntity, stat: RidingStat): Double {
val expr = statExpressions[stat] ?: return 0.0
return getRuntime(entity).resolveDouble(expr)
}
override fun speed(entity: PokemonEntity, driver: Player): Float {
val topSpeed = entity.getRideStat(RidingStat.SPEED, RidingStyle.AIR, MINTOPSPEED, MAXTOPSPEED)
val accel = entity.getRideStat(RidingStat.ACCELERATION, RidingStyle.AIR, MINACCEL, MAXACCEL)
val topSpeed = this.getStat(entity, RidingStat.SPEED)
val accel = this.getStat(entity, RidingStat.ACCELERATION)
//speed up and slow down based on input
if (driver.zza > 0.0 && currSpeed < topSpeed) {
currSpeed = min(currSpeed + accel , topSpeed)
} else if (driver.zza < 0.0 && currSpeed > 0.0) {
//Decelerate is now always a constant half of max acceleration.
currSpeed = max(currSpeed - (MAXACCEL / 2), 0.0)
currSpeed = max(currSpeed - (accel / 2), 0.0)
}
return currSpeed.toFloat()
@@ -120,7 +121,51 @@ class VehicleLandController : RideController {
override fun rotation(entity: PokemonEntity, driver: LivingEntity): Vec2 {
val rotationDegrees = driver.xxa * getRuntime(entity).resolveFloat(this.rotationSpeed)
return Vec2(driver.xRot, entity.yRot - rotationDegrees)
val state = getState(entity, ::VehicleLandState)
if( Minecraft.getInstance().options.keyJump.isDown() ){
//If the current drift has not been set and the car is rotating then set drift
if( state.currDrift == 0.0 && rotationDegrees != 0.0f) {
//Set to base value which will be half.
state.currDrift = sign(rotationDegrees) * 2.5
state.turnSmoother.reset()
}
//Calculate any changes to drift
val driftChangeRate = (1.0 / (20.0 * 0.15))
//If Drift is negative already
if(state.currDrift < 0) {
state.currDrift = (state.currDrift + (sign(rotationDegrees) * driftChangeRate)).coerceIn(-5.0, -1.0)
}else{
state.currDrift = (state.currDrift + (sign(rotationDegrees) * driftChangeRate)).coerceIn(1.0, 5.0)
}
return Vec2(driver.xRot, entity.yRot - state.currDrift.toFloat())
}
//If the drift key is not held then reset the curr drift to 0 and use normal rotation
else{
//If the drift key has just been released perform boost addition and drift reset
if( state.currDrift != 0.0){
state.turnSmoother.reset()
state.currDrift = 0.0
state.driftBoost = min(state.driftBoost + 1.0, 2.0)
driver.level().playSound(
null as Player?,
driver.getX(),
driver.getY(),
driver.getZ(), SoundEvents.PLAYER_ATTACK_WEAK,
driver.getSoundSource(), 1.0f, 0.2f
)
}
//Restrict turning while boost is active
val boostRotDebuff = if( state.driftBoost != 0.0 ) 0.5f else 1.0f
return Vec2(driver.xRot, entity.yRot - (rotationDegrees * boostRotDebuff) )
//return Vec2(driver.xRot, state.turnSmoother.getNewDeltaValue((entity.yRot - rotationDegrees).toDouble(), 1.0).toFloat())
}
}
override fun velocity(entity: PokemonEntity, driver: Player, input: Vec3): Vec3 {
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.world.phys.Vec3
data class BirdAirState(val entity: PokemonEntity) : RidingState {
var currSpeed: Double = 0.0
var lastVelocity: Vec3 = Vec3(0.0, 0.0, 0.0)
override var rideVel: Vec3 = Vec3.ZERO
override var stamina: Float = 1.0f
var gliding: Boolean = false
}
@@ -11,8 +11,11 @@ package com.cobblemon.mod.common.pokemon.riding.states
import com.cobblemon.mod.common.api.riding.RidingState
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.world.phys.Vec3
open class CompositeState(pokemonEntity: PokemonEntity) : RidingState {
override var rideVel: Vec3 = Vec3.ZERO
override var stamina: Float = 1.0f
var activeController: RideController? = null
var timeTransitioned = -100L
}
@@ -0,0 +1,20 @@
/*
* 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.pokemon.riding.states
import com.cobblemon.mod.common.api.riding.RidingState
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.world.phys.Vec3
data class DolphinLiquidState(val entity: PokemonEntity) : RidingState {
override var rideVel: Vec3 = Vec3.ZERO
override var stamina: Float = 1.0f
var currSpeed: Double = 0.0
var gliding: Boolean = false
}
@@ -13,7 +13,7 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.world.phys.Vec3
data class GenericLandState(val entity: PokemonEntity) : RidingState {
override var rideVel: Vec3 = Vec3.ZERO
override var stamina: Float = 1.0f
var currSpeed: Double = 0.0
var rideVel: Vec3 = Vec3.ZERO
var stamina: Float = 1.0f
}
@@ -13,9 +13,9 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.world.phys.Vec3
data class JetAirState(val entity: PokemonEntity) : RidingState {
override var rideVel: Vec3 = Vec3.ZERO
override var stamina: Float = 1.0f
var currSpeed: Double = 0.0
var stamina: Float = 1.0f
var rideVel: Vec3 = Vec3.ZERO
var currMouseXForce: Double = 0.0
var currMouseYForce: Double = 0.0
}
@@ -9,7 +9,10 @@
package com.cobblemon.mod.common.pokemon.riding.states
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.world.phys.Vec3
class RunUpToFlightCompositeState(pokemonEntity: PokemonEntity) : CompositeState(pokemonEntity) {
override var rideVel: Vec3 = Vec3.ZERO
override var stamina: Float = 1.0f
var currSpeed: Double = 0.0
}
@@ -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.pokemon.riding.states
import com.cobblemon.mod.common.api.riding.RidingState
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.util.SmoothDouble
import net.minecraft.world.phys.Vec3
data class VehicleLandState(val entity: PokemonEntity) : RidingState {
override var rideVel: Vec3 = Vec3.ZERO
override var stamina: Float = 1.0f
var currDrift: Double = 0.0
var driftBoost: Double = 0.0
var turnSmoother: SmoothDouble = SmoothDouble()
}
@@ -124,8 +124,14 @@
},
"pokeball": {
"scale": {
"0.4583": {"pre": [0, 0, 0], "post": [0.3, 0.3, 0.3]},
"0.5417": {"pre": [0.3, 0.3, 0.3], "post": [0.3, 0.3, 0.3]},
"0.4583": {
"pre": [0, 0, 0],
"post": [0.3, 0.3, 0.3]
},
"0.5417": {
"pre": [0.3, 0.3, 0.3],
"post": [0.3, 0.3, 0.3]
},
"0.7083": [1, 1, 1]
}
},
@@ -556,105 +562,6 @@
}
}
},
"animation.trainer_generic.send_out": {
"animation_length": 3.625,
"bones": {
"torso": {
"rotation": {
"0.0": [1, 0, 0],
"0.9375": [1, 0, 0],
"1.375": [1, -7.5, 0],
"2.875": [1, -7.5, 0],
"3.375": [1, 0, 0],
"3.625": [1, 0, 0]
}
},
"head": {
"rotation": {
"0.0": [-1, 0, 0],
"0.9375": [-1, 0, 0],
"1.375": [-1, 7.5, 0],
"2.875": [-1, 7.5, 0],
"3.375": [-1, 0, 0],
"3.625": [-1, 0, 0]
}
},
"arm_ball": {
"rotation": {
"0.0": [3, 0, 3.5],
"0.5": [3, 0, 3.5],
"0.9375": {
"pre": [14.5, 2.5, -17.5],
"post": [14.5, 2.5, -17.5],
"lerp_mode": "catmullrom"
},
"1.125": {
"post": [5, 2.5, -0.5],
"lerp_mode": "catmullrom"
},
"1.375": [-91, 7.5, 0],
"2.875": [-91, 7.5, 0],
"3.1875": {
"pre": [5, 2.5, -0.5],
"post": [5, 2.5, -0.5],
"lerp_mode": "catmullrom"
},
"3.375": {
"post": [14.5, 2.5, -17.5],
"lerp_mode": "catmullrom"
},
"3.625": [3, 0, 3.5]
},
"position": {
"0.0": [0, 0.1, 0],
"3.625": [0, 0.1, 0]
}
},
"arm_left": {
"rotation": {
"0.0": [3, 0, -3.5],
"0.9375": [3, 0, -3.5],
"1.375": [13, 0, -6],
"2.875": [13, 0, -6],
"3.375": [3, 0, -3.5]
},
"position": [0, 0.1, 0]
},
"pokeball": {
"scale": {
"0.9375": {"pre": [0, 0, 0], "post": [0.3, 0.3, 0.3]},
"1.0": {"pre": [0.3, 0.3, 0.3], "post": [0.3, 0.3, 0.3]},
"1.1875": [1, 1, 1],
"3.0625": [1, 1, 1],
"3.25": [0.4, 0.4, 0.4],
"3.375": {"pre": [0.4, 0.4, 0.4], "post": [0, 0, 0]}
}
},
"leg_right": {
"rotation": {
"0.9375": [0, 8, 0.5],
"1.375": [1, 10, 0.5],
"2.875": [1, 10, 0.5],
"3.375": [0, 8, 0.5]
},
"position": {
"0.9375": [-0.2, 0, 0],
"1.375": [-0.2, 0, -0.2],
"2.875": [-0.2, 0, -0.2],
"3.375": [-0.2, 0, 0]
}
},
"leg_left": {
"rotation": {
"0.9375": [0, -8, -0.5],
"1.375": [0, -10, -0.5],
"2.875": [0, -10, -0.5],
"3.375": [0, -8, -0.5]
},
"position": [0.2, 0, 0]
}
}
},
"animation.trainer_generic.command": {
"animation_length": 2,
"bones": {
@@ -996,6 +903,276 @@
"scale": 0
}
}
},
"animation.trainer_generic.return": {
"animation_length": 3.625,
"bones": {
"torso": {
"rotation": {
"0.0": [1, 0, 0],
"0.9375": [1, 0, 0],
"1.375": [1, -7.5, 0],
"2.875": [1, -7.5, 0],
"3.375": [1, 0, 0],
"3.625": [1, 0, 0]
}
},
"head": {
"rotation": {
"0.0": [-1, 0, 0],
"0.9375": [-1, 0, 0],
"1.375": [-1, 7.5, 0],
"2.875": [-1, 7.5, 0],
"3.375": [-1, 0, 0],
"3.625": [-1, 0, 0]
}
},
"arm_ball": {
"rotation": {
"0.0": [3, 0, 3.5],
"0.5": [3, 0, 3.5],
"0.9375": {
"pre": [14.5, 2.5, -17.5],
"post": [14.5, 2.5, -17.5],
"lerp_mode": "catmullrom"
},
"1.125": {
"post": [5, 2.5, -0.5],
"lerp_mode": "catmullrom"
},
"1.375": [-91, 7.5, 0],
"2.875": [-91, 7.5, 0],
"3.1875": {
"pre": [5, 2.5, -0.5],
"post": [5, 2.5, -0.5],
"lerp_mode": "catmullrom"
},
"3.375": {
"post": [14.5, 2.5, -17.5],
"lerp_mode": "catmullrom"
},
"3.625": [3, 0, 3.5]
},
"position": {
"0.0": [0, 0.1, 0],
"3.625": [0, 0.1, 0]
}
},
"arm_left": {
"rotation": {
"0.0": [3, 0, -3.5],
"0.9375": [3, 0, -3.5],
"1.375": [13, 0, -6],
"2.875": [13, 0, -6],
"3.375": [3, 0, -3.5]
},
"position": [0, 0.1, 0]
},
"pokeball": {
"scale": {
"0.9375": {
"pre": [0, 0, 0],
"post": [0.3, 0.3, 0.3]
},
"1.0": [0.3, 0.3, 0.3],
"1.1875": [1, 1, 1],
"3.0625": [1, 1, 1],
"3.25": [0.4, 0.4, 0.4],
"3.375": {
"pre": [0.4, 0.4, 0.4],
"post": [0, 0, 0]
}
}
},
"leg_right": {
"rotation": {
"0.9375": [0, 8, 0.5],
"1.375": [1, 10, 0.5],
"2.875": [1, 10, 0.5],
"3.375": [0, 8, 0.5]
},
"position": {
"0.9375": [-0.2, 0, 0],
"1.375": [-0.2, 0, -0.2],
"2.875": [-0.2, 0, -0.2],
"3.375": [-0.2, 0, 0]
}
},
"leg_left": {
"rotation": {
"0.9375": [0, -8, -0.5],
"1.375": [0, -10, -0.5],
"2.875": [0, -10, -0.5],
"3.375": [0, -8, -0.5]
},
"position": [0.2, 0, 0]
}
},
"timeline": {
"1.0": "q.render_item(v.actioning_pokemon_ball, 'item_right');",
"3.3125": "q.clear_items();"
}
},
"animation.trainer_generic.send_out": {
"animation_length": 2.875,
"bones": {
"torso_crouch": {
"rotation": {
"0.9167": [0, 0, 0],
"1.4167": {
"pre": [0, 10, 0],
"post": [0, 10, 0],
"lerp_mode": "catmullrom"
},
"1.5417": {
"post": [0, -10, 0],
"lerp_mode": "catmullrom"
},
"1.7917": [0, 0, 0]
}
},
"arms": {
"rotation": {
"0.9167": [0, 0, 0],
"1.4167": {
"pre": [0, 10, 0],
"post": [0, 10, 0],
"lerp_mode": "catmullrom"
},
"1.5417": {
"post": [0, -10, 0],
"lerp_mode": "catmullrom"
},
"1.7917": [0, 0, 0]
}
},
"torso_item_right": {
"rotation": [0.5, 0, 0]
},
"arms_item_right": {
"rotation": {
"0.9167": [0, 0, 0],
"1.4167": {
"pre": [0, 10, 0],
"post": [0, 10, 0],
"lerp_mode": "catmullrom"
},
"1.5417": {
"post": [0, -10, 0],
"lerp_mode": "catmullrom"
},
"1.7917": [0, 0, 0]
}
},
"arm_item_right": {
"rotation": {
"0.9167": [1, 0, 2.5],
"1.4167": {
"pre": [-85, 10, -10],
"post": [-85, 10, -10],
"lerp_mode": "catmullrom"
},
"1.5417": {
"post": [-72.5, -30, 0],
"lerp_mode": "catmullrom"
},
"1.7917": [1, 0, 2.5]
},
"position": [0, 0.1, 0]
},
"torso_item_left": {
"rotation": [0.5, 0, 0]
},
"arms_item_left": {
"rotation": {
"0.9167": [0, 0, 0],
"1.4167": {
"pre": [0, 10, 0],
"post": [0, 10, 0],
"lerp_mode": "catmullrom"
},
"1.5417": {
"post": [0, -10, 0],
"lerp_mode": "catmullrom"
},
"1.7917": [0, 0, 0]
}
},
"arm_item_left": {
"rotation": {
"0.9167": [1, 0, -2.5],
"1.4167": {
"pre": [4, 0, 0],
"post": [4, 0, 0],
"lerp_mode": "catmullrom"
},
"1.5417": {
"post": [-8, 0, 0],
"lerp_mode": "catmullrom"
},
"1.7917": [1, 0, -2.5]
},
"position": [0, 0.1, 0]
},
"torso": {
"rotation": [0.5, 0, 0]
},
"head": {
"rotation": [-0.5, 0, 0]
},
"arm_right": {
"rotation": {
"0.9167": [1, 0, 2.5],
"1.4167": {
"pre": [-85, 10, -10],
"post": [-85, 10, -10],
"lerp_mode": "catmullrom"
},
"1.5417": {
"post": [-72.5, -30, 0],
"lerp_mode": "catmullrom"
},
"1.7917": [1, 0, 2.5]
},
"position": [0, 0.1, 0]
},
"arm_left": {
"rotation": {
"0.9167": [1, 0, -2.5],
"1.4167": {
"pre": [4, 0, 0],
"post": [4, 0, 0],
"lerp_mode": "catmullrom"
},
"1.5417": {
"post": [-8, 0, 0],
"lerp_mode": "catmullrom"
},
"1.7917": [1, 0, -2.5]
},
"position": [0, 0.1, 0]
},
"leg_right": {
"rotation": [0, 2, 0.5],
"position": [-0.05, 0, 0]
},
"leg_left": {
"rotation": [0, -2, -0.5],
"position": [0.05, 0, 0]
}
},
"timeline": {
"0.0": "q.render_item(v.actioning_pokemon_ball, 'item_right');",
"1.5833": "q.render_item('air', 'item_right');"
}
}
},
"geometry.animations": {
"texturewidth": 64,
"textureheight": 64
},
"geometry.unknown": {
"texturewidth": 16,
"textureheight": 16
}
}
@@ -81,6 +81,14 @@
{"origin": [-7, 12, -2], "size": [3, 12, 4], "inflate": 0.25, "uv": [40, 32]}
]
},
{
"name": "item_right",
"parent": "arm_right",
"pivot": [-5.5, 12, -2],
"locators": {
"item_right": [-5.5, 12, -2]
}
},
{
"name": "pokeball",
"parent": "arm_ball",
@@ -99,6 +107,14 @@
{"origin": [4, 12, -2], "size": [3, 12, 4], "inflate": 0.25, "uv": [48, 48]}
]
},
{
"name": "item_left",
"parent": "arm_left",
"pivot": [5.5, 12, -2],
"locators": {
"item_left": [5.5, 12, -2]
}
},
{
"name": "leg_right",
"parent": "body",
@@ -97,6 +97,14 @@
{"origin": [-7, 12, -2], "size": [3, 11, 4], "inflate": 0.25, "uv": [40, 31]}
]
},
{
"name": "item_right",
"parent": "arm_right",
"pivot": [-5.5, 12, -2],
"locators": {
"item_right": [-5.5, 12, -2]
}
},
{
"name": "arm_left",
"parent": "torso",
@@ -106,6 +114,14 @@
{"origin": [4, 12, -2], "size": [3, 11, 4], "inflate": 0.25, "uv": [46, 48]}
]
},
{
"name": "item_left",
"parent": "arm_left",
"pivot": [5.5, 12, -2],
"locators": {
"item_left": [5.5, 12, -2]
}
},
{
"name": "leg_right",
"parent": "body",
@@ -81,6 +81,14 @@
{"origin": [-8, 12, -2], "size": [4, 12, 4], "inflate": 0.25, "uv": [40, 32]}
]
},
{
"name": "item_right",
"parent": "arm_right",
"pivot": [-6, 12, -2],
"locators": {
"item_right": [-6, 12, -2]
}
},
{
"name": "pokeball",
"parent": "arm_ball",
@@ -99,6 +107,14 @@
{"origin": [4, 12, -2], "size": [4, 12, 4], "inflate": 0.25, "uv": [48, 48]}
]
},
{
"name": "item_left",
"parent": "arm_left",
"pivot": [6, 12, -2],
"locators": {
"item_left": [6, 12, -2]
}
},
{
"name": "leg_right",
"parent": "body",
@@ -79,29 +79,18 @@
"cubes": [
{"origin": [-8, 12, -2], "size": [4, 12, 4], "uv": [40, 16]},
{"origin": [-8, 12, -2], "size": [4, 12, 4], "inflate": 0.25, "uv": [40, 32]}
]
},
{
"name": "pokeball",
"parent": "arm_ball",
"pivot": [-6, 12, 0],
"cubes": [
{"origin": [-8, 8, -2], "size": [4, 2, 4], "pivot": [-6, 10, 0], "rotation": [90, 0, 0], "uv": [0, 122]}
],
"locators": {
"beam": [-6, 8, 0]
}
},
{
"name": "pokeball_lid",
"parent": "pokeball",
"pivot": [-6, 10, 0],
"rotation": [90, 0, 0],
"cubes": [
{"origin": [-6.5, 9.5, -2.02], "size": [1, 1, 0], "uv": [0, 123]},
{"origin": [-7, 9, -2.01], "size": [2, 2, 0], "uv": [0, 124]},
{"origin": [-8, 10, -2], "size": [4, 2, 4], "uv": [0, 116]}
]
"name": "item_right",
"parent": "arm_right",
"pivot": [-6, 12, -2],
"locators": {
"item_right": [-6, 12, -2]
}
},
{
"name": "arm_left",
@@ -112,6 +101,14 @@
{"origin": [4, 12, -2], "size": [4, 12, 4], "inflate": 0.25, "uv": [48, 48]}
]
},
{
"name": "item_left",
"parent": "arm_left",
"pivot": [6, 12, -2],
"locators": {
"item_left": [6, 12, -2]
}
},
{
"name": "leg_right",
"parent": "body",
@@ -3,11 +3,16 @@
"portraitTranslation": [0.0, 1.1, 0.0],
"profileScale": 1.0,
"profileTranslation": [0.0, -0.5, 0.0],
"properties": {
"item_display_context": "THIRD_PERSON_RIGHT_HAND",
"item_right_display_context": "THIRD_PERSON_RIGHT_HAND",
"item_left_display_context": "THIRD_PERSON_LEFT_HAND"
},
"animations": {
"win": "q.bedrock_primary('trainer_generic', 'win', q.curve('symmetrical_wide'))",
"lose": "q.bedrock_primary('trainer_generic', 'lose', q.curve('symmetrical_wide'))",
"send_out": "q.bedrock_primary('trainer_generic', 'send_out', q.curve('symmetrical'), q.exclude_labels('look'))",
"recall": "q.bedrock_primary('trainer_generic', 'send_out', q.curve('symmetrical_wide'), q.exclude_labels('look'))",
"recall": "q.bedrock_primary('trainer_generic', 'return', q.curve('symmetrical_wide'), q.exclude_labels('look'))",
"punch_left": "q.primary_animation(q.punch('head', 'body', 'arm_left', 'arm_right', false), q.curve('symmetrical_wide'), q.exclude_labels('look'))",
"punch_right": "q.primary_animation(q.punch('head', 'body', 'arm_right', 'arm_left', true), q.curve('symmetrical_wide'), q.exclude_labels('look'))"
},
@@ -14543,7 +14543,7 @@
"rotation": [0, -6, -15]
},
"wing_right_elbow": {
"rotation": [0, -6, -15],
"rotation": [0, "-6", -15],
"position": [0.45, 0.1, 0]
},
"wing_right_membrane": {
@@ -7,8 +7,8 @@
"texture_width": 256,
"texture_height": 128,
"visible_bounds_width": 10,
"visible_bounds_height": 5,
"visible_bounds_offset": [0, 1.5, 0]
"visible_bounds_height": 7,
"visible_bounds_offset": [0, 2.5, 0]
},
"bones": [
{
@@ -20,7 +20,7 @@
"parent": "charizard",
"pivot": [0, 0, -1],
"locators": {
"root": [0, 0, -1]
"root": [0, 0, 0]
}
},
{
@@ -28,7 +28,7 @@
"parent": "charizard",
"pivot": [0, 41, -1],
"locators": {
"top": [0, 41, -1]
"top": [0, 41, 0]
}
},
{
@@ -116,8 +116,7 @@
{
"name": "head_correction",
"parent": "head",
"pivot": [0, 43.5, -0.5],
"rotation": [-95, 0, 0]
"pivot": [0, 43.5, -0.5]
},
{
"name": "head_ai",
@@ -128,15 +127,20 @@
"name": "head_angle",
"parent": "head_ai",
"pivot": [0, 43.5, -0.5],
"rotation": [95, 0, 0],
"cubes": [
{"origin": [-3.5, 41.5, -2], "size": [7, 7, 5], "uv": [48, 0]},
{"origin": [-3.5, 41.5, -4], "size": [7, 4, 2], "uv": [95, 64]},
{"origin": [-2.5, 48, -2], "size": [5, 5, 2], "inflate": 0.02, "uv": [37, 31]},
{"origin": [-2, 51.48744, -3.03033], "size": [0, 1, 1], "inflate": 0.01, "pivot": [-2, 52.51777, -2], "rotation": [45, 0, 15], "uv": [43, 42]},
{"origin": [2, 51.48744, -3.03033], "size": [0, 1, 1], "inflate": 0.01, "pivot": [2, 52.51777, -2], "rotation": [45, 0, -15], "uv": [41, 42]}
{"origin": [-3.5, 41.5, -4], "size": [7, 4, 2], "uv": [95, 64]}
]
},
{
"name": "item_hat",
"parent": "head_angle",
"pivot": [0, 45, 3],
"rotation": [-90, 0, 0],
"locators": {
"item_hat": [0, 45, 3]
}
},
{
"name": "locator_head",
"parent": "head_angle",
@@ -146,59 +150,51 @@
"head": [0, 46.5, 3]
}
},
{
"name": "locator_mouth",
"parent": "head_angle",
"pivot": [0, 48.75, -3],
"rotation": [-90, 0, 0],
"locators": {
"mouth": [0, 48.75, -3]
}
},
{
"name": "locator_special_attack",
"parent": "head_angle",
"pivot": [0, 48.75, -3],
"rotation": [-90, 0, 0],
"locators": {
"special_attack": [0, 48.75, -3]
}
},
{
"name": "locator_jaw_upper",
"parent": "head_angle",
"pivot": [0, 51.75, -2.25],
"rotation": [-90, 0, 0],
"locators": {
"jaw1": [0, 51.75, -2.25]
}
},
{
"name": "brow",
"parent": "head_angle",
"pivot": [0, 48.275, 1.765],
"cubes": [
{"origin": [-2.5, 48.55, 1], "size": [5, 0, 1], "uv": [0, 14]},
{"origin": [-3.5, 47.5, 1.53], "size": [7, 1, 1], "inflate": 0.04, "uv": [0, 12]}
]
},
{
"name": "eye",
"parent": "head_angle",
"pivot": [0, -3.5, 1.5]
},
{
"name": "locator_face",
"parent": "eye",
"parent": "head_angle",
"pivot": [0, 53.75, -0.25],
"rotation": [-90, 0, 0],
"locators": {
"face": [0, 53.75, -0.25]
}
},
{
"name": "locator_item_face",
"parent": "head_angle",
"pivot": [0, 48.5, 0.5],
"rotation": [-90, 0, 0],
"locators": {
"item_face": [0, 48.5, 0.5]
}
},
{
"name": "locator_mouth",
"parent": "head_angle",
"pivot": [0, 45.75, -2],
"rotation": [-90, 0, 0],
"locators": {
"mouth": [0, 45.75, -2],
"special": [0, 45.75, -2]
}
},
{
"name": "eyes",
"parent": "head_angle",
"pivot": [0, -3.5, 1.5]
},
{
"name": "brow",
"parent": "eyes",
"pivot": [0, 48.275, 1.765],
"cubes": [
{"origin": [-2.5, 48.55, 1], "size": [5, 0, 1], "uv": [0, 14]},
{"origin": [-3.5, 47.5, 1.53], "size": [7, 1, 1], "inflate": 0.04, "uv": [0, 12]}
]
},
{
"name": "eye_right",
"parent": "eye",
"parent": "eyes",
"pivot": [-2.75, 47.75, 0.875],
"cubes": [
{"origin": [-3.5, 47.5, 0], "size": [2, 1, 2], "inflate": 0.01, "uv": [106, 36]},
@@ -226,7 +222,7 @@
},
{
"name": "eye_left",
"parent": "eye",
"parent": "eyes",
"pivot": [2.75, 47.75, 0.875],
"cubes": [
{"origin": [1.5, 47.5, 0], "size": [2, 1, 2], "inflate": 0.01, "uv": [44, 82]},
@@ -255,6 +251,91 @@
{"origin": [2.41, 46.4, 0.5], "size": [1, 1, 1], "inflate": 0.03, "uv": [39, 86]}
]
},
{
"name": "muzzle",
"parent": "head_angle",
"pivot": [-3, 48.5, -2],
"cubes": [
{"origin": [-2.5, 48, -2], "size": [5, 5, 2], "inflate": 0.02, "uv": [37, 31]},
{"origin": [-2, 51.48744, -3.03033], "size": [0, 1, 1], "inflate": 0.01, "pivot": [-2, 52.51777, -2], "rotation": [45, 0, 15], "uv": [43, 42]},
{"origin": [2, 51.48744, -3.03033], "size": [0, 1, 1], "inflate": 0.01, "pivot": [2, 52.51777, -2], "rotation": [45, 0, -15], "uv": [41, 42]}
]
},
{
"name": "locator_jaw_upper",
"parent": "muzzle",
"pivot": [0, 51.75, -2.25],
"rotation": [-90, 0, 0],
"locators": {
"jaw1": [0, 51.75, -2.25]
}
},
{
"name": "nose",
"parent": "muzzle",
"pivot": [0, 53, 0],
"rotation": [-40, 0, 0],
"cubes": [
{"origin": [-2.5, 51.36698, -1.67861], "size": [5, 2, 2], "inflate": 0.03, "uv": [48, 12]},
{"origin": [-2, 53.40698, -1.17861], "size": [4, 0, 1], "uv": [0, 15]}
]
},
{
"name": "jaw",
"parent": "head_angle",
"pivot": [0, 45.5, -2],
"cubes": [
{"origin": [-3.5, 45.5, -4], "size": [7, 3, 2], "inflate": 0.01, "uv": [76, 20]}
]
},
{
"name": "jaw2",
"parent": "jaw",
"pivot": [0, 48.5, -4],
"rotation": [-7.5, 0, 0],
"cubes": [
{"origin": [-2, 48.49, -4], "size": [4, 4, 2], "uv": [0, 51]},
{"origin": [-1.5, 48.5, -2], "size": [3, 4, 0], "uv": [90, 0]},
{"origin": [1.5, 51.49749, -3.03033], "size": [0, 1, 1], "pivot": [1.5, 52.52782, -2], "rotation": [45, 0, -7.5], "uv": [37, 39]},
{"origin": [-1.5, 51.49749, -3.03033], "size": [0, 1, 1], "pivot": [-1.5, 52.52782, -2], "rotation": [45, 0, 7.5], "uv": [37, 38]}
]
},
{
"name": "locator_jaw_lower",
"parent": "jaw2",
"pivot": [0, 51.75, -1.75],
"locators": {
"jaw2": {
"offset": [0, 51.75, -1.75],
"rotation": [-90, 0, 0]
}
}
},
{
"name": "tongue",
"parent": "jaw",
"pivot": [0, 45.5, -2],
"cubes": [
{"origin": [-1.5, 45.5, -2], "size": [3, 3, 0], "uv": [87, 29]}
]
},
{
"name": "tongue2",
"parent": "tongue",
"pivot": [0, 48.5, -2],
"cubes": [
{"origin": [-1, 48.5, -2], "size": [2, 2, 0], "inflate": 0.01, "uv": [46, 38]}
]
},
{
"name": "tongue3",
"parent": "tongue2",
"pivot": [0, 50.5, -2],
"cubes": [
{"origin": [-1, 50.5, -2], "size": [2, 1, 0], "uv": [37, 43]},
{"origin": [-0.5, 51.5, -2], "size": [1, 1, 0], "uv": [45, 43]}
]
},
{
"name": "horns",
"parent": "head_angle",
@@ -300,72 +381,6 @@
"horn2": [2.5, 39, 2]
}
},
{
"name": "nose",
"parent": "head_angle",
"pivot": [0, 53, 0],
"rotation": [-40, 0, 0],
"cubes": [
{"origin": [-2.5, 51.36698, -1.67861], "size": [5, 2, 2], "inflate": 0.03, "uv": [48, 12]},
{"origin": [-2, 53.40698, -1.17861], "size": [4, 0, 1], "uv": [0, 15]}
]
},
{
"name": "jaw",
"parent": "head_angle",
"pivot": [0, 45.5, -2],
"cubes": [
{"origin": [-3.5, 45.5, -4], "size": [7, 3, 2], "inflate": 0.01, "uv": [76, 20]}
]
},
{
"name": "jaw2",
"parent": "jaw",
"pivot": [0, 48.5, -4],
"rotation": [-7.5, 0, 0],
"cubes": [
{"origin": [-2, 48.49, -4], "size": [4, 4, 2], "uv": [0, 51]},
{"origin": [-1.5, 48.5, -1.99], "size": [3, 4, 0], "uv": [90, 0]},
{"origin": [1.5, 51.49749, -3.03033], "size": [0, 1, 1], "pivot": [1.5, 52.52782, -2], "rotation": [45, 0, -7.5], "uv": [37, 39]},
{"origin": [-1.5, 51.49749, -3.03033], "size": [0, 1, 1], "pivot": [-1.5, 52.52782, -2], "rotation": [45, 0, 7.5], "uv": [37, 38]}
]
},
{
"name": "locator_jaw_lower",
"parent": "jaw2",
"pivot": [0, 51.75, -1.75],
"locators": {
"jaw2": {
"offset": [0, 51.75, -1.75],
"rotation": [-90, 0, 0]
}
}
},
{
"name": "tongue",
"parent": "jaw",
"pivot": [0, 45.5, -2],
"cubes": [
{"origin": [-1.5, 45.5, -2], "size": [3, 3, 0], "uv": [87, 29]}
]
},
{
"name": "tongue2",
"parent": "tongue",
"pivot": [0, 48.5, -2],
"cubes": [
{"origin": [-1, 48.5, -2], "size": [2, 2, 0], "inflate": 0.01, "uv": [46, 38]}
]
},
{
"name": "tongue3",
"parent": "tongue2",
"pivot": [0, 50.5, -2],
"cubes": [
{"origin": [-1, 50.5, -2], "size": [2, 1, 0], "uv": [37, 43]},
{"origin": [-0.5, 51.5, -2], "size": [1, 1, 0], "uv": [45, 43]}
]
},
{
"name": "arms",
"parent": "torso2",
@@ -403,6 +418,14 @@
{"origin": [-21.5, 22, -1.5], "size": [3, 2, 3], "inflate": 0.1, "uv": [0, 58]}
]
},
{
"name": "locator_item",
"parent": "hand_right",
"pivot": [-21.6, 23, 0],
"locators": {
"item": [-21.6, 23, 0]
}
},
{
"name": "locator_hand_primary",
"parent": "hand_right",
@@ -913,6 +936,14 @@
{"origin": [0, 9.5, 58.5], "size": [0, 13, 7], "pivot": [0, 14.45, 62], "rotation": [0, 90, 0], "uv": [0, 92]}
]
},
{
"name": "locator_fire",
"parent": "fire2",
"pivot": [0, 11, 62],
"locators": {
"fire": [0, 11, 62]
}
},
{
"name": "legs",
"parent": "body",
@@ -945,9 +976,10 @@
{
"name": "locator_foot_primary",
"parent": "foot_right",
"pivot": [-9, 1.5, -7],
"pivot": [-9, 1.5, -5],
"rotation": [90, 0, 0],
"locators": {
"foot_primary": [-9, 1.5, -7]
"foot_primary": [-9, 1.5, -5]
}
},
{
@@ -1009,9 +1041,10 @@
{
"name": "locator_foot_secondary",
"parent": "foot_left",
"pivot": [9, 1.5, -7],
"pivot": [9, 1.5, -5],
"rotation": [90, 0, 0],
"locators": {
"foot_secondary": [9, 1.5, -7]
"foot_secondary": [9, 1.5, -5]
}
},
{
@@ -34,7 +34,10 @@
"pivot": [0, 26.2, 0],
"cubes": [
{"origin": [-5, 25.2, -4], "size": [10, 7, 8], "inflate": 0.01, "uv": [26, 32]}
]
],
"locators": {
"seat_1": [0, 28, 4]
}
},
{
"name": "fin",
@@ -26,7 +26,10 @@
"pivot": [0, 24.5, -1],
"cubes": [
{"origin": [-6, 18.5, -13.75], "size": [12, 12, 26], "uv": [52, 39]}
]
],
"locators": {
"seat_1": [0, 30, 0]
}
},
{
"name": "neck",
@@ -86,12 +86,36 @@
"fly": {
"poseTypes": ["FLY"],
"isBattle": false,
"animations": ["q.look('head_ai')", "q.bedrock('charizard', 'air_fly')"],
"condition": "!q.riding_alt_pose('GLIDING')",
"animations": [
"q.look('head_ai')",
"q.bedrock('charizard', 'air_fly')",
{
"condition": "q.has_passenger",
"animation": "q.bedrock('charizard', 'riding_look')"
}
],
"quirks": [
"q.bedrock_quirk('charizard', 'blink')",
"q.bedrock_quirk('charizard', 'air_fly_quirk')"
]
},
"glide": {
"poseTypes": ["FLY"],
"isBattle": false,
"condition": "q.riding_alt_pose('GLIDING')",
"animations": [
"q.look('head_ai')",
"q.bedrock('charizard', 'air_glide_TEST')",
{
"condition": "q.has_passenger",
"animation": "q.bedrock('charizard', 'riding_look')"
}
],
"quirks": [
"q.bedrock_quirk('charizard', 'blink')"
]
},
"surface_idle": {
"poseTypes": ["STAND", "FLOAT"],
"isTouchingWater": true,
@@ -0,0 +1,60 @@
{
"portraitScale": 1.5,
"portraitTranslation": [-1.0, 1.76, 0.0],
"profileScale": 0.5,
"profileTranslation": [0.0, 1.0, 0.0],
"rootBone": "mudsdale",
"animations": {
"cry": "q.bedrock_stateful('mudsdale', 'cry')"
},
"poses": {
"portrait": {
"poseTypes": ["PORTRAIT", "PROFILE"],
"isBattle": false,
"isTouchingWater": false,
"animations": [
"q.look('head_ai')",
"q.bedrock('mudsdale', 'ground_idle')"
],
"quirks": ["q.bedrock_quirk('mudsdale', 'blink')"]
},
"standing": {
"poseTypes": ["STAND", "NONE"],
"isBattle": false,
"isTouchingWater": false,
"animations": [
"q.look('head_ai')",
"q.bedrock('mudsdale', 'ground_idle')"
],
"quirks": ["q.bedrock_quirk('mudsdale', 'blink')"]
},
"walking": {
"poseTypes": ["WALK"],
"isTouchingWater": false,
"condition": "!q.riding_alt_pose('SPRINTING')",
"animations": [
"q.look('head_ai')",
"q.bedrock('mudsdale', 'ground_walk')"
],
"quirks": ["q.bedrock_quirk('mudsdale', 'blink')"]
},
"running": {
"poseTypes": ["WALK"],
"isTouchingWater": false,
"condition": "q.riding_alt_pose('SPRINTING')",
"animations": [
"q.look('head_ai')",
"q.bedrock('mudsdale', 'ground_run')"
],
"quirks": ["q.bedrock_quirk('mudsdale', 'blink')"]
},
"sleep": {
"poseTypes": ["SLEEP"],
"animations": ["q.bedrock('mudsdale', 'sleep')"],
"quirks": ["q.bedrock_quirk('mudsdale', 'blink')"],
"namedAnimations": {
"cry": "q.bedrock_stateful('dummy', 'cry')"
}
}
}
}
@@ -1,4 +1,4 @@
{
{
"species": "cobblemon:garchomp",
"order": 1,
"variations": [
@@ -97,12 +97,12 @@
"party": {
"type": "simple",
"pokemon": [
"spiritomb level=100 held_item=cobblemon:sitrus_berry ability=pressure nature=jolly moves=darkpulse,shadowball,willowisp,suckerpunch hp_iv=31 attack_iv=31 defence_iv=31 special_attack_iv=31 special_defence_iv=31 speed_iv=31 hp_ev=252 special_attack_ev=252 speed_ev=6",
"porygonz level=100 held_item=cobblemon:life_orb ability=adaptability nature=timid moves=hyperbeam,shadowball,icebeam,thunderbolt hp_iv=31 attack_iv=31 defence_iv=31 special_attack_iv=31 special_defence_iv=31 speed_iv=31 hp_ev=6 special_attack_ev=252 speed_ev=252",
"togekiss level=100 held_item=cobblemon:leftovers ability=serenegrace nature=timid moves=airslash,dazzlinggleam,aurasphere,thunderwave hp_iv=31 attack_iv=31 defence_iv=31 special_attack_iv=31 special_defence_iv=31 speed_iv=31 hp_ev=6 special_attack_ev=252 speed_ev=252",
"lucario level=100 held_item=cobblemon:focus_sash ability=innerfocus nature=jolly moves=closecombat,meteormash,earthquake,extremespeed hp_iv=31 attack_iv=31 defence_iv=31 special_attack_iv=31 special_defence_iv=31 speed_iv=31 hp_ev=6 attack_ev=252 speed_ev=252",
"milotic level=100 held_item=cobblemon:flame_orb ability=marvelscale nature=bold moves=recover,mirrorcoat,icebeam,scald hp_iv=31 attack_iv=31 defence_iv=31 special_attack_iv=31 special_defence_iv=31 speed_iv=31 hp_ev=252 defence_ev=252 special_attack_ev=6",
"garchomp level=100 held_item=cobblemon:yache_berry ability=roughskin nature=jolly moves=dragonclaw,earthquake,swordsdance,poisonjab hp_iv=31 attack_iv=31 defence_iv=31 special_attack_iv=31 special_defence_iv=31 speed_iv=31 hp_ev=6 attack_ev=252 speed_ev=252"
"spiritomb level=100 pokeball=master_ball held_item=cobblemon:sitrus_berry ability=pressure nature=jolly moves=darkpulse,shadowball,willowisp,suckerpunch hp_iv=31 attack_iv=31 defence_iv=31 special_attack_iv=31 special_defence_iv=31 speed_iv=31 hp_ev=252 special_attack_ev=252 speed_ev=6",
"porygonz level=100 pokeball=great_ball held_item=cobblemon:life_orb ability=adaptability nature=timid moves=hyperbeam,shadowball,icebeam,thunderbolt hp_iv=31 attack_iv=31 defence_iv=31 special_attack_iv=31 special_defence_iv=31 speed_iv=31 hp_ev=6 special_attack_ev=252 speed_ev=252",
"togekiss level=100 pokeball=ultra_ball held_item=cobblemon:leftovers ability=serenegrace nature=timid moves=airslash,dazzlinggleam,aurasphere,thunderwave hp_iv=31 attack_iv=31 defence_iv=31 special_attack_iv=31 special_defence_iv=31 speed_iv=31 hp_ev=6 special_attack_ev=252 speed_ev=252",
"lucario level=100 pokeball=dive_ball held_item=cobblemon:focus_sash ability=innerfocus nature=jolly moves=closecombat,meteormash,earthquake,extremespeed hp_iv=31 attack_iv=31 defence_iv=31 special_attack_iv=31 special_defence_iv=31 speed_iv=31 hp_ev=6 attack_ev=252 speed_ev=252",
"milotic level=100 pokeball=love_ball held_item=cobblemon:flame_orb ability=marvelscale nature=bold moves=recover,mirrorcoat,icebeam,scald hp_iv=31 attack_iv=31 defence_iv=31 special_attack_iv=31 special_defence_iv=31 speed_iv=31 hp_ev=252 defence_ev=252 special_attack_ev=6",
"garchomp level=100 pokeball=quick_ball held_item=cobblemon:yache_berry ability=roughskin nature=jolly moves=dragonclaw,earthquake,swordsdance,poisonjab hp_iv=31 attack_iv=31 defence_iv=31 special_attack_iv=31 special_defence_iv=31 speed_iv=31 hp_ev=6 attack_ev=252 speed_ev=252"
]
}
}
@@ -317,26 +317,5 @@
"tutor:waterpulse"
],
"preEvolution": "dragonair",
"evolutions": [],
"riding": {
"stats": {
"SPEED": {
"ranges": "30-60"
},
"ACCELERATION": {
"ranges": "30-65"
},
"SKILL": {
"ranges": "30-60"
},
"JUMP": {
"ranges": "30-60"
},
"STAMINA": {
"ranges": "35-75"
}
},
"seats": [],
"controller": null
}
"evolutions": []
}
@@ -59,6 +59,64 @@
}
}
},
"riding": {
"stats": {
"SPEED": {
"ranges": {
"LAND": "100-101",
"LIQUID": "5-20"
}
},
"ACCELERATION": {
"ranges": {
"LAND": "100-101",
"LIQUID": "50-80"
}
},
"SKILL": {
"ranges": {
"LAND": "100-101",
"LIQUID": "20-40"
}
},
"JUMP": {
"ranges": {
"LAND": "100-101",
"LIQUID": "5-10"
}
},
"STAMINA": {
"ranges": {
"LAND": "100-101",
"LIQUID": "25-65"
}
}
},
"seats": [
{
"offset": {
"x": 0,
"y": 0.4,
"z": 0
},
"poseOffsets": [
{
"poseTypes": [
"WALK"
],
"offset": {
"x": 0,
"y": 0.0,
"z": 0
}
}
]
}
],
"controller": {
"key": "cobblemon:land/generic"
}
},
"drops": {
"amount": 6,
"entries": [