Compare commits

...
26 changed files with 2596 additions and 206 deletions
@@ -8,6 +8,8 @@
package com.cobblemon.mod.common;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix3f;
import org.joml.Vector3f;
@@ -117,8 +119,17 @@ public interface Rollable {
return (float) Math.toDegrees(upVector.angleSigned(UP, forwardVector));
}
/**
* Get the angular velocity in degrees per second as a Vec3
*/
Vec3 angRollVel();
public void rotationOnMouseXY( double yMouse, double xMouse );
boolean shouldRoll();
boolean useMouseInputRoll();
void clearRotation();
}
@@ -12,9 +12,15 @@ import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffectRegistry;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.Vec3;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LivingEntity.class)
@@ -28,4 +34,70 @@ public abstract class LivingEntityMixin {
}
}
/*
@Inject(method = "travelRidden", at = @At(value = "HEAD"))
private void cobblemon$altTravelRidden(Player player, Vec3 travelVector)
{
this.setSpeed(this.getRiddenSpeed(player));
this.travel(vec3);
}
/*
// Shadow the original method so we can call it.
@Shadow
protected abstract boolean isControlledByLocalInstance();
/**
* Redirect the call to isControlledByLocalInstance() within travelRidden.
* When travelRidden calls isControlledByLocalInstance(), it will get our modified result.
*/
/*
@Redirect(
method = "travelRidden(Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/world/phys/Vec3;)V",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/entity/LivingEntity;isControlledByLocalInstance()Z"
)
)
private boolean redirectIsControlledByLocalInstance(LivingEntity instance) {
// Return true if either the original method returns true or our custom condition is true.
return this.isControlledByLocalInstance() || this.b;
}
@Overwrite
private void travelRidden(Player player, Vec3 travelVector) {
Vec3 vec3 = this.getRiddenInput(player, travelVector);
this.tickRidden(player, vec3);
if (this.isControlledByLocalInstance()) {
this.setSpeed(this.getRiddenSpeed(player));
this.travel(vec3);
} else {
this.calculateEntityAnimation(false);
this.setDeltaMovement(Vec3.ZERO);
this.tryCheckInsideBlocks();
}
}
@ModifyArg(
method = "travelRidden",
at = @At(
value = "INVOKE",
target = "setDeltaMovement(Lnet/minecraft/world/phys/Vec3;)V"
),
index = 0 // the first (and only) argument
)
private Vec3 modifyDeltaMovementArgument(Vec3 original) {
// Optionally, check if original is Vec3.ZERO, to only replace that call:
if (original.equals(Vec3.ZERO)) {
// Return your custom vector instead of Vec3.ZERO
return original;
}
// Otherwise, return the original value
return original;
}
*/
}
@@ -33,8 +33,11 @@ public class MouseHandlerMixin {
@Shadow private double accumulatedScrollY;
@Shadow @Final private Minecraft minecraft;
@Unique SmoothDouble xMouseSmoother = new SmoothDouble();
@Unique SmoothDouble yMouseSmoother = new SmoothDouble();
@Unique SmoothDouble pitchSmoother = new SmoothDouble();
@Unique SmoothDouble rollSmoother = new SmoothDouble();
@Unique SmoothDouble yawSmoother = new SmoothDouble();
@Shadow @Final private SmoothDouble smoothTurnY;
@@ -109,16 +112,48 @@ public class MouseHandlerMixin {
}
if (!(player instanceof Rollable rollable)) return true;
if (!rollable.shouldRoll()) {
xMouseSmoother.reset();
yMouseSmoother.reset();
pitchSmoother.reset();
rollSmoother.reset();
yawSmoother.reset();
return true;
}
var pitch = pitchSmoother.getNewDeltaValue(cursorDeltaY * 0.15f, d);
var roll = rollSmoother.getNewDeltaValue(cursorDeltaX * 0.15f, d);
rollable.rotate(0.0F, (float)pitch, (float)roll);
//maybe have a rollable x mouse and rollable y mouse that take the xmousesmoother
//and y mouse smoother ( which are right now named pichsmoother and roll smoother)
// values and feed it to those rollable functions which will be called by the
//specific ride controller where rollable.rotate can then be called and used
//controller specific.
var yMouse = yMouseSmoother.getNewDeltaValue(cursorDeltaY * 0.15f, d);
var xMouse = xMouseSmoother.getNewDeltaValue(cursorDeltaX * 0.15f, d);
rollable.rotationOnMouseXY( yMouse, xMouse );
//get the current angular velocity requested to be applied
//by the ride controller
var angRot = rollable.angRollVel();
//Multiply against deltatime to get a consistent turn
//independent of framerate
if( angRot != null ) {
//var yaw = angRot.x * d;
//Add the yaw to the mouse here... this all needs changed. It should
//just map which 0 1 or 2 of the three it is going to modulate with
//what
var yaw = yawSmoother.getNewDeltaValue(angRot.x * 0.5f, d);
//yaw += yawSmoother.getNewDeltaValue(cursorDeltaX * 0.15f, d);
//var pitch = angRot.y * d;
var pitch = pitchSmoother.getNewDeltaValue(angRot.y * 0.5f, d);
//pitch += pitchSmoother.getNewDeltaValue(cursorDeltaY * 0.15f, d);
//var roll = angRot.z * d;
var roll = rollSmoother.getNewDeltaValue(angRot.z * 0.5f, d);
rollable.rotate((float) yaw, (float) pitch, (float) roll);
}
return false;
}
@Inject(method = "handleAccumulatedMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MouseHandler;isMouseGrabbed()Z", ordinal = 0))
@@ -47,6 +47,7 @@ import net.minecraft.world.food.FoodProperties;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix3f;
import org.objectweb.asm.Opcodes;
@@ -55,6 +56,7 @@ import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@@ -272,6 +274,7 @@ public abstract class PlayerMixin extends LivingEntity implements Rollable, Scan
@Override
public Rollable updateOrientation(Function<Matrix3f, Matrix3f> update) {
//Disabled to allow non mouse inputs to modify roll
if (!shouldRoll()) return this;
if (this.orientation == null) {
@@ -299,6 +302,45 @@ public abstract class PlayerMixin extends LivingEntity implements Rollable, Scan
return pokemonEntity.getRiding().shouldRoll(pokemonEntity);
}
@Override
public void rotationOnMouseXY( double yMouse, double xMouse ) {
LocalPlayer player = Minecraft.getInstance().player;
if (!this.equals(player)) return;
var playerVehicle = player.getVehicle();
if (playerVehicle == null) return;
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return;
pokemonEntity.getRiding().rotationOnMouseXY(pokemonEntity, player, yMouse, xMouse);
}
@Override
public boolean useMouseInputRoll() {
LocalPlayer player = Minecraft.getInstance().player;
if (!this.equals(player)) return true;
if (player.isHolding(Items.DIAMOND)) return false;
var playerVehicle = player.getVehicle();
if (playerVehicle == null) return true;
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return true;
return pokemonEntity.getRiding().useMouseInputRoll(pokemonEntity);
}
@Override
public Vec3 angRollVel() {
var ret = new Vec3(0.0, 0.0, 0.0);
LocalPlayer player = Minecraft.getInstance().player;
if (!this.equals(player)) return ret;
var playerVehicle = player.getVehicle();
if (playerVehicle == null) return ret;
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return ret;
return pokemonEntity.getRiding().angRollVel(pokemonEntity, player);
}
@Override
public void clearRotation() {
this.orientation = null;
@@ -10,15 +10,19 @@ package com.cobblemon.mod.common.api.riding
import com.bedrockk.molang.runtime.MoLangRuntime
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.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.withQueryValue
import net.minecraft.ChatFormatting
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
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 kotlin.math.sin
data class RidingManager(val entity: PokemonEntity) {
var lastSpeed = 0F
@@ -58,8 +62,11 @@ 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.horizontalDistance()
driver.displayClientMessage(Component.literal("Speed Entity: ").withStyle { it.withColor(ChatFormatting.GREEN) }.append(Component.literal("${String.format("%.2f", speedEntity)} b/t")), true)
// driver.displayClientMessage(Component.literal("Speed: ").withStyle { it.withColor(ChatFormatting.GREEN) }.append(Component.literal("$lastSpeed b/t")), true)
}
fun speed(entity: PokemonEntity, driver: Player): Float {
@@ -111,8 +118,32 @@ data class RidingManager(val entity: PokemonEntity) {
return controller.shouldRoll(entity)
}
//FIXME: Make this not just be false for all controllers lol
fun rotationOnMouseXY( entity: PokemonEntity, driver: Player, yMouse: Double, xMouse: Double ){
val controller = getController(entity) ?: return
controller.rotationOnMouseXY( entity, driver, yMouse, xMouse )
}
/**
* Determines whether mouse input will affect the rotation of
* the rollable object
*/
fun useMouseInputRoll(entity: PokemonEntity): Boolean {
val controller = getController(entity) ?: return true
return controller.useMouseInputRoll(entity)
}
fun turnOffOnGround(entity: PokemonEntity): Boolean {
val controller = getController(entity) ?: return false
return controller.turnOffOnGround(entity)
}
fun angRollVel(entity: PokemonEntity, driver: Player): Vec3 {
val controller = getController(entity) ?: return Vec3(0.0, 0.0, 0.0)
return controller.angRollVel(entity, driver)
}
fun dismountOnShift(): Boolean {
return false
val controller = getController(entity) ?: return false
return controller.dismountOnShift(entity)
}
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.riding.controller
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
@@ -71,6 +72,14 @@ interface RideController : Encodable, Decodable {
*/
fun rotation(entity: PokemonEntity, driver: LivingEntity) : Vec2
/**
* Sets the change current change in rotation for that tick in three
* dimensions
*/
fun angRollVel(entity: PokemonEntity, driver: Player): Vec3 {
return Vec3(0.0, 0.0, 0.0)
}
fun clampPassengerRotation(entity: PokemonEntity, driver: LivingEntity) {}
fun updatePassengerRotation(entity: PokemonEntity, driver: LivingEntity) {
@@ -91,6 +100,21 @@ interface RideController : Encodable, Decodable {
fun shouldRoll(entity: PokemonEntity): Boolean = false
fun useMouseInputRoll(entity: PokemonEntity): Boolean = false
//If function is not overwritten by controllers then just perform the defualt
//rolling function
fun rotationOnMouseXY( entity: PokemonEntity, driver: Player, yMouse: Double, xMouse: Double ){
if(driver !is Rollable) return
val rollable = driver as Rollable
rollable.rotate(0.0f, yMouse.toFloat(), xMouse.toFloat())
}
fun turnOffOnGround(entity: PokemonEntity): Boolean = false
fun dismountOnShift(entity: PokemonEntity): Boolean = false
fun getRuntime(entity: PokemonEntity) = entity.riding.runtime
fun <T : RidingState> getState(entity: PokemonEntity, constructor: (PokemonEntity) -> T): T {
@@ -1,49 +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.gen3
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.CobblemonPose
import com.cobblemon.mod.common.entity.PoseType
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class WhiscashModel (root: ModelPart) : PokemonPosableModel(root) {
override val rootPart = root.registerChildWithAllChildren("whiscash")
override var portraitScale = 0.8F
override var portraitTranslation = Vec3(-0.35, 0.4, 0.0)
override var profileScale = 0.6F
override var profileTranslation = Vec3(-0.1, 0.6, 0.0)
lateinit var standing: CobblemonPose
lateinit var walk: CobblemonPose
override val cryAnimation = CryProvider { bedrockStateful("whiscash", "cry") }
override fun registerPoses() {
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.STATIONARY_POSES + PoseType.UI_POSES,
animations = arrayOf(
bedrock("whiscash", "water_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = PoseType.MOVING_POSES,
animations = arrayOf(
bedrock("whiscash", "water_idle")
)
)
}
}
@@ -349,7 +349,6 @@ object PokemonModelRepository : VaryingModelRepository<PokemonPosableModel>() {
inbuilt("spewpa", ::SpewpaModel)
inbuilt("vivillon", ::VivillonModel)
inbuilt("barboach", ::BarboachModel)
inbuilt("whiscash", ::WhiscashModel)
inbuilt("combee", ::CombeeModel)
inbuilt("vespiquen", ::VespiquenModel)
inbuilt("lillipup", ::LillipupModel)
@@ -87,6 +87,7 @@ object MountedPlayerRenderer {
state.runtime.environment.setSimpleVariable("age_in_ticks", DoubleValue(ageInTicks.toDouble()))
state.runtime.environment.setSimpleVariable("limb_swing", DoubleValue(limbSwing.toDouble()))
state.runtime.environment.setSimpleVariable("limb_swing_amount", DoubleValue(limbSwingAmount.toDouble()))
state.runtime.environment.setSimpleVariable("velocity", DoubleValue(pokemonEntity.deltaMovement.horizontalDistance()))
// TODO add the q.player reference, requires a cached thing or something on the client idk
@@ -1353,6 +1353,7 @@ open class PokemonEntity(
}
}
override fun travel(movementInput: Vec3) {
val prevBlockPos = this.blockPosition()
if (beamMode != 3) { // Don't let Pokémon move during recall
@@ -1599,6 +1600,9 @@ open class PokemonEntity(
jumping = value
}
override fun tickRidden(driver: Player, movementInput: Vec3) {
super.tickRidden(driver, movementInput)
this.riding.tick(this, driver, movementInput)
@@ -1762,8 +1766,45 @@ open class PokemonEntity(
// Set back to land pose type?
}
/*
These two functions (fluids and ground) need to become riding configurable and
dependent. Also, the onGround() function seems to affect quite a few spots in
code and would likely need to be changed to be something more robust instead of
just overriding this method.
*/
override fun isAffectedByFluids(): Boolean {
var fluidAffected = true
if( this.hasControllingPassenger() )
{
//Change this so it calls something from the controller to check
//if the specific controller wants to ignore fluid physics since
//not every single one will want to
fluidAffected = false
}
return fluidAffected
}
//this seems a bit hacky to me seeing as how many spots in the base classes its used.
//However, there are odd interactions with the controllers when they are meant to be
//flying or swimming but they are touching the ground and this needs to be prevented.
//Having it be able to be turned off by the flying or swimming controllers is the
//temp solution I have found.
override fun onGround() : Boolean
{
if( riding.turnOffOnGround( this ) )
{
return false
}
return super.onGround()
}
//I think already mentioned but should maybe be riding controller configurable
override fun dismountsUnderwater(): Boolean {
return true
return false
}
override fun getDefaultGravity(): Double {
@@ -10,11 +10,13 @@ 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.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.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.riding.controllers.HelicopterAirController.Companion.ROTATION_LIMIT
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readString
@@ -26,6 +28,7 @@ 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 kotlin.math.*
class BirdAirController : RideController {
override val key = KEY
@@ -43,9 +46,57 @@ class BirdAirController : RideController {
private set
var speed: Expression = "1.0".asExpression()
private set
var lastVelocity = Vec3(0.0,0.0,0.0)
private set
var maxSpeed = 0.0
private set
override fun speed(entity: PokemonEntity, driver: Player): Float {
return getRuntime(entity).resolveFloat(speed)
var drivingForce = driver.zza
val maxPropelSpeed = 0.5
var maxGlideSpeed = 1.0
var glideSpeedChange = 0.01
//speed up and slow down based on input
if( drivingForce > 0 && maxSpeed < maxPropelSpeed)
{
maxSpeed += 0.02
}
else if ( drivingForce < 0)
{
maxSpeed = max( maxSpeed - 0.1, 0.0)
}
val rollable = driver as? Rollable
if ( rollable != null )
{
maxGlideSpeed *= abs(sin(Math.toRadians( rollable.pitch.toDouble() )))
//Larger speed change the larger the pitch is.
glideSpeedChange *= sin(Math.toRadians( rollable.pitch.toDouble() ))
//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( drivingForce <= 0 || maxSpeed > maxPropelSpeed )
{
//speed decrease should be 2x speed increase?
maxSpeed = max( maxSpeed + glideSpeedChange*2, 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.
if( maxSpeed < maxGlideSpeed )
{
maxSpeed = max( maxSpeed + glideSpeedChange, maxGlideSpeed)
}
}
}
println(maxSpeed)
return 1.0f * maxSpeed.toFloat()
}
override fun rotation(entity: PokemonEntity, driver: LivingEntity): Vec2 {
@@ -54,11 +105,49 @@ class BirdAirController : RideController {
override fun velocity(entity: PokemonEntity, driver: Player, input: Vec3): Vec3 {
val f = driver.xxa * 0.2f
var g = driver.zza
if (g <= 0.0f) {
g *= 0.25f
var drivingForce = driver.zza
//if (drivingForce < 0.0f) {
// drivingForce = -0.25f
//}
drivingForce = 1.0f
{
//Always push forward
drivingForce = 1.0f
}
/*
if (!entity.isInWater && !entity.isUnderWater)
{
lastVelocity = Vec3(lastVelocity.x, -1.0, lastVelocity.z)
return lastVelocity
}
*/
var leftForce = 0.0
var upForce = 0.0
var forwardForce = 0.0
val rollable = driver as? Rollable
if( rollable != null ) {
//Likely should just be a check of the speed/max_speed instead of mult by driving force
/*
if( drivingForce = 0.0 )
{
leftForce = -1.0 * drivingForce * sin(Math.toRadians( rollable.roll.toDouble() ) )
upForce
}*/
leftForce = -1.0 * drivingForce * sin(Math.toRadians( rollable.roll.toDouble() ) )
upForce = drivingForce * -1.0 * sin(Math.toRadians( rollable.pitch.toDouble() ))
//limit how fast you can descend?
upForce *= 1.0
forwardForce = drivingForce * cos(Math.toRadians( rollable.pitch.toDouble() ))
}
//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)
@@ -68,12 +157,106 @@ class BirdAirController : RideController {
entity.jumpInputStrength = 0
entity.addDeltaMovement(jumpVector)
}
*/
val velocity = Vec3(f.toDouble(), 0.0, g.toDouble())
val velocity = Vec3(leftForce, upForce, forwardForce)
return velocity
}
override fun angRollVel(entity: PokemonEntity, driver: Player): Vec3 {
val rollable = driver as? Rollable
//In degrees per second? It was supposed to be I think but
//I have messed something up
val rotationChangeRate = 3.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() )))
//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() )))
//Create a deadzone for easier leveling out
if( rollable.roll <= 5.0 && rollable.roll >= -5.0)
{
yawForce = 0.0
pitchForce = 0.0
}
return Vec3( yawForce, pitchForce, 0.0)
}
/*
val rotLimit = 15.0
var rollForce = -1.0 * driver.xxa * rotationChangeRate
//TODO: Fix accumulated movement causing pitch and roll out of set bounds
if( rollable != null ) {
//If the roll or pitch have exceeded the limit then do not modulate that
//rotation in that direction further
val rotLim = 30.0
rollForce *= 1.0.pow(-1.0 * (abs(rollable.roll.toDouble()) / rotLimit))
if( rollable.roll >= rotLimit && rollForce > 0.0)
{
rollForce = 0.0;
}
if( rollable.roll <= -rotLimit && rollForce < 0.0)
{
rollForce = 0.0;
}
//Ignore x,y,z its angular velocity:
//yaw, pitch, roll
return Vec3( 0.0, 0.0, rollForce)
}
*/
return Vec3(0.0, 0.0, 0.0)
}
override fun rotationOnMouseXY( entity: PokemonEntity, driver: Player, yMouse: Double, xMouse: Double ){
if(driver !is Rollable) return
val rollable = driver as Rollable
val rotLimit = 65.0
val nextRollRot = rollable.roll + xMouse.toFloat()
if( xMouse > 0 )
{
if( rollable.roll > rotLimit / 3 )
{
val adjustedX = 0.5* xMouse * ( (rotLimit - rollable.roll) / ( rotLimit) )
rollable.rotate(0.0f, yMouse.toFloat(), adjustedX.toFloat() )
return
}
}else{
if( rollable.roll < -1.0 * rotLimit / 3 )
{
val adjustedX = 0.5* xMouse * ( (rotLimit + rollable.roll) / ( rotLimit) )
rollable.rotate(0.0f, yMouse.toFloat(), adjustedX.toFloat() )
return
}
}
//Ignore x,y,z its angular velocity:
//yaw, pitch, roll
rollable.rotate(0.0f, yMouse.toFloat(), 0.5f * xMouse.toFloat() )
}
override fun canJump(
entity: PokemonEntity,
driver: Player
@@ -89,21 +272,25 @@ class BirdAirController : RideController {
return Vec3.ZERO
}
override fun gravity(entity: PokemonEntity, regularGravity: Double): Double? {
return getRuntime(entity)
.withQueryValue("gravity", DoubleValue(regularGravity))
.resolveFloat(gravity)
.toDouble()
//Going to use my own gravity calculations for now.
override fun gravity(entity: PokemonEntity, regularGravity: Double): Double {
return 0.0
}
override fun shouldRoll(entity: PokemonEntity) = true
override fun useMouseInputRoll(entity: PokemonEntity): Boolean = false
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeString(gravity.toString())
buffer.writeString(horizontalAcceleration.toString())
buffer.writeString(verticalAcceleration.toString())
buffer.writeString(speed.toString())
buffer.writeDouble(maxSpeed)
buffer.writeDouble(this.lastVelocity.x)
buffer.writeDouble(this.lastVelocity.y)
buffer.writeDouble(this.lastVelocity.z)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
@@ -111,6 +298,11 @@ class BirdAirController : RideController {
horizontalAcceleration = buffer.readString().asExpression()
verticalAcceleration = buffer.readString().asExpression()
speed = buffer.readString().asExpression()
this.maxSpeed = buffer.readDouble()
val x = buffer.readDouble()
val y = buffer.readDouble()
val z = buffer.readDouble()
this.lastVelocity = Vec3(x, y, z)
}
@@ -0,0 +1,197 @@
/*
* 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.controllers
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.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
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.sin
import kotlin.math.sqrt
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.entityData.get(PokemonEntity.MOVING) })
override val condition: (PokemonEntity) -> Boolean = { entity ->
//If there are only fluid blocks or air block below the ride
//or if the entity is in water then activate the controller
Shapes.create(entity.boundingBox).blockPositionsAsListRounded().any {
if (it.y.toDouble() == (entity.position().y)) {
val blockState = entity.level().getBlockState(it.below())
return@any (blockState.isAir || !blockState.fluidState.isEmpty ) ||
(entity.isInWater || entity.isUnderWater)
}
true
}
}
override fun speed(entity: PokemonEntity, driver: Player): Float {
return getRuntime(entity).resolveFloat(speed)
}
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 velocity(entity: PokemonEntity, driver: Player, input: Vec3): Vec3 {
val runtime = getRuntime(entity)
if (!entity.isInWater && !entity.isUnderWater)
{
lastVelocity = Vec3(lastVelocity.x, -1.0, lastVelocity.z)
return lastVelocity
}
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)
}
//Get current angle looking up or down
//to determine how much the entity rises
//or falls
val currXRot = driver.xRot.toDouble()
//has to be flipped
val yComp = -1.0f * g.toDouble() * sin(Math.toRadians(currXRot))
//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
val rollable = driver as? Rollable
val currRoll = rollable?.roll
if (currRoll != null)
{
zComp = -1.0 * g.toDouble() * sin(Math.toRadians( currRoll.toDouble() ) )
}
val currVelocity = Vec3(zComp, yComp , g.toDouble())
lastVelocity = currVelocity
return currVelocity
}
override fun angRollVel(entity: PokemonEntity, driver: Player): Vec3 {
if (!entity.isInWater && !entity.isUnderWater)
{
return Vec3(0.0, 0.0, 0.0)
}
val rollable = driver as? Rollable
//this should be changed to be speed maybe?
val movingForce = driver.zza
if( rollable != null ) {
var yawAngVel = 60 * sin(Math.toRadians(rollable.roll.toDouble())).toFloat()
var pitchAngVel = -20 * Math.abs(sin(Math.toRadians(rollable.roll.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)
}
return Vec3(0.0, 0.0, 0.0)
}
override fun canJump(entity: PokemonEntity, driver: Player) = getRuntime(entity).resolveBoolean(canJump)
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())
}
override fun shouldRoll(entity: PokemonEntity) = true
override fun turnOffOnGround(entity: PokemonEntity): Boolean = true
override fun dismountOnShift(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)
}
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)
}
}
@@ -9,35 +9,47 @@
package com.cobblemon.mod.common.pokemon.riding.controllers
import com.bedrockk.molang.Expression
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.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.writeString
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
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 java.lang.Math.pow
import kotlin.math.*
/**
* Allows for flight like a bird controller, except you don't have to do any "flapping" motion,
* you can just go up and down like creative
*
* @author Apion
* @author Apion/Jackowes
* @since January 1, 2025
*/
class HelicopterAirController : RideController {
override val key = KEY
override val poseProvider = PoseProvider(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { it.deltaMovement.horizontalDistance() > 0.1 })
override val condition: (PokemonEntity) -> Boolean = { true }
//If there are only fluid blocks or air block below the ride
//then activate the controller. If it is in water the ride will
//dismount accordingly
override val condition: (PokemonEntity) -> Boolean = { entity ->
Shapes.create(entity.boundingBox).blockPositionsAsListRounded().any {
if (it.y.toDouble() == (entity.position().y)) {
val blockState = entity.level().getBlockState(it.below())
return@any (blockState.isAir || !blockState.fluidState.isEmpty )
}
true
}
}
var gravity: Expression = "1.0".asExpression()
private set
@@ -48,8 +60,12 @@ class HelicopterAirController : RideController {
var speed: Expression = "1.0".asExpression()
private set
override fun speed(entity: PokemonEntity, driver: Player): Float {
return getRuntime(entity).resolveFloat(speed)
//Increased max speed to exaggerate movement.
//This likely just needs to be a static number if it really is just
//the scalar for the velocity vector
return getRuntime(entity).resolveFloat(speed)*4
}
override fun rotation(entity: PokemonEntity, driver: LivingEntity): Vec2 {
@@ -58,19 +74,83 @@ class HelicopterAirController : RideController {
override fun velocity(entity: PokemonEntity, driver: Player, input: Vec3): Vec3 {
val runtime = getRuntime(entity)
val f = driver.xxa * 0.2f
var g = driver.zza
if (g <= 0.0f) {
g *= 0.25f
//If the player is not modulating height then hover
var yVel = if (driver.jumping) runtime.resolveDouble(verticalVelocity)
else if (driver.isShiftKeyDown) -runtime.resolveDouble(verticalVelocity)
else (0.0)
yVel *= 0.25
var xVel = 0.0
var zVel = 0.0
val rollable = driver as? Rollable
//Horizontal velocity is based on pitch and roll
if( rollable != null ) {
xVel = -1.0 * sin(Math.toRadians(rollable.roll.toDouble()))
zVel = sin(Math.toRadians(rollable.pitch.toDouble()))
}
val yVel = if (driver.jumping) runtime.resolveDouble(verticalVelocity)
else if (driver.isShiftKeyDown) -runtime.resolveDouble(verticalVelocity)
else (entity.gravity)
return Vec3(xVel, yVel, zVel)
}
val velocity = Vec3(f.toDouble(), yVel, g.toDouble())
return velocity
override fun angRollVel(entity: PokemonEntity, driver: Player): Vec3 {
val rollable = driver as? Rollable
//In degrees per second? It was supposed to be I think but
//I have messed something up
val rotationChangeRate = 2.0
val rotLimit = 30.0
var pitchForce = driver.zza * rotationChangeRate
var rollForce = -1.0 * driver.xxa * rotationChangeRate
//TODO: Fix accumulated movement causing pitch and roll out of set bounds
if( rollable != null ) {
//If the roll or pitch have exceeded the limit then do not modulate that
//rotation in that direction further
//modulate the forces so that they trend towards 0 as they approach rotation limit.
//The current function has it drop off very quickly which may not be ideal.
//Prev function:
// pitchForce = pitchForce * max(rotLim - abs( rollable.pitch.toDouble() ), 0.0)
//The three denotes that the force will be 1/3 what it would have been at
//the rotation limit
rollForce *= 2.0.pow(-1.0 * (abs(rollable.roll.toDouble()) / ROTATION_LIMIT))
pitchForce *= 2.0.pow(-1.0 * (abs(rollable.pitch.toDouble()) / ROTATION_LIMIT))
if( rollable.roll >= ROTATION_LIMIT && rollForce > 0.0)
{
rollForce = 0.0;
}
if( rollable.roll <= -ROTATION_LIMIT && rollForce < 0.0)
{
rollForce = 0.0;
}
if( rollable.pitch >= ROTATION_LIMIT && pitchForce > 0.0 )
{
pitchForce = 0.0;
}
if( rollable.pitch <= -ROTATION_LIMIT && pitchForce < 0.0 )
{
pitchForce = 0.0;
}
//Ignore x,y,z its angular velocity:
//yaw, pitch, roll
return Vec3( 0.0, pitchForce.toDouble(), rollForce)
}
return Vec3(0.0, 0.0, 0.0)
}
override fun canJump(
@@ -88,10 +168,24 @@ class HelicopterAirController : RideController {
return Vec3.ZERO
}
override fun gravity(entity: PokemonEntity, regularGravity: Double): Double? {
override fun shouldRoll(entity: PokemonEntity): Boolean = true
override fun rotationOnMouseXY( entity: PokemonEntity, driver: Player, yMouse: Double, xMouse: Double ){
if(driver !is Rollable) return
val rollable = driver as Rollable
//Ignore x,y,z its angular velocity:
//yaw, pitch, roll
rollable.rotate(xMouse.toFloat(), 0.0f, 0.0f )
}
override fun gravity(entity: PokemonEntity, regularGravity: Double): Double {
return 0.0
}
//Need to ensure that crouching lowers altitude and doesn't eject the player
override fun dismountOnShift(entity: PokemonEntity): Boolean = false
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeString(gravity.toString())
@@ -110,5 +204,6 @@ class HelicopterAirController : RideController {
companion object {
val KEY = cobblemonResource("air/helicopter")
val ROTATION_LIMIT = 30.0f
}
}
@@ -90,6 +90,10 @@ class RunUpToFlightCompositeController : RideController {
return getActiveController(entity).velocity(entity, driver, input)
}
override fun angRollVel(entity: PokemonEntity, driver: Player): Vec3 {
return getActiveController(entity).angRollVel(entity, driver)
}
override fun canJump(entity: PokemonEntity, driver: Player) = true
override fun jumpForce(
@@ -114,6 +118,14 @@ class RunUpToFlightCompositeController : RideController {
override fun shouldRoll(entity: PokemonEntity): Boolean = getActiveController(entity).shouldRoll(entity)
override fun useMouseInputRoll(entity: PokemonEntity): Boolean = getActiveController(entity).useMouseInputRoll(entity)
override fun rotationOnMouseXY( entity: PokemonEntity, driver: Player, yMouse: Double, xMouse: Double ){
getActiveController(entity).rotationOnMouseXY( entity, driver, yMouse, xMouse )
}
override fun dismountOnShift(entity: PokemonEntity): Boolean = getActiveController(entity).dismountOnShift(entity)
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeString(minimumSpeed.getString())
@@ -72,7 +72,7 @@ class VehicleLandController : RideController {
//This might not actually work, depending on what the yPos actually is. yPos of the middle of the entity? the feet?
if (it.y.toDouble() == (entity.position().y)) {
val blockState = entity.level().getBlockState(it.below())
return@any !blockState.isAir && blockState.fluidState.isEmpty
return@any !(!blockState.isAir && blockState.fluidState.isEmpty)
}
true
}
@@ -33,7 +33,8 @@ object RideControllerAdapter : JsonDeserializer<RideController> {
HelicopterAirController.KEY to HelicopterAirController::class.java,
GliderAirController.KEY to GliderAirController::class.java,
FallToFlightCompositeController.KEY to FallToFlightCompositeController::class.java,
VehicleLandController.KEY to VehicleLandController::class.java
VehicleLandController.KEY to VehicleLandController::class.java,
DolphinController.KEY to DolphinController::class.java
)
override fun deserialize(element: JsonElement, type: Type, context: JsonDeserializationContext): RideController {
@@ -6,26 +6,32 @@
"bones": {
"arm_left": {
"rotation": [
"math.r2d(-math.cos(v.limb_swing * 38.17) * v.limb_swing_amount - math.sin(v.age_in_ticks * 5.16) * 0.05)",
0,
"-math.r2d(math.cos(v.age_in_ticks * 5.16) * 0.05 + 0.05)"
"-23.2941+math.r2d(-math.cos(v.limb_swing * 38.17) * v.limb_swing_amount - math.sin(v.age_in_ticks * 5.16) * 0.05)",
3.57079,
"6.5998-math.r2d(math.cos(v.age_in_ticks * 5.16) * 0.05 + 0.05)"
]
},
"arm_right": {
"rotation": [
"math.r2d(math.cos(v.limb_swing * 38.17) * v.limb_swing_amount + math.sin(v.age_in_ticks * 5.16) * 0.05)",
0,
"math.r2d(math.cos(v.age_in_ticks * 5.16) * 0.05 + 0.05)"
"-20.8359+math.r2d(math.cos(v.limb_swing * 38.17) * v.limb_swing_amount + math.sin(v.age_in_ticks * 5.16) * 0.05)",
-2.68108,
"-7.007+math.r2d(math.cos(v.age_in_ticks * 5.16) * 0.05 + 0.05)"
]
},
"pokeball": {
"scale": 0
},
"leg_right": {
"rotation": ["math.r2d(math.cos(v.limb_swing * 38.17 + 180) * v.limb_swing_amount * 1.4)", 0, 0]
"rotation": ["-87.8218+math.r2d(math.cos(v.limb_swing * 38.17 + 180) * v.limb_swing_amount * 1.4)", 8.11827, 0.65893]
},
"leg_left": {
"rotation": ["math.r2d(math.cos(v.limb_swing * 38.17) * v.limb_swing_amount * 1.4)", 0, 0]
"rotation": ["-88.8357+math.r2d(math.cos(v.limb_swing * 38.17) * v.limb_swing_amount * 1.4)", -8.23239, 1.90829]
},
"body": {
"rotation": [0, 0, 0]
},
"head": {
"rotation": ["7.5-v.velocity*10", 0, 0]
}
}
},
@@ -124,8 +130,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]
}
},
@@ -622,12 +634,21 @@
},
"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]},
"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]}
"3.375": {
"pre": [0.4, 0.4, 0.4],
"post": [0, 0, 0]
}
}
},
"leg_right": {
@@ -26,7 +26,10 @@
"pivot": [0, 7.75, -0.5],
"cubes": [
{"origin": [-4, 4.25, -4], "size": [8, 8, 7], "uv": [0, 44]}
]
],
"locators": {
"seat1": [0, 12, -4]
}
},
{
"name": "upper_torso",
@@ -7,8 +7,8 @@
"texture_width": 128,
"texture_height": 128,
"visible_bounds_width": 9,
"visible_bounds_height": 2.5,
"visible_bounds_offset": [0, 0.75, 0]
"visible_bounds_height": 3,
"visible_bounds_offset": [0, 0.5, 0]
},
"bones": [
{
@@ -27,7 +27,11 @@
"cubes": [
{"origin": [-12, 0, -20], "size": [24, 16, 23], "uv": [0, 0]},
{"origin": [-11, 6, -22.5], "size": [22, 2, 3], "inflate": 0.01, "uv": [50, 44]}
]
],
"locators": {
"seat": [7, 16, -3],
"seat1": [0, 16, -11]
}
},
{
"name": "whisker_right",
@@ -236,7 +240,6 @@
"name": "tail",
"parent": "torso",
"pivot": [0, 6, 1],
"rotation": [0, 0, 0],
"cubes": [
{"origin": [-8, 0.5, 1], "size": [16, 11, 9], "uv": [0, 39]}
]
@@ -261,7 +264,6 @@
"name": "tail4",
"parent": "tail3",
"pivot": [0, 5, 18],
"rotation": [0, 0, 0],
"cubes": [
{"origin": [-6.5, 5, 17], "size": [13, 0, 8], "uv": [42, 49]}
]
@@ -0,0 +1,62 @@
{
"portraitScale": 1.14,
"portraitTranslation": [-0.66, 1.91, 0],
"profileScale": 0.48,
"profileTranslation": [0, 0.99, 0],
"rootBone": "whiscash",
"animations": {
"cry": "q.bedrock_stateful('whiscash', 'cry')",
"recoil": "q.bedrock_stateful('whiscash', 'recoil')"
},
"poses": {
"battle-standing": {
"poseTypes": ["STAND"],
"isBattle": true,
"isTouchingWater": false,
"animations": ["q.bedrock('whiscash', 'ground_idle')"],
"quirks": ["q.bedrock_quirk('whiscash', 'blink')"]
},
"underwater-battle-standing": {
"poseTypes": ["FLOAT"],
"isBattle": true,
"isTouchingWater": true,
"isUnderWater": true,
"animations": ["q.bedrock('whiscash', 'water_idle')"],
"quirks": ["q.bedrock_quirk('whiscash', 'blink')"]
},
"standing": {
"poseTypes": ["STAND", "NONE", "PORTRAIT", "PROFILE"],
"isBattle": false,
"isTouchingWater": false,
"animations": ["q.look('head')", "q.bedrock('whiscash', 'ground_idle')"],
"quirks": [
"q.bedrock_quirk('whiscash', 'blink')"
]
},
"walking": {
"poseTypes": ["WALK"],
"isTouchingWater": false,
"animations": ["q.bedrock('whiscash', 'ground_walk')"],
"quirks": ["q.bedrock_quirk('whiscash', 'blink')"]
},
"swim": {
"poseTypes": ["SWIM"],
"isTouchingWater": true,
"isUnderWater": true,
"animations": ["q.bedrock('whiscash', 'water_swim')"],
"quirks": ["q.bedrock_quirk('whiscash', 'blink')"]
},
"sleep": {
"poseTypes": ["SLEEP"],
"isTouchingWater": false,
"animations": ["q.bedrock('whiscash', 'ground_sleep')"]
},
"underwater-sleep": {
"poseTypes": ["SLEEP"],
"poseName": ["water-sleep"],
"isTouchingWater": true,
"isUnderWater": true,
"animations": ["q.bedrock('whiscash', 'water_sleep')"]
}
}
}
@@ -0,0 +1,62 @@
{
"portraitScale": 1.14,
"portraitTranslation": [-0.66, 1.91, 0],
"profileScale": 0.48,
"profileTranslation": [0, 0.99, 0],
"rootBone": "whiscash_nero",
"animations": {
"cry": "q.bedrock_stateful('whiscash_nero', 'cry')",
"recoil": "q.bedrock_stateful('whiscash_nero', 'recoil')"
},
"poses": {
"battle-standing": {
"poseTypes": ["STAND"],
"isBattle": true,
"isTouchingWater": false,
"animations": ["q.bedrock('whiscash_nero', 'ground_idle')"],
"quirks": ["q.bedrock_quirk('whiscash_nero', 'blink')"]
},
"underwater-battle-standing": {
"poseTypes": ["FLOAT"],
"isBattle": true,
"isTouchingWater": true,
"isUnderWater": true,
"animations": ["q.bedrock('whiscash_nero', 'water_battle_idle')"],
"quirks": ["q.bedrock_quirk('whiscash_nero', 'blink')"]
},
"standing": {
"poseTypes": ["STAND", "NONE", "PORTRAIT", "PROFILE"],
"isBattle": false,
"isTouchingWater": false,
"animations": ["q.look('head')", "q.bedrock('whiscash_nero', 'ground_idle')"],
"quirks": [
"q.bedrock_quirk('whiscash_nero', 'blink')"
]
},
"walking": {
"poseTypes": ["WALK"],
"isTouchingWater": false,
"animations": ["q.bedrock('whiscash_nero', 'ground_walk')"],
"quirks": ["q.bedrock_quirk('whiscash_nero', 'blink')"]
},
"swim": {
"poseTypes": ["SWIM"],
"isTouchingWater": true,
"isUnderWater": true,
"animations": ["q.bedrock('whiscash_nero', 'water_swim')"],
"quirks": ["q.bedrock_quirk('whiscash_nero', 'blink')"]
},
"sleep": {
"poseTypes": ["SLEEP"],
"isTouchingWater": false,
"animations": ["q.bedrock('whiscash_nero', 'ground_sleep')"]
},
"underwater-sleep": {
"poseTypes": ["SLEEP"],
"poseName": ["water-sleep"],
"isTouchingWater": true,
"isUnderWater": true,
"animations": ["q.bedrock('whiscash_nero', 'water_sleep')"]
}
}
}
@@ -11,6 +11,7 @@
},
{
"aspects": ["whiscash_nero"],
"poser": "cobblemon:whiscash",
"model": "cobblemon:whiscash_nero.geo",
"texture": "cobblemon:textures/pokemon/0340_whiscash/whiscash_nero.png"
},
@@ -70,7 +70,7 @@
{
"offset": {
"x": 0,
"y": 1.5,
"y": 0.5,
"z": -0.5
},
"poseOffsets": [
@@ -80,8 +80,8 @@
],
"offset": {
"x": 0,
"y": 0.7,
"z": 1
"y": 0,
"z": 0
}
},
{
@@ -90,37 +90,8 @@
],
"offset": {
"x": 0,
"y": 0.2,
"z": 1
}
}
]
},
{
"offset": {
"x": 0,
"y": 1.5,
"z": -1.5
},
"poseOffsets": [
{
"poseTypes": [
"HOVER"
],
"offset": {
"x": 0,
"y": 0.4,
"z": 1
}
},
{
"poseTypes": [
"FLY"
],
"offset": {
"x": 0,
"y": 0.2,
"z": 0.2
"y": 0,
"z": 0
}
}
]
@@ -52,7 +52,7 @@
},
"riding": {
"controller": {
"key": "cobblemon:swim/generic",
"key": "cobblemon:land/generic",
"speed": "Math.clamp(q.entity.velocity.horizontal_magnitude + 0.07, 0, 0.08)",
"jumpVector": [0, "0.3*q.jump_strength/50", "-1 * 0.5*q.jump_strength/50"]
},
@@ -49,6 +49,39 @@
"height": 1,
"fixed": false
},
"riding": {
"controller": {
"key": "cobblemon:swim/dolphin",
"speed": "Math.clamp(q.entity.velocity.horizontal_magnitude + 0.5, 0, 3)",
"jumpVector": [0, "0.3*q.jump_strength/50", "-1 * 0.5*q.jump_strength/50"]
},
"seats": [
{
"offset": {
"x": 0.0,
"y": 0.4,
"z": 0.0
},
"poseAnimations": [
{
"animations": [
{
"fileName": "trainer_generic",
"animationName": "animation.trainer_generic.idle"
}
]
}
]
},
{
"offset": {
"x": 0.0,
"y": 0.7,
"z": -1.0
}
}
]
},
"behaviour": {
"moving": {
"walk": {