Compare commits

...
Author SHA1 Message Date
JacksonB ad350afb20 Start of a rought attempt at adding ride boost and ride sound 2025-03-15 16:09:06 -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
17 changed files with 897 additions and 172 deletions
+82
View File
@@ -0,0 +1,82 @@
{
"lastSavedVersion": "1.7.0",
"maxPokemonLevel": 100,
"maxPokemonFriendship": 255,
"announceDropItems": true,
"defaultDropItemMethod": "on-entity",
"ambientPokemonCryTicks": 1080,
"defaultBoxCount": 30,
"pokemonSaveIntervalSeconds": 30,
"storageFormat": "nbt",
"preventCompletePartyDeposit": false,
"mongoDBConnectionString": "mongodb://localhost:27017",
"mongoDBDatabaseName": "cobblemon",
"maxVerticalCorrectionBlocks": 64,
"minimumLevelRangeMax": 10,
"enableSpawning": true,
"minimumDistanceBetweenEntities": 8.0,
"maxNearbyBlocksHorizontalRange": 4,
"maxNearbyBlocksVerticalRange": 2,
"maxVerticalSpace": 8,
"worldSliceDiameter": 8,
"worldSliceHeight": 16,
"ticksBetweenSpawnAttempts": 20.0,
"minimumSliceDistanceFromPlayer": 16.0,
"maximumSliceDistanceFromPlayer": 64.0,
"exportSpawnConfig": false,
"savePokemonToWorld": true,
"exportStarterConfig": false,
"autoUpdateShowdown": true,
"defaultFleeDistance": 32.0,
"allowExperienceFromPvP": true,
"experienceShareMultiplier": 0.5,
"luckyEggMultiplier": 1.5,
"allowSpectating": true,
"experienceMultiplier": 2.0,
"pokemonPerChunk": 1.0,
"passiveStatuses": {
"cobblemon:poison": "180-300",
"cobblemon:poisonbadly": "180-300",
"cobblemon:paralysis": "180-300",
"cobblemon:frozen": "180-300",
"cobblemon:sleep": "180-300",
"cobblemon:burn": "180-300"
},
"infiniteHealerCharge": false,
"maxHealerCharge": 6.0,
"secondsToChargeHealingMachine": 900.0,
"defaultFaintTimer": 300,
"faintAwakenHealthPercent": 0.2,
"healPercent": 0.05,
"healTimer": 60,
"baseApricornTreeGenerationChance": 0.1,
"displayEntityLevelLabel": true,
"displayEntityNameLabel": true,
"displayNameForUnknownPokemon": false,
"displayEntityLabelsWhenCrouchingOnly": false,
"shinyRate": 8192.0,
"shinyNoticeParticlesDistance": 24.0,
"captureCalculator": "cobblemon",
"playerDamagePokemon": true,
"appleLeftoversChance": 0.025,
"maxRootsInArea": 5,
"bigRootPropagationChance": 0.1,
"energyRootChance": 0.25,
"maxDynamaxLevel": 10,
"teraTypeRate": 20.0,
"defaultPasturedPokemonLimit": 16,
"pastureBlockUpdateTicks": 40,
"pastureMaxWanderDistance": 64,
"pastureMaxPerChunk": 4.0,
"maxInsertedFossilItems": 2,
"walkingInBattleAnimations": false,
"battleWildMaxDistance": 12.0,
"tradeMaxDistance": 12.0,
"battlePvPMaxDistance": 32.0,
"battleSpectateMaxDistance": 64.0,
"maxPokedexScanningDetectionRange": 10.0,
"hideUnimplementedPokemonInThePokedex": false,
"summaryPokemonFollowCursor": true,
"partyPortraitAnimations": "NEVER_ANIMATE",
"enableDebugKeys": false
}
@@ -0,0 +1,50 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding
import net.minecraft.client.player.LocalPlayer
import net.minecraft.client.resources.sounds.AbstractTickableSoundInstance
import net.minecraft.client.resources.sounds.SoundInstance
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundSource
/**
* put what it is here
*
* @author Jackowes
* @since March 4th, 2025
*/
class RidingOnPlayerSoundEventInstance(
private val player: LocalPlayer,
soundEvent: SoundEvent
) : AbstractTickableSoundInstance(soundEvent, SoundSource.PLAYERS, SoundInstance.createUnseededRandom()) {
init {
this.looping = true
this.delay = 0
this.volume = 0.1f
}
fun setPitch( pitch: Float ){
this.pitch = pitch
}
fun setVolume( volume: Float){
this.volume = volume
}
override fun tick() {
//TODO: implement effectively
//For now it should just play the sound?
setPitch(1.0f)
println("ticking")
}
}
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.command.argument
import com.cobblemon.mod.common.api.permission.CobblemonPermissions
import com.cobblemon.mod.common.command.argument.SpeciesArgumentType
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.permission
import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.arguments.FloatArgumentType
import com.mojang.brigadier.context.CommandContext
import net.minecraft.commands.CommandSourceStack
import net.minecraft.commands.Commands
object SetRideBoost {
fun register(dispatcher: CommandDispatcher<CommandSourceStack>) {
val command = Commands.literal("changewalkspeed")
.permission(CobblemonPermissions.CHANGE_WALK_SPEED)
.then(
Commands.argument("pokemon", SpeciesArgumentType.species())
.then(Commands.argument("walkSpeed", FloatArgumentType.floatArg()).executes(::execute))
)
.executes(::execute)
dispatcher.register(command)
}
private fun execute(context: CommandContext<CommandSourceStack>) : Int {
val pkm = SpeciesArgumentType.getPokemon(context, "pokemon")
val walkSpeed = FloatArgumentType.getFloat(context, "walkSpeed")
pkm.behaviour.moving.walk.walkSpeed = walkSpeed.toString().asExpressionLike()
pkm.forms.clear()
pkm.forms.add(FormData().also { it.initialize(pkm)})
return Command.SINGLE_SUCCESS
}
}
@@ -0,0 +1,42 @@
/*
* 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.net.messages.client.pokemon.update
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.net.IntSize
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readEnumConstant
import com.cobblemon.mod.common.util.readSizedInt
import net.minecraft.network.RegistryFriendlyByteBuf
class RideBoostUpdatePacket(pokemon: () -> Pokemon, rideStyle: RidingStyle, rideStat: RidingStat, value: Int)
: IntUpdatePacket<RideBoostUpdatePacket>(pokemon, value) {
override val id = ID
override fun getSize() = IntSize.U_BYTE
private val rideStyle = rideStyle
private val rideStat = rideStat
override fun set(pokemon: Pokemon, value: Int) {
pokemon.setRideBoost(rideStyle, rideStat, value)
}
companion object {
val ID = cobblemonResource("ride_boost_update")
fun decode(buffer: RegistryFriendlyByteBuf): RideBoostUpdatePacket {
val pokemon = decodePokemon(buffer)
val rideStyle = buffer.readEnumConstant(RidingStyle::class.java)
val rideStat = buffer.readEnumConstant(RidingStat::class.java)
val value = buffer.readSizedInt(IntSize.U_SHORT)
return RideBoostUpdatePacket(pokemon, rideStyle, rideStat, value)
}
}
}
@@ -58,6 +58,8 @@ import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.SettableObservable
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.riding.RidingProperties
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.scheduling.afterOnServer
import com.cobblemon.mod.common.api.storage.StoreCoordinates
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
@@ -332,6 +334,25 @@ open class Pokemon : ShowdownIdentifiable {
}
}
/**
* TODO: Give a description and move the set and get to te appropriate place
*/
/*
var rideBoost: MutableMap<RidingStyle, MutableMap<RidingStat, Int>> = mutableMapOf()
private set(value) {
field = value
onChange(RideBoostUpdatePacket({ this }, value))
}
fun setRideBoost(rideStyle: RidingStyle, rideStat: RidingStat, value: Int) {
rideBoost.getOrPut(rideStyle) { mutableMapOf() }[rideStat] = value
}
fun getRideBoost(rideStyle: RidingStyle, rideStat: RidingStat): Int {
return rideBoost[rideStyle]?.get(rideStat) ?: 0 // Default to 0 if missing
}
*/
var state: PokemonState = InactivePokemonState()
set(value) {
if (field != value) {
@@ -9,7 +9,7 @@
package com.cobblemon.mod.common.pokemon.riding.controllers
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.riding.controller.RideController
@@ -19,7 +19,9 @@ 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
@@ -32,81 +34,38 @@ class BirdAirController : RideController {
override val poseProvider = PoseProvider(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) {
val player = it.passengers.firstOrNull() as? Player ?: return@PoseOption false
it.deltaMovement.length() > 0.3 })
/*
.with(PoseOption(PoseType.FLY) {
val player = it.passengers.firstOrNull() as? Player ?: return@PoseOption false
player.zza > 0.0f // Checks if the player is actively attempting to move forward
})
*/
it.deltaMovement.length() > 0.5 })
override val condition: (PokemonEntity) -> Boolean = { true }
var jumpVector = listOf("0".asExpression(), "0.3".asExpression(), "0".asExpression())
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 135.0, 45.0)".asExpression()
private set
var gravity: Expression = "0".asExpression()
var topSpeedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 1.0, 0.35)".asExpression()
private set
var horizontalAcceleration: Expression = "0.1".asExpression()
var glideTopSpeedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 2.0, 1.0)".asExpression()
private set
var verticalAcceleration: Expression = "0.1".asExpression()
// 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
// Where 90 is the width of the range and 45 is the offset.
// So the handling interpolates between 135 and 45
var handlingExpr: Expression = "(q.get_ride_stats('SKILL', 'AIR') / 100) * 90 + 45".asExpression()
var infiniteStamina: Expression = "false".asExpression()
private set
var topSpeedExpr: Expression = "(q.get_ride_stats('SPEED', 'AIR') / 100) * (1.0 - 0.35) + 0.35".asExpression()
private set
//Reaches max top speed from a standstill at minimum 8 seconds and max 3 seconds.
//This is a bit of a confusing expression and likely should be cleaned up to be more readable
var accelExpr: Expression = "(q.get_ride_stats('ACCELERATION', 'AIR') / 100) * (0.01666666666 - 0.00625) + 0.00625".asExpression()
var infiniteAltitude: Expression = "false".asExpression()
private set
override fun speed(entity: PokemonEntity, driver: Player): Float {
//Retrieve the current bird controller state
//val state = entity.riding.getState(KEY) { e -> BirdAirControllerState(e) }
//Retrieve the current bird controller state and stats
val state = getState(entity, ::BirdAirState)
//retrieve stats
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
val accel = getRuntime(entity).resolveDouble(accelExpr)
val maxGlideSpeed = topSpeed * 2
var glideSpeedChange = 0.00
//speed up and slow down based on input
if (driver.zza > 0.0 && state.currSpeed < topSpeed) {
state.currSpeed = min(state.currSpeed + accel , topSpeed)
} else if (driver.zza < 0.0 && state.currSpeed > 0.0) {
//Decelerate is currently always a constant half of max acceleration.
state.currSpeed = max(state.currSpeed - (0.0166 / 2), 0.0)
}
val rollable = driver as? Rollable
if (rollable != null) {
glideSpeedChange = sin(Math.toRadians(rollable.pitch.toDouble()))
glideSpeedChange = glideSpeedChange.pow(3)
//TODO: Possibly create a deadzone around parallel where speed isn't taken away from?
if (glideSpeedChange <= 0.0) {
//Ensures that a propelling force is still able to be applied when
//climbing in height
if (driver.zza <= 0) {
//speed decrease should be 2x speed increase?
state.currSpeed = max(state.currSpeed + (0.0166) * glideSpeedChange, 0.0 )
}
} else {
// only add to the speed if it hasn't exceeded the current
//glide angles maximum amount of speed that it can give.
state.currSpeed = min(state.currSpeed + ((0.0166 * 2) * glideSpeedChange), maxGlideSpeed)
}
}
//air resistance
state.currSpeed = max(state.currSpeed - 0.005, 0.0)
return state.currSpeed.toFloat()
return state.rideVel.length().toFloat()
}
override fun rotation(entity: PokemonEntity, driver: LivingEntity): Vec2 {
@@ -118,62 +77,76 @@ class BirdAirController : RideController {
var leftForce = 0.0
var upForce = 0.0
var forwardForce = 0.0
val state = getState(entity, ::BirdAirState)
//Perform ride velocity update
calculateRideSpaceVel(entity, driver, state)
//Translate ride space velocity to world space velocity.
val rollable = driver as? Rollable
if (rollable != null) {
//Need to deadzone this when straight up or down
// test with and without strafing added. May be wanted while moving
leftForce = -1.0 * sin(Math.toRadians(rollable.roll.toDouble()))
upForce = -1.0 * sin(Math.toRadians(rollable.pitch.toDouble()))
upForce += -1.0 * sin(Math.toRadians(rollable.pitch.toDouble())) * state.rideVel.z
forwardForce += cos(Math.toRadians(rollable.pitch.toDouble())) * state.rideVel.z
forwardForce = cos(Math.toRadians(rollable.pitch.toDouble()))
upForce += cos(Math.toRadians(rollable.pitch.toDouble())) * state.rideVel.y
forwardForce += sin(Math.toRadians(rollable.pitch.toDouble())) * state.rideVel.y
}
//TODO: Add in an upwards and downwards force when pressing space maybe?
//Leave out tap to flap right now
/*
var jumpVector = Vec3.ZERO
if (entity.jumpInputStrength > 0) {
val runtime = getRuntime(entity)
runtime.environment.query.addFunction("jump_strength") { DoubleValue(entity.jumpInputStrength.toDouble()) }
val jumpForces = this.jumpVector.map { runtime.resolveFloat(it) }
jumpVector = Vec3(jumpForces[0].toDouble(), jumpForces[1].toDouble(), jumpForces[2].toDouble())
jumpVector = jumpVector.toVector3f().mul((driver as Rollable).orientation).toVec3d()
entity.jumpInputStrength = 0
entity.addDeltaMovement(jumpVector)
//Bring the ride out of the sky when stamina is depleted.
if (state.stamina <= 0.0) {
upForce -= 0.3
}
*/
//Retrieve the current composite controller state
val state = getState(entity, ::BirdAirState)
val velocity = Vec3(0.0 , upForce * state.currSpeed, forwardForce * state.currSpeed)
val altitudeLimit = getRuntime(entity).resolveDouble(altitudeExpr)
//Only limit altitude if altitude is not infinite
if (!getRuntime(entity).resolveBoolean(infiniteAltitude)) {
//Provide a hard limit on altitude
upForce = if (entity.y >= altitudeLimit && upForce > 0) 0.0 else upForce
}
val velocity = Vec3(state.rideVel.x , upForce, forwardForce)
return velocity
}
override fun angRollVel(entity: PokemonEntity, driver: Player, deltaTime: Double): Vec3 {
val rollable = driver as? Rollable
val state = getState(entity, ::BirdAirState)
//TODO: Tie in handling
val handling = getRuntime(entity).resolveDouble(handlingExpr)
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
val rotationChangeRate = 10.0
if (rollable != null) {
var yawForce = rotationChangeRate * sin(Math.toRadians(rollable.roll.toDouble()))
//for a bit of correction on the rolls limit it to a quarter the amount
var pitchForce = -0.25 * rotationChangeRate * abs(sin(Math.toRadians(rollable.roll.toDouble())))
var pitchForce = -0.35 * rotationChangeRate * abs(sin(Math.toRadians(rollable.roll.toDouble())))
//limit rotation modulation when pitched up heavily or pitched down heavily
yawForce *= abs(cos(Math.toRadians(rollable.pitch.toDouble())))
pitchForce *= abs(cos(Math.toRadians(rollable.pitch.toDouble()))) * 1.5
//Create a deadzone for easier leveling out
//Fully ignore roll influenced angle changes when looking a certain amount up or down
if( abs(cos(Math.toRadians(rollable.pitch.toDouble()))) <= 0.25 )
{
yawForce = 0.0
pitchForce = 0.0
}
//TODO: is the deadzone needed?
/*
if (rollable.roll <= 0.0 && rollable.roll >= -0.0) {
yawForce = 0.0
pitchForce = 0.0
}
*/
return Vec3(yawForce, pitchForce, 0.0)
}
@@ -191,42 +164,56 @@ class BirdAirController : RideController {
deltaTime: Double
): Vec3 {
if (driver !is Rollable) return Vec3.ZERO
val rollable = driver as Rollable
val state = getState(entity, ::BirdAirState)
val handling = getRuntime(entity).resolveDouble(handlingExpr)
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
//Smooth out mouse input.
val smoothingSpeed = 4
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
var xInput = xMouseSmoother.getNewDeltaValue(xMouse * 0.1 * invertRoll, deltaTime * smoothingSpeed);
val YInput = yMouseSmoother.getNewDeltaValue(yMouse * 0.1 * invertPitch, deltaTime * smoothingSpeed);
val xInput = xMouseSmoother.getNewDeltaValue(xMouse * 0.1 * invertRoll, deltaTime * smoothingSpeed);
val yInput = yMouseSmoother.getNewDeltaValue(yMouse * 0.1 * invertPitch, deltaTime * smoothingSpeed);
//limit rolling based on healing
val rotLimit = handling
//limit rolling based on handling and current speed.
//modulated by speed so that when flapping idle in air you are ont wobbling around to look around
val rotMin = 15.0
var rollForce = xInput
val rotLimit = max(handling * normalizeVal(state.rideVel.length(), 0.0, topSpeed).pow(3), rotMin)
//Limit roll by non linearly decreasing inputs towards
// a rotation limit based on the current distance from
// that rotation limit
if (abs(rollable.roll + xInput) < rotLimit) {
if (sign(xInput) == sign(rollable.roll).toDouble()) {
if (abs(rollable.roll + rollForce) < rotLimit) {
if (sign(rollForce) == sign(rollable.roll).toDouble()) {
val d = abs(abs(rollable.roll) - rotLimit)
xInput *= (d.pow(2)) / (rotLimit.pow(2))
rollForce *= (d.pow(2)) / (rotLimit.pow(2))
}
} else if (sign(xInput) == sign(rollable.roll).toDouble()) {
xInput = 0.0
} else if (sign(rollForce) == sign(rollable.roll).toDouble()) {
rollForce = 0.0
}
//Give the ability to yaw with x mouse input when at low speeds.
val yawForce = xInput * ( 1.0 - normalizeVal(state.rideVel.length(), 0.0, topSpeed).pow(3))
//yaw, pitch, roll
return Vec3(0.0, YInput, xInput)
return Vec3(yawForce, yInput, rollForce)
}
override fun canJump(
entity: PokemonEntity,
driver: Player
): Boolean {
return true
return false
}
override fun setRideBar(entity: PokemonEntity, driver: Player): Float {
//Retrieve stamina from state
val state = getState(entity, ::BirdAirState)
return (state.stamina / 1.0f)
}
override fun jumpForce(
@@ -243,29 +230,181 @@ 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 state = getState(entity, ::BirdAirState)
//Must I ensure that topspeed is greater than minimum?
val normalizedGlideSpeed = normalizeVal(state.rideVel.length(), topSpeed, glideTopSpeed)
//Only ever want the fov change to be a max of 0.2 and for it to have non linear scaling.
return 1.0f + normalizedGlideSpeed.pow(2).toFloat() * 0.2f
}
override fun useAngVelSmoothing(entity: PokemonEntity): Boolean = false
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeString(gravity.toString())
buffer.writeString(horizontalAcceleration.toString())
buffer.writeString(verticalAcceleration.toString())
buffer.writeString(topSpeedExpr.toString())
buffer.writeString(glideTopSpeedExpr.toString())
buffer.writeString(accelExpr.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) {
gravity = buffer.readString().asExpression()
horizontalAcceleration = buffer.readString().asExpression()
verticalAcceleration = buffer.readString().asExpression()
topSpeedExpr = buffer.readString().asExpression()
glideTopSpeedExpr = buffer.readString().asExpression()
accelExpr = buffer.readString().asExpression()
accelExpr = buffer.readString().asExpression()
handlingExpr = buffer.readString().asExpression()
altitudeExpr = buffer.readString().asExpression()
infiniteStamina = buffer.readString().asExpression()
infiniteAltitude = buffer.readString().asExpression()
}
companion object {
val KEY = cobblemonResource("air/bird")
}
/*
* Normalizes the given value between a min and a max.
* The result is clamped between 0.0 and 1.0, where 0.0 represents x is at or below min
* and 1.0 represents x is at or above it.
*/
fun normalizeVal(x: Double, min: Double, max: Double): Double {
require(max > min) { "max must be greater than min" }
return ((x - min) / (max - min)).coerceIn(0.0, 1.0)
}
/*
* Calculates the change in the ride space vector due to player input and ride state
*/
fun calculateRideSpaceVel( entity: PokemonEntity, driver: Player, state: BirdAirState){
//retrieve stats
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
val glideTopSpeed = getRuntime(entity).resolveDouble(glideTopSpeedExpr)
val accel = getRuntime(entity).resolveDouble(accelExpr)
val staminaStat = getRuntime(entity).resolveDouble(staminaExpr)
var glideSpeedChange = 0.00
val currSpeed = state.rideVel.length()
//Flag for determining if stamina should be lost
var loseStamina = 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())))
loseStamina = true
}
val rollable = driver as? Rollable
if (rollable != null) {
//Base glide speed change on current pitch of the ride.
glideSpeedChange = sin(Math.toRadians(rollable.pitch.toDouble()))
glideSpeedChange = glideSpeedChange.pow(3) * 0.5
//TODO: Possibly create a deadzone around parallel where glide doesn't affect speed?
if (glideSpeedChange <= 0.0) {
//Ensures that a propelling force is still able to be applied when
//climbing in height
if (driver.zza <= 0) {
//speed decrease should be 2x speed increase?
//state.currSpeed = max(state.currSpeed + (0.0166) * glideSpeedChange, 0.0 )
state.rideVel = Vec3(
state.rideVel.x,
state.rideVel.y,
lerp( state.rideVel.z, 0.0,glideSpeedChange * -0.0166 * 2 ))
}
} else {
// only add to the speed if it hasn't exceeded the current
//glide angles maximum amount of speed that it can give.
//state.currSpeed = min(state.currSpeed + ((0.0166 * 2) * glideSpeedChange), maxGlideSpeed)
state.rideVel = Vec3(
state.rideVel.x,
state.rideVel.y,
min(state.rideVel.z + ((0.0166 * 2) * glideSpeedChange), glideTopSpeed))
}
}
//Lateral movement based on driver input.
val latTopSpeed = topSpeed / 2.0
if (driver.xxa != 0.0f && state.stamina > 0.0) {
state.rideVel = Vec3(
(state.rideVel.x + (accel * driver.xxa)).coerceIn(-latTopSpeed, latTopSpeed),
state.rideVel.y,
state.rideVel.z)
loseStamina = 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)
loseStamina = true
}
else {
state.rideVel = Vec3(
state.rideVel.x,
lerp(state.rideVel.y, 0.0, vertTopSpeed / 20.0),
state.rideVel.z)
}
//Only perform stamina logic if the ride does not have infinite stamina
if (getRuntime(entity).resolveBoolean(infiniteStamina)) {
if (loseStamina) {
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)))
}
}
@@ -49,9 +49,9 @@ class JetAirController : RideController {
var accelExpr: Expression = "q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 1.0)), (1.0 / (20.0 * 5.0)))".asExpression()
private set
// Between 60 seconds and 10 seconds at the lowest when at full speed.
var staminaExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 60.0, 10.0)".asExpression()
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 60.0, 10.0)".asExpression()
private set
var jumpExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 300.0, 128.0)".asExpression()
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 300.0, 128.0)".asExpression()
private set
// Make configurable by json
@@ -123,14 +123,12 @@ class JetAirController : RideController {
upForce = if(state.stamina > 0.0) upForce else -0.7
val altitudeLimit = getRuntime(entity).resolveDouble(jumpExpr)
val pushingHeightLimit = if(getRuntime(entity).resolveBoolean(infiniteAltitude)) false
else (entity.y >= 128 && entity.xRot <= 0)
//Incorporate the altitude limiter.
//Apply downwards force if limit is reached. Resist upwards force on a buffer of 30 blocks past the limit.
upForce = if(pushingHeightLimit) upForce - upForce * ((entity.y - altitudeLimit) / 30) - 0.5
else upForce
//Only limit altitude if altitude is not infinite
if (!getRuntime(entity).resolveBoolean(infiniteAltitude)) {
//Provide a hard limit on altitude
upForce = if (entity.y >= altitudeLimit && upForce > 0) 0.0 else upForce
}
val velocity = Vec3(0.0 , upForce, forwardForce )
@@ -23,6 +23,7 @@ import com.cobblemon.mod.common.util.getString
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.resolveInt
import com.cobblemon.mod.common.util.writeString
import net.minecraft.client.Minecraft
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
@@ -74,12 +75,32 @@ class RunUpToFlightCompositeController : RideController {
if (state.activeController == flightController && entity.onGround() && state.timeTransitioned + 20 < entity.level().gameTime) {
//Pass the speed to the next state
val flightState = flightController.getState(entity, ::BirdAirState)
val groundState = landController.getState(entity, ::GenericLandState)
val flightState = flightController.getState( entity, ::BirdAirState )
val groundState = landController.getState( entity, ::GenericLandState )
groundState.currSpeed = flightState.currSpeed
groundState.rideVel = flightState.rideVel
groundState.stamina = flightState.stamina
getState(entity, ::RunUpToFlightCompositeState).activeController = landController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
getState(entity, ::RunUpToFlightCompositeState).let {
it.activeController = landController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
it.timeTransitioned = entity.level().gameTime
}
}
else if (state.activeController == landController && Minecraft.getInstance().options.keyJump.isDown && state.timeTransitioned + 20 < entity.level().gameTime) {
//Pass the speed to the next state
val flightState = flightController.getState( entity, ::BirdAirState )
val groundState = landController.getState( entity, ::GenericLandState )
flightState.currSpeed = groundState.currSpeed
flightState.rideVel = groundState.rideVel
flightState.stamina = groundState.stamina
getState(entity, ::RunUpToFlightCompositeState).let {
it.activeController = flightController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
it.timeTransitioned = entity.level().gameTime
}
}
return getActiveController(entity).speed(entity, driver)
@@ -101,6 +122,14 @@ class RunUpToFlightCompositeController : RideController {
return getActiveController(entity).angRollVel(entity, driver, deltaTime)
}
override fun rideFovMult(entity: PokemonEntity, driver: Player): Float {
return getActiveController(entity).rideFovMult(entity, driver)
}
override fun setRideBar(entity: PokemonEntity, driver: Player): Float {
return getActiveController(entity).setRideBar(entity, driver)
}
override fun canJump(entity: PokemonEntity, driver: Player): Boolean {
return getActiveController(entity).canJump(entity, driver)
}
@@ -111,19 +140,9 @@ class RunUpToFlightCompositeController : RideController {
jumpStrength: Int
): Vec3 {
val controller = getActiveController(entity)
if (controller == landController && jumpStrength >= getRuntime(entity).resolveInt(minimumJump)) {
val state = getState(entity, ::CompositeState)
val isJumping = Minecraft.getInstance().options.keyJump.isDown
//Pass the speed to the next state
val flightState = flightController.getState(entity, ::BirdAirState )
val groundState = landController.getState(entity, ::GenericLandState)
flightState.currSpeed = groundState.currSpeed
getState(entity, ::RunUpToFlightCompositeState).let {
it.activeController = flightController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
it.timeTransitioned = entity.level().gameTime
}
}
return controller.jumpForce(entity, driver, jumpStrength)
}
@@ -84,6 +84,7 @@ class RunUpToJetFlightCompositeController : RideController {
val groundState = landController.getState(entity, ::GenericLandState)
groundState.currSpeed = flightState.currSpeed
groundState.stamina = flightState.stamina
groundState.rideVel = flightState.rideVel
//Clear accumulated rotation input when transferring to ground input
flightState.currMouseXForce = 0.0
@@ -108,6 +109,7 @@ class RunUpToJetFlightCompositeController : RideController {
val groundState = landController.getState(entity, ::GenericLandState)
flightState.currSpeed = groundState.currSpeed
flightState.stamina = groundState.stamina
flightState.rideVel = groundState.rideVel
getState(entity, ::RunUpToFlightCompositeState).let {
it.activeController = flightController
@@ -8,54 +8,35 @@
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
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.api.riding.RidingOnPlayerSoundEventInstance
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 kotlin.math.absoluteValue
import com.cobblemon.mod.common.pokemon.riding.states.LandVehicleState
import com.cobblemon.mod.common.util.*
import net.minecraft.client.Minecraft
import net.minecraft.client.player.LocalPlayer
import net.minecraft.client.resources.sounds.Sound
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundEvents
import net.minecraft.util.Mth
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.max
import kotlin.math.min
import kotlin.math.*
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()
@@ -69,6 +50,9 @@ class VehicleLandController : RideController {
var reverseDriveFactor = "0.25".asExpression()
private set
var sound = "minecraft:item.elytra.flying".asExpression()
private set
var minimumSpeedToTurn = "0.1".asExpression()
private set
var rotationSpeed = "45/20".asExpression()
@@ -76,8 +60,20 @@ class VehicleLandController : RideController {
var lookYawLimit = "101".asExpression()
private set
var currSpeed = 0.0
// TODO: Tweak these stats to a workable state.
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 140.0, 20.0)".asExpression()
private set
var topSpeedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 1.0, 0.5)".asExpression()
private set
// Max accel is a whole 1.0 in 2 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 * 2.0)), (1.0 / (20.0 * 6.0)))".asExpression()
private set
// Between 60 seconds and 10 seconds at the lowest when at full speed.
var staminaExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 60.0, 10.0)".asExpression()
private set
var jumpExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 300.0, 128.0)".asExpression()
private set
@Transient
override val key: ResourceLocation = KEY
@@ -93,47 +89,132 @@ class VehicleLandController : RideController {
if (entity.isInWater || entity.isUnderWater) {
return@any false
}
/*
//This might not actually work, depending on what the yPos actually is. yPos of the middle of the entity? the feet?
if (it.y.toDouble() == (entity.position().y)) {
val blockState = entity.level().getBlockState(it.below())
return@any !(!blockState.isAir && blockState.fluidState.isEmpty)
return@any (!blockState.isAir && blockState.fluidState.isEmpty)
}
*/
true
}
}
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)
//Retrieve Vehicle state
val state = getState(entity, ::LandVehicleState)
//retrieve stats
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr) * 2
val accel = getRuntime(entity).resolveDouble(accelExpr) * 2
//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)
if( driver.zza > 0.0 && state.currSpeed < topSpeed)
{
state.currSpeed = min( state.currSpeed + accel , topSpeed)
}
else if ( driver.zza < 0.0 && state.currSpeed > 0.0)
{
//Decelerate is currently always half of accel
state.currSpeed = max( state.currSpeed - (accel * 2) , 0.0)
}
else{
state.currSpeed = max( state.currSpeed - (accel / 2.0) , 0.0)
}
return currSpeed.toFloat()
return state.currSpeed.toFloat()
}
override fun rotation(entity: PokemonEntity, driver: LivingEntity): Vec2 {
//Retrieve Vehicle state
val state = getState(entity, ::LandVehicleState)
val rotationDegrees = driver.xxa * getRuntime(entity).resolveFloat(this.rotationSpeed)
return Vec2(driver.xRot, entity.yRot - rotationDegrees)
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
)
val lp = driver as? LocalPlayer
lp?.let {
//val resourceLocation = ResourceLocation.tryParse(getRuntime(entity).resolveString(sound))
//val soundEvent = BuiltInRegistries.SOUND_EVENT.getOptional(resourceLocation).orElse(null)
val soundInstance = RidingOnPlayerSoundEventInstance(it, SoundEvents.AMBIENT_UNDERWATER_LOOP)
Minecraft.getInstance().soundManager.play(soundInstance)
}
}
//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 {
//Retrieve Vehicle state
val state = getState(entity, ::LandVehicleState)
val runtime = getRuntime(entity)
val driveFactor = runtime.resolveFloat(this.driveFactor)
var g = driver.zza * driveFactor
if (g <= 0.0f) {
g *= runtime.resolveFloat(this.reverseDriveFactor)
}
if (driver.xxa != 0F && g.absoluteValue < runtime.resolveFloat(this.minimumSpeedToTurn)) {
driver.xxa = 0F
val grav = -1.0
var velocity = Vec3(0.0,0.0,0.0)
//If the vehicle is currently drifting then do a "poor mans" rotation of the velocity vector to offset it
//from the front of the vehicle. This likely needs cleaned up to be
if( state.currDrift != 0.0){
//Drift speed should be slower the higher the drift is
//normalize the drift speed
val driftSpeed = (1.0 - ((abs(state.currDrift) - 1.0) / (5.0 - 1.0))) * 0.5 + 0.3
velocity = Vec3((1.0 * state.currSpeed) * (driftSpeed / 2.0) * -sign(state.currDrift) + min(state.driftBoost, 0.5) * 1.25,
grav,
(1.0 * state.currSpeed) * driftSpeed + min(state.driftBoost, 0.5) * 1.25)
}else{
velocity = Vec3(0.0 , grav, state.currSpeed + min(state.driftBoost, 0.5) * 1.25)
}
val velocity = Vec3(0.0 /* Maybe do drifting here later */, 0.0, g.toDouble() * currSpeed)
//tick down boost
state.driftBoost = max( state.driftBoost - (1.0 / (20.0 * 2)), 0.0)
return velocity
}
@@ -146,7 +227,7 @@ class VehicleLandController : RideController {
override fun clampPassengerRotation(entity: PokemonEntity, driver: LivingEntity) {
val f = Mth.wrapDegrees(driver.yRot - entity.yRot)
val runtime = getRuntime(entity)
val lookYawLimit = runtime.resolveFloat(this.lookYawLimit)
val lookYawLimit = 120.0f
val g = Mth.clamp(f, -lookYawLimit, lookYawLimit)
driver.yRotO += g - f
driver.yRot = driver.yRot + g - f
@@ -162,6 +243,27 @@ class VehicleLandController : RideController {
return Vec3(jumpVector[0].toDouble(), jumpVector[1].toDouble(), jumpVector[2].toDouble())
}
override fun inertia(entity: PokemonEntity ) : Double {
val state = getState(entity, ::LandVehicleState)
//val inertia = if(Minecraft.getInstance().options.keyJump.isDown()) 0.1 else 0.5
return 0.1
}
override fun rideFovMult(entity: PokemonEntity, driver: Player): Float {
val state = getState(entity, ::LandVehicleState)
//Only increase fov when actively using the boost.
val fovIncrease = (min( state.driftBoost, 0.5 ) * 0.3).toFloat()
return 1.0f + fovIncrease
}
override fun shouldRoll(entity: PokemonEntity): Boolean {
return false
}
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeString(this.speed.getString())
@@ -174,6 +276,11 @@ class VehicleLandController : RideController {
buffer.writeString(this.minimumSpeedToTurn.getString())
buffer.writeString(this.rotationSpeed.getString())
buffer.writeString(this.lookYawLimit.getString())
buffer.writeString(this.handlingExpr.getString())
buffer.writeString(this.staminaExpr.getString())
buffer.writeString(this.jumpExpr.getString())
buffer.writeString(this.topSpeedExpr.getString())
buffer.writeString(this.accelExpr.getString())
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
@@ -189,6 +296,11 @@ class VehicleLandController : RideController {
this.minimumSpeedToTurn = buffer.readString().asExpression()
this.rotationSpeed = buffer.readString().asExpression()
this.lookYawLimit = buffer.readString().asExpression()
this.handlingExpr = buffer.readString().asExpression()
this.staminaExpr = buffer.readString().asExpression()
this.jumpExpr = buffer.readString().asExpression()
this.topSpeedExpr = buffer.readString().asExpression()
this.accelExpr = buffer.readString().asExpression()
}
}
@@ -14,5 +14,6 @@ import net.minecraft.world.phys.Vec3
data class BirdAirState(val entity: PokemonEntity) : RidingState {
var currSpeed: Double = 0.0
var lastVelocity: Vec3 = Vec3(0.0, 0.0, 0.0)
var stamina: Float = 1.0f
var rideVel: Vec3 = Vec3.ZERO
}
@@ -0,0 +1,16 @@
/*
* 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
data class DolphinState(val entity: PokemonEntity) : RidingState {
var currSpeed: Double = 0.0
}
@@ -0,0 +1,33 @@
/*
* 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 LandVehicleState(val entity: PokemonEntity) : RidingState {
var currSpeed: Double = 0.0
var stamina: Float = 1.0f
var currMouseXForce: Double = 0.0
var currMouseYForce: Double = 0.0
//I am thinking this will either be a ranging from -1.0 to 1.0
//When drift is first activated while a turn is initiated it will go to (the sign of the turn)*0.5.
//Then you are able to change it between +-1.0 and 0.25 which will determine how much forced rotation
//The drift gives you. The second component is the offset of the velocity vector. Might simply be able
//to add an equal amount of x velocity as the current z velocity? Or maybe just a transformation to turn
//it to the right?? This will be the hardest part actually... Because if you add a similar one then the
//magnitude willbe increased? Maybe just half both and then that effectively slows it? idk.
var currDrift: Double = 0.0
//Determines how much boost is left.
var driftBoost: Double = 0.0
var turnSmoother: SmoothDouble = SmoothDouble()
}
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.util.adapters
import com.google.gson.*
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import java.lang.reflect.Type
/**
* Adapter for deserializing SoundEvent types.
*
* @author Jackowes
* @since March 5th, 2025
*/
object SoundEventAdapter : JsonDeserializer<SoundEvent> {
override fun deserialize(json: JsonElement, type: Type, context: JsonDeserializationContext): SoundEvent {
val soundId = json.asString
// Attempt to grab the sound resource location. If incorrect then return null
val resourceLocation = ResourceLocation.read(soundId).result().orElse(null)
?: throw JsonParseException("Invalid sound event resource location: $soundId")
// Attempt to retrieve the SoundEvent from the registry
return BuiltInRegistries.SOUND_EVENT.getOptional(resourceLocation)
.orElseThrow { JsonParseException("Unknown sound event: $soundId") }
}
}
@@ -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, 24, 0]
}
},
{
"name": "neck",
@@ -43,6 +43,67 @@
"eggCycles": 20,
"baseFriendship": 50,
"baseScale": 1,
"riding": {
"stats": {
"SPEED": {
"displayName": "Speed",
"description": "Its top speed",
"ranges": {
"LAND": "100-101"
}
},
"ACCELERATION": {
"displayName": "Acceleration",
"description": "How fast it can speed up",
"ranges": {
"LAND": "100-101"
}
},
"SKILL": {
"displayName": "Handling",
"description": "How fast it can turn",
"ranges": {
"LAND": "100-101"
}
},
"JUMP": {
"displayName": "Altitude",
"description": "How high it can fly",
"ranges": {
"LAND": "100-101"
}
},
"STAMINA": {
"displayName": "Fuel/Stamina",
"description": "How long it can sprint for.",
"ranges": {
"LAND": "100-101"
}
}
},
"controller": {
"key": "cobblemon:land/generic"
},
"seats": [
{
"offset": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"poseAnimations": [
{
"animations": [
{
"fileName": "trainer_generic",
"animationName": "animation.trainer_generic.ride_test"
}
]
}
]
}
]
},
"hitbox": {
"width": 1.3,
"height": 2.6,
@@ -42,6 +42,68 @@
"catchRate": 75,
"eggCycles": 20,
"baseFriendship": 50,
"riding": {
"stats": {
"SPEED": {
"displayName": "Speed",
"description": "Its top speed",
"ranges": {
"LAND": "100-101"
}
},
"ACCELERATION": {
"displayName": "Acceleration",
"description": "How fast it can speed up",
"ranges": {
"LAND": "100-101"
}
},
"SKILL": {
"displayName": "Handling",
"description": "How fast it can turn",
"ranges": {
"LAND": "100-101"
}
},
"JUMP": {
"displayName": "Altitude",
"description": "How high it can fly",
"ranges": {
"LAND": "100-101"
}
},
"STAMINA": {
"displayName": "Fuel/Stamina",
"description": "How long it can stay off the ground",
"ranges": {
"LAND": "100-101"
}
}
},
"controller": {
"key": "cobblemon:land/vehicle",
"sound": "minecraft:entity.elder_guardian.flop"
},
"seats": [
{
"offset": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"poseAnimations": [
{
"animations": [
{
"fileName": "trainer_generic",
"animationName": "animation.trainer_generic.ride_test"
}
]
}
]
}
]
},
"behaviour": {
"resting": {
"canSleep": true,