Compare commits

...
36 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
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
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
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
37 changed files with 1225 additions and 454 deletions
@@ -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
@@ -508,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("#")
@@ -788,7 +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("is_gliding") { DoubleValue(pokemonEntity.useRidingAltPose()) }
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,9 +168,9 @@ data class RidingManager(val entity: PokemonEntity) {
return controller.jumpForce(entity, driver, jumpStrength)
}
fun useRidingAltPose(entity: PokemonEntity, driver: Player): Boolean {
val controller = getController(entity) ?: return false
return controller.useRidingAltPose(entity, driver)
fun getActiveRidingAltPose(entity: PokemonEntity, driver: Player): RidingAltPose {
val controller = getController(entity) ?: return RidingAltPose.NONE
return controller.getActiveRidingAltPose(entity, driver)
}
/**
@@ -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,7 +123,7 @@ interface RideController : Encodable, Decodable {
fun useAngVelSmoothing(entity: PokemonEntity): Boolean = false
fun useRidingAltPose(entity: PokemonEntity, driver: Player): 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
@@ -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))
}
}
@@ -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,6 +36,7 @@ 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
@@ -138,6 +140,97 @@ abstract class PosableState : Schedulable {
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) }
@@ -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)
@@ -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(
@@ -1410,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
@@ -1436,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)
}
@@ -1813,9 +1843,9 @@ open class PokemonEntity(
return this.riding.speed(this, controller)
}
fun useRidingAltPose(): Boolean {
val driver = this.controllingPassenger as? Player ?: return false
return this.riding.useRidingAltPose(this, driver)
fun getActiveRidingAltPose(): RidingAltPose {
val driver = this.controllingPassenger as? Player ?: return RidingAltPose.NONE
return this.riding.getActiveRidingAltPose(this, driver)
}
var jumpInputStrength: Int = 0 // move this
@@ -15,13 +15,14 @@ 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.Mth
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
@@ -38,27 +39,27 @@ class BirdAirController : RideController {
override val condition: (PokemonEntity) -> Boolean = { true }
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 135.0, 45.0)".asExpression()
private set
var topSpeedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 1.0, 0.35)".asExpression()
private set
var glideTopSpeedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 2.0, 1.0)".asExpression()
private set
// Max accel is a whole 1.0 in 3 seconds. 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 * 3.0)), (1.0 / (20.0 * 8.0)))".asExpression()
private set
//Seconds self propelled flight (glide is not self propelled in this case)
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 120.0, 20.0)".asExpression()
private set
//max y level for the ride
var altitudeExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 200.0, 128.0)".asExpression()
private set
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 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 {
//Retrieve the current bird controller state and stats
@@ -99,7 +100,7 @@ class BirdAirController : RideController {
upForce -= 0.3
}
val altitudeLimit = getRuntime(entity).resolveDouble(altitudeExpr)
val altitudeLimit = this.getStat(entity, RidingStat.JUMP)
//Only limit altitude if altitude is not infinite
if (!getRuntime(entity).resolveBoolean(infiniteAltitude)) {
@@ -117,13 +118,13 @@ class BirdAirController : RideController {
val state = getState(entity, ::BirdAirState)
//TODO: Tie in handling
val handling = getRuntime(entity).resolveDouble(handlingExpr)
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
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 influence pitch as well
//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
@@ -150,8 +151,8 @@ class BirdAirController : RideController {
val rollable = driver as Rollable
val state = getState(entity, ::BirdAirState)
val handling = getRuntime(entity).resolveDouble(handlingExpr)
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
val handling = this.getStat(entity, RidingStat.SKILL)
val topSpeed = this.getStat(entity, RidingStat.SPEED)
//Smooth out mouse input.
val smoothingSpeed = 4
@@ -214,8 +215,8 @@ class BirdAirController : RideController {
override fun shouldRoll(entity: PokemonEntity) = true
override fun rideFovMult(entity: PokemonEntity, driver: Player): Float {
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
val glideTopSpeed = getRuntime(entity).resolveDouble(glideTopSpeedExpr)
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?
@@ -228,28 +229,19 @@ class BirdAirController : RideController {
override fun useAngVelSmoothing(entity: PokemonEntity): Boolean = false
override fun useRidingAltPose(entity: PokemonEntity, driver: Player): Boolean {
override fun getActiveRidingAltPose(entity: PokemonEntity, driver: Player): RidingAltPose {
val state = getState(entity, ::BirdAirState)
return state.gliding
val pose = if (state.gliding) RidingAltPose.GLIDING else RidingAltPose.NONE
return pose
}
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeString(topSpeedExpr.toString())
buffer.writeString(glideTopSpeedExpr.toString())
buffer.writeString(accelExpr.toString())
buffer.writeString(handlingExpr.toString())
buffer.writeString(altitudeExpr.toString())
buffer.writeString(infiniteStamina.toString())
buffer.writeString(infiniteAltitude.toString())
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
topSpeedExpr = buffer.readString().asExpression()
glideTopSpeedExpr = buffer.readString().asExpression()
accelExpr = buffer.readString().asExpression()
handlingExpr = buffer.readString().asExpression()
altitudeExpr = buffer.readString().asExpression()
infiniteStamina = buffer.readString().asExpression()
infiniteAltitude = buffer.readString().asExpression()
}
@@ -276,12 +268,12 @@ class BirdAirController : RideController {
//retrieve stats
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
val glideTopSpeed = getRuntime(entity).resolveDouble(glideTopSpeedExpr)
val accel = getRuntime(entity).resolveDouble(accelExpr)
val staminaStat = getRuntime(entity).resolveDouble(staminaExpr)
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.0
var glideSpeedChange = 0.00
val currSpeed = state.rideVel.length()
@@ -394,6 +386,7 @@ class BirdAirController : RideController {
}
//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,
@@ -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('STAMINA', 'AIR', 60.0, 10.0)".asExpression()
private set
var jumpExpr: Expression = "q.get_ride_stats('JUMP', '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,7 +123,7 @@ 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 altitudeLimit = this.getStat(entity, RidingStat.JUMP)
//Only limit altitude if altitude is not infinite
if (!getRuntime(entity).resolveBoolean(infiniteAltitude)) {
@@ -185,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)
@@ -239,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)
@@ -256,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())
}
@@ -269,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()
}
@@ -288,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
@@ -67,6 +69,10 @@ class RunUpToFlightCompositeController : RideController {
}
}
override fun getStat(entity: PokemonEntity, stat: RidingStat): Double {
return getActiveController(entity).getStat(entity, stat)
}
override fun speed(
entity: PokemonEntity,
driver: Player
@@ -76,9 +82,8 @@ class RunUpToFlightCompositeController : RideController {
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
@@ -94,7 +99,6 @@ class RunUpToFlightCompositeController : RideController {
//Pass the speed to the next state
val flightState = flightController.getState( entity, ::BirdAirState )
val groundState = landController.getState( entity, ::GenericLandState )
flightState.currSpeed = groundState.currSpeed
flightState.rideVel = groundState.rideVel
flightState.stamina = groundState.stamina
@@ -112,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,
@@ -160,8 +172,8 @@ class RunUpToFlightCompositeController : RideController {
override fun useAngVelSmoothing(entity: PokemonEntity): Boolean = getActiveController(entity).useAngVelSmoothing(entity)
override fun useRidingAltPose(entity: PokemonEntity, driver: Player): Boolean {
return getActiveController(entity).useRidingAltPose(entity, driver)
override fun getActiveRidingAltPose(entity: PokemonEntity, driver: Player): RidingAltPose {
return getActiveController(entity).getActiveRidingAltPose(entity, driver)
}
override fun rotationOnMouseXY
@@ -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(
@@ -125,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,8 +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 stamina: Float = 1.0f
var rideVel: Vec3 = Vec3.ZERO
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()
}
@@ -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": [
{
@@ -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,8 +86,15 @@
"fly": {
"poseTypes": ["FLY"],
"isBattle": false,
"condition": "!q.is_gliding",
"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')"
@@ -96,8 +103,15 @@
"glide": {
"poseTypes": ["FLY"],
"isBattle": false,
"condition": "q.is_gliding",
"animations": ["q.look('head_ai')", "q.bedrock('charizard', 'air_glide_TEST')"],
"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')"
]
@@ -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": [
@@ -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": [