Compare commits

...
Author SHA1 Message Date
jackowes 91aac1d75e initial commit with the ground vehicles catching on blocks issue fixed 2025-08-11 21:04:23 -05:00
jackowes f4f6cbee38 Address MR comments 2025-08-10 02:02:49 -05:00
jackowes 1664b917f8 Dolphin time 2025-08-08 16:48:07 -05:00
Test 55587908a2 update state icons/ride controls overlay 2025-08-07 22:05:09 -04:00
jackowes e1ed483d67 Remove altitude limits on rides 2025-08-07 11:49:59 -05:00
jackowes c460bac177 Accessibility rides for jet and bird. Need hooked up to cobble settings/config and auto changed out if accessibility rides are enabled. Further refinement needed but waiting for the rolling counterparts to be polished first. 2025-08-06 20:29:08 -05:00
jackowes ad6771f8f4 minekart improvements 2025-08-05 21:34:52 -05:00
jackowes ad1a68cefe Add base of minekart behaviour and fix player head movement flag for a few rides 2025-08-05 16:19:18 -05:00
jackowes e8ab8fda71 Fix sound attenuation, inverse input_right() animation data, and properly destroy sounds after dismounting a pokmeon. 2025-08-05 10:09:00 -05:00
jackowes 7def27e437 dupe function from merge 2025-08-04 20:37:24 -05:00
jackowes f3db0ed61d hover anim tests 2025-08-04 20:29:10 -05:00
jackowes fdcff83a2d Merge branch 'main' into riding-controllers-expanded
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/land/VehicleBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/liquid/BoatBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
#	common/src/main/resources/assets/cobblemon/sounds.json
#	common/src/main/resources/data/cobblemon/species/generation1/charizard.json
2025-08-04 20:11:56 -05:00
jackowes 8815758bc0 Adjust all ride params after double tick error. Mirror deltamovement scale to ridevel to fix the catching on terrain issue. Scale smoothly between sprinting turn rate and walking turn rate on horse. 2025-08-04 20:00:34 -05:00
jackowes 1aaea961fb Fix cursed golurk jsons 2025-08-02 02:59:52 -05:00
jackowes 8253b96a67 Add multiplayer support for driver input tracking 2025-08-02 01:42:15 -05:00
jackowes 6593fcc250 fix jittery mons 2025-08-01 00:59:09 -05:00
jackowes f51376f9c3 quick fixes 2025-07-31 21:58:23 -05:00
jackowes 23ecc3dd05 Merge branch 'main' into riding-controllers-expanded
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/RidingBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/RidingController.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/GenericSwimBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/air/BirdBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/air/GliderBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/air/JetBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/land/HorseBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/land/VehicleBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/liquid/BurstBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
#	common/src/main/resources/assets/cobblemon/bedrock/pokemon/animations/0967_cyclizar/cyclizar.animation.json
#	common/src/main/resources/assets/cobblemon/bedrock/pokemon/models/0967_cyclizar/cyclizar.geo.json
#	common/src/main/resources/assets/cobblemon/bedrock/pokemon/posers/0967_cyclizar/cyclizar.json
#	common/src/main/resources/data/cobblemon/species/generation1/charizard.json
#	common/src/main/resources/data/cobblemon/species/generation9/cyclizar.json
2025-07-31 21:25:01 -05:00
jackowes 9e6f27162e quick tweak 2025-07-31 21:13:06 -05:00
jackowes 6c279e0986 vehicle tweaks 2025-07-31 21:13:06 -05:00
jackowes 76d1291180 vehicle v3 becoming 2025-07-31 21:13:06 -05:00
jackowes 627f09010b Start of vehicle v3 2025-07-31 21:13:03 -05:00
jackowes 9afd0e6fc3 nope 2025-07-31 21:11:28 -05:00
jackowes 1ea361cce9 I cannot for fine the culprit of this increase in vertical impulse when moving as opposed to standing still. :(. Also rocket improvements. 2025-07-31 21:11:28 -05:00
jackowes 41bf22c43a More rocket improvements
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/GenericSwimBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/RocketBehaviour.kt
2025-07-31 21:11:24 -05:00
jackowes 71989292d3 Various rocket improvements including: lower boost turning speed, turn momentum, behaviour switch time decrease, temp golurk flight animation tweaks for fluidity (need to check with gingle), also tweaked the feel and air resistance of the ride as a whole 2025-07-31 21:10:35 -05:00
jackowes f46b13da6a Quick fix to velocity zeroing for dimensions that horse and rocket don't care about. 2025-07-31 21:10:35 -05:00
JacksonB ab1f169fcc add forgotten rocket loop 2025-05-20 22:35:59 -05:00
JacksonB ce5928e783 Add rocket sounds 2025-05-20 22:35:15 -05:00
JacksonB e3e40627e8 Beginnings of rocket controller (for real this time) 2025-05-16 21:41:20 -05:00
JacksonB 140158f5bb Default riding sounds to only be heard by passengers and default sounds to not muffle or reduce in volume when behind a block. Also move the attenuation logic out of RideLoopSound and have openAL handle all attenuation models. 2025-05-11 19:33:00 -05:00
JacksonB aeec991e7b add license 2025-04-27 22:44:06 -05:00
JacksonB 7751b13611 merge main 2025-04-27 18:50:35 -05:00
JacksonB e0cbc0bdc3 Riding Sound refactor to support and arbitrary number of sounds per behaviour per species. Implemented a fade in and out for any rideSound instance and added support for a handful of configurations when specifying the sound. 2025-04-27 16:52:04 -05:00
JacksonB 2f64ab1018 merge main 2025-04-26 17:33:09 -05:00
JacksonB 6c74d30ce8 Fix some filter issues and add saucer noise for magnezone 2025-04-26 17:26:35 -05:00
JacksonB 25956d5f23 temp commit for sound fixes 2025-04-25 16:16:15 -05:00
JacksonB f6832cd44c temp commit for cone raycasting 2025-04-25 11:46:33 -05:00
JacksonB 313e9dadb6 Add soundOcclusion, doppler effect, and custom non linear sound attenuation. 2025-04-25 09:05:43 -05:00
JacksonB 1d171268ed Behaviours can specify a looping ride sound and control its volume and pitch dynamically. Still need to allow multiple sound instances and also clean up my wip code 2025-04-23 00:34:13 -05:00
72 changed files with 12901 additions and 1618 deletions

No files matched your search

+1
View File
@@ -240,6 +240,7 @@
- Offset in EmitterShape now ignores scale to be more like Blockbench by default. You can get this behaviour back by adding `"scale": true` in the `attachment_options` property in most EmitterShapes.
- Not specifying a dex in `/pokedex printcalculations {player} {dex}` will now print the National Dex statistics instead of showing all dexes. `/pokedex printcalculations {player} all` is how to view all dex statistics in one command.
- Removed Braised Vivichoke
- Updated Pokémon state icons shown in party interfaces
### Fixes
- Fixed game crashing when removing national pokedex using datapacks
@@ -0,0 +1,15 @@
/*
* 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.duck;
import org.joml.Vector3f;
public interface ServerEntityDuck {
void setDriverInput(Vector3f driverInput);
}
@@ -12,9 +12,11 @@ import com.cobblemon.mod.common.CobblemonNetwork;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.api.net.NetworkPacket;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.duck.ServerEntityDuck;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.mixin.accessor.ChunkMapAccessor;
import com.cobblemon.mod.common.mixin.accessor.TrackedEntityAccessor;
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateDriverInputPacket;
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateOrientationPacket;
import com.cobblemon.mod.common.net.messages.client.pokemon.update.ClientboundUpdateRidingStatePacket;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
@@ -26,6 +28,7 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerPlayerConnection;
import net.minecraft.world.entity.Entity;
import org.joml.Matrix3f;
import org.joml.Vector3f;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -38,7 +41,7 @@ import java.util.Set;
import java.util.function.Consumer;
@Mixin(ServerEntity.class)
public abstract class ServerEntityMixin {
public abstract class ServerEntityMixin implements ServerEntityDuck {
@Shadow
@Final
@@ -50,11 +53,16 @@ public abstract class ServerEntityMixin {
private Matrix3f cobblemon$lastSentOrientation;
@Unique
private boolean cobblemon$lastSentActive;
@Unique
private Vector3f cobblemon$driverInput;
@Unique
private Vector3f cobblemon$lastSentDriverInput;
@Inject(method = "sendChanges", at = @At("TAIL"))
private void cobblemon$sendChanges(CallbackInfo ci) {
cobblemon$sendOrientationChanges();
cobblemon$sendRidingStateChanges();
cobblemon$sendDriverInput();
}
private void cobblemon$sendRidingStateChanges() {
@@ -82,6 +90,17 @@ public abstract class ServerEntityMixin {
cobblemon$broadcast(new ClientboundUpdateOrientationPacket(currOrientation, currActive, entity.getId()));
}
private void cobblemon$sendDriverInput() {
if (!(this.entity instanceof ServerPlayer serverPlayer)) return;
if(!(serverPlayer.getVehicle() instanceof PokemonEntity)) return;
if(cobblemon$driverInput == null) return;
// If no change in input is detected then don't send a new packet
if (cobblemon$driverInput.equals(cobblemon$lastSentDriverInput)) return;
cobblemon$lastSentDriverInput = cobblemon$driverInput;
cobblemon$broadcast(new ClientboundUpdateDriverInputPacket(cobblemon$driverInput, entity.getId()));
}
private void cobblemon$broadcast(NetworkPacket<?> packet) {
if (!(entity.level() instanceof ServerLevel level)) return;
ChunkMap chunkMap = level.getChunkSource().chunkMap;
@@ -97,5 +116,9 @@ public abstract class ServerEntityMixin {
}
}
@Override
public void setDriverInput(Vector3f driverInput) {
cobblemon$driverInput = driverInput;
}
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.mixin.accessor;
import net.minecraft.server.level.ServerEntity;
import net.minecraft.server.network.ServerPlayerConnection;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@@ -18,4 +19,7 @@ import java.util.Set;
public interface TrackedEntityAccessor {
@Accessor("seenBy")
Set<ServerPlayerConnection> getSeenBy();
@Accessor("serverEntity")
ServerEntity getServerEntity();
}
@@ -19,6 +19,7 @@ import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel;
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.net.messages.server.orientation.ServerboundUpdateOrientationPacket;
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateDriverInputPacket;
import com.mojang.authlib.GameProfile;
import net.minecraft.client.CameraType;
import net.minecraft.client.Minecraft;
@@ -83,6 +84,13 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
controllable.getOrientationController().setActive(shouldUseCustomOrientation);
}
@Inject(method = "rideTick", at = @At("HEAD"))
private void cobblemon$updateDriverInputRideTick(CallbackInfo ci) {
if (Minecraft.getInstance().player != (Object)this) return;
if (!(this.getVehicle() instanceof PokemonEntity pokemonEntity)) return;
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateDriverInputPacket(this.xxa, this.zza, this.jumping, this.isShiftKeyDown()));
}
@Unique
private boolean cobblemon$shouldUseCustomOrientation(LocalPlayer player) {
var playerVehicle = player.getVehicle();
@@ -127,7 +135,7 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
return super.pick(hitDistance, partialTicks, hitFluids);
}
Vec3 locatorOffset = new Vec3(locator.getMatrix().getTranslation(new Vector3f()));
Vec3 locatorOffset = locator != null ? new Vec3(locator.getMatrix().getTranslation(new Vector3f())) : Vec3.ZERO;
OrientationController controller = this.getOrientationController();
@@ -15,6 +15,7 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Entity.class)
@@ -29,4 +30,19 @@ public abstract class EntityMixin {
}
}
@Redirect(
method = "collide(Lnet/minecraft/world/phys/Vec3;)Lnet/minecraft/world/phys/Vec3;",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/entity/Entity;onGround()Z"
)
)
private boolean cobblemon$forceOnGroundForStepUp(Entity entity) {
if (entity instanceof PokemonEntity pokemon && entity.hasControllingPassenger()) {
return true;
}
return entity.onGround();
}
}
@@ -41,6 +41,7 @@ import com.cobblemon.mod.common.client.net.pasture.UpdatePastureConflictFlagHand
import com.cobblemon.mod.common.client.net.pokedex.ServerConfirmedRegisterHandler
import com.cobblemon.mod.common.client.net.pokemon.update.ClientboundUpdateRidingStateHandler
import com.cobblemon.mod.common.client.net.pokemon.update.PokemonUpdatePacketHandler
import com.cobblemon.mod.common.client.net.riding.ClientboundUpdateDriverInputHandler
import com.cobblemon.mod.common.client.net.settings.OpenCobblemonConfigEditorHandler
import com.cobblemon.mod.common.client.net.settings.ServerSettingsPacketHandler
import com.cobblemon.mod.common.client.net.sound.UnvalidatedPlaySoundS2CPacketHandler
@@ -83,6 +84,7 @@ import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityP
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticlePacket
import com.cobblemon.mod.common.net.messages.client.npc.CloseNPCEditorPacket
import com.cobblemon.mod.common.net.messages.client.npc.OpenNPCEditorPacket
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateDriverInputPacket
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateOrientationPacket
import com.cobblemon.mod.common.net.messages.client.pasture.ClosePasturePacket
import com.cobblemon.mod.common.net.messages.client.pasture.OpenPasturePacket
@@ -173,6 +175,7 @@ import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetMarkingsPa
import com.cobblemon.mod.common.net.messages.server.pokemon.update.ServerboundUpdateRidingStatePacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.evolution.AcceptEvolutionPacket
import com.cobblemon.mod.common.net.messages.server.riding.DismountPokemonPacket
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateDriverInputPacket
import com.cobblemon.mod.common.net.messages.server.starter.RequestStarterScreenPacket
import com.cobblemon.mod.common.net.messages.server.storage.SwapPCPartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.party.MovePartyPokemonPacket
@@ -223,6 +226,7 @@ import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetActiveMarkH
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetMarkingsHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.ServerboundUpdateRidingStateHandler
import com.cobblemon.mod.common.net.serverhandling.riding.DismountPokemonPacketHandler
import com.cobblemon.mod.common.net.serverhandling.riding.DriverInputPacketHandler
import com.cobblemon.mod.common.net.serverhandling.starter.RequestStarterScreenHandler
import com.cobblemon.mod.common.net.serverhandling.starter.SelectStarterPacketHandler
import com.cobblemon.mod.common.net.serverhandling.storage.BenchMoveHandler
@@ -465,6 +469,7 @@ object CobblemonNetwork {
// Riding
list.add(PacketRegisterInfo(ClientboundUpdateRidingStatePacket.ID, ClientboundUpdateRidingStatePacket::decode, ClientboundUpdateRidingStateHandler))
list.add(PacketRegisterInfo(ClientboundUpdateDriverInputPacket.ID, ClientboundUpdateDriverInputPacket::decode, ClientboundUpdateDriverInputHandler))
return list
}
@@ -571,6 +576,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(ServerboundUpdateRidingStatsPacket.ID, ServerboundUpdateRidingStatsPacket::decode, ServerboundUpdateRidingStatsHandler))
list.add(PacketRegisterInfo(ServerboundUpdateRidingStatRangePacket.ID, ServerboundUpdateRidingStatRangePacket::decode, ServerboundUpdateRidingStatRangeHandler))
list.add(PacketRegisterInfo(DismountPokemonPacket.ID, DismountPokemonPacket::decode, DismountPokemonPacketHandler))
list.add(PacketRegisterInfo(ServerboundUpdateDriverInputPacket.ID, ServerboundUpdateDriverInputPacket::decode, DriverInputPacketHandler))
// Cooking
list.add(PacketRegisterInfo(ToggleCookingPotLidPacket.ID, ToggleCookingPotLidPacket::decode, ToggleCookingPotLidHandler))
@@ -1645,6 +1645,8 @@ object MoLangFunctions {
map.put("get_alt_pose") { StringValue(pokemonEntity.getAltPose()) }
map.put("is_gliding") { DoubleValue(pokemonEntity.isUsingAltPose(cobblemonResource("gliding"))) }
map.put("is_sprinting") { DoubleValue(pokemonEntity.isUsingAltPose(cobblemonResource("sprinting"))) }
map.put("is_drifting") { DoubleValue(pokemonEntity.isUsingAltPose(cobblemonResource("drifting"))) }
map.put("is_powered_drifting") { DoubleValue(pokemonEntity.isUsingAltPose(cobblemonResource("powered_drifting"))) }
map.put("in_air") { DoubleValue(pokemonEntity.isUsingAltPose(cobblemonResource("in_air"))) }
map.put("is_wild") { DoubleValue(pokemonEntity.ownerUUID == null) }
map.put("is_in_party") { DoubleValue(pokemonEntity.pokemon.storeCoordinates.get()?.store is PartyStore) }
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.api.riding.behaviour.types.air.*
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.MinekartBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BoatBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BurstBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.DolphinBehaviour
@@ -22,15 +23,19 @@ object RidingBehaviours {
init {
register(BirdBehaviour.KEY, BirdBehaviour())
register(BirdNoRollBehaviour.KEY, BirdNoRollBehaviour())
register(DolphinBehaviour.KEY, DolphinBehaviour())
register(HorseBehaviour.KEY, HorseBehaviour())
register(BoatBehaviour.KEY, BoatBehaviour())
register(GliderBehaviour.KEY, GliderBehaviour())
register(HelicopterBehaviour.KEY, HelicopterBehaviour())
register(JetBehaviour.KEY, JetBehaviour())
register(JetNoRollBehaviour.KEY, JetNoRollBehaviour())
register(BurstBehaviour.KEY, BurstBehaviour())
register(VehicleBehaviour.KEY, VehicleBehaviour())
register(MinekartBehaviour.KEY, MinekartBehaviour())
register(HoverBehaviour.KEY, HoverBehaviour())
register(RocketBehaviour.KEY, RocketBehaviour())
register(CompositeBehaviour.KEY, CompositeBehaviour())
}
@@ -107,14 +107,6 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
upForce -= 0.3
}
val altitudeLimit = vehicle.runtime.resolveDouble(settings.jumpExpr)
//Only limit altitude if altitude is not infinite
if (!vehicle.runtime.resolveBoolean(settings.infiniteAltitude)) {
//Provide a hard limit on altitude
upForce = if (vehicle.y >= altitudeLimit && upForce > 0) 0.0 else upForce
}
val velocity = Vec3(state.rideVelocity.get().x , upForce, forwardForce)
return velocity
@@ -129,12 +121,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
val glideTopSpeed = vehicle.runtime.resolveDouble(settings.glidespeedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val staminaStat = vehicle.runtime.resolveDouble(settings.staminaExpr)
var glideSpeedChange = 0.0
val currSpeed = state.rideVelocity.get().length()
//Flag for determining if player is actively inputting
var activeInput = false
var newVelocity = Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, state.rideVelocity.get().z)
@@ -469,7 +456,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
state: BirdState,
vehicle: PokemonEntity
): Boolean {
return true
return false
}
override fun getRideSounds(
@@ -0,0 +1,518 @@
/*
* 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.behaviour.types.air
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import org.joml.Vector3f
import kotlin.math.*
class BirdNoRollBehaviour : RidingBehaviour<BirdNoRollSettings, BirdNoRollState> {
companion object {
val KEY = cobblemonResource("air/birdnoroll")
}
override val key: ResourceLocation = KEY
override fun getRidingStyle(settings: BirdNoRollSettings, state: BirdNoRollState): RidingStyle {
return RidingStyle.AIR
}
val poseProvider = PoseProvider<BirdNoRollSettings, BirdNoRollState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > 0.2 })
override fun isActive(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity): Boolean {
return true
}
override fun pose(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player
): Float {
return state.rideVelocity.get().length().toFloat()
}
override fun rotation(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
var yVel = 0.0
var leftForce = 0.0
var upForce = 0.0
var forwardForce = 0.0
//Perform ride velocity update
calculateRideSpaceVel(settings, state, vehicle, driver)
//Translate ride space velocity to world space velocity.
val controller = (driver as? OrientationControllable)?.orientationController
if (controller != null) {
upForce += -1.0 * sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().z
forwardForce += cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().z
upForce += cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().y
forwardForce += sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().y
}
//Bring the ride out of the sky when stamina is depleted.
if (state.stamina.get() <= 0.0) {
upForce -= 0.3
}
val velocity = Vec3(state.rideVelocity.get().x , upForce, forwardForce)
return velocity
}
/*
* Calculates the change in the ride space vector due to player input and ride state
*/
fun calculateRideSpaceVel(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity, driver: Player) {
//retrieve stats
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val glideTopSpeed = vehicle.runtime.resolveDouble(settings.glidespeedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val staminaStat = vehicle.runtime.resolveDouble(settings.staminaExpr)
var glideSpeedChange = 0.0
val currSpeed = state.rideVelocity.get().length()
//Flag for determining if player is actively inputting
var activeInput = false
var newVelocity = Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, state.rideVelocity.get().z)
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0) {
//make sure it can't exceed top speed
val forwardInput = when {
driver.zza > 0 && newVelocity.z > topSpeed -> 0.0
driver.zza < 0 && newVelocity.z < (-topSpeed / 3.0) -> 0.0
else -> driver.zza.sign
}
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble())))
activeInput = true
}
val controller = (driver as? OrientationControllable)?.orientationController
if (controller != null) {
//Base glide speed change on current pitch of the ride.
glideSpeedChange = sin(Math.toRadians(controller.pitch.toDouble()))
glideSpeedChange = glideSpeedChange * 0.25
//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 )
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
lerp( newVelocity.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)
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
min(newVelocity.z + ((0.0166 * 2) * glideSpeedChange), glideTopSpeed)
)
}
}
//Vertical movement based on driver input.
val vertTopSpeed = topSpeed / 2.0
val vertInput = when {
driver.jumping && state.rideVelocity.get().length() < 0.3 -> 1.0
driver.isShiftKeyDown && state.rideVelocity.get().length() < 0.3 -> -1.0
else -> 0.0
}
if (vertInput != 0.0 && state.stamina.get() > 0.0) {
newVelocity = Vec3(
newVelocity.x,
(newVelocity.y + (accel * vertInput)).coerceIn(-vertTopSpeed, vertTopSpeed),
newVelocity.z)
activeInput = true
}
else {
newVelocity = Vec3(
newVelocity.x,
lerp(newVelocity.y, 0.0, vertTopSpeed / 20.0),
newVelocity.z)
}
//Check if the ride should be gliding
if (driver.isLocalPlayer) {
if (activeInput && state.stamina.get() > 0.0) {
state.gliding.set(false)
} else {
state.gliding.set(true)
}
}
//air resistance
if( abs(newVelocity.z) > 0) {
newVelocity = newVelocity.subtract(0.0, 0.0, min(0.0015 * newVelocity.z.sign, newVelocity.z))
}
state.rideVelocity.set(newVelocity)
}
override fun angRollVel(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
//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
//TODO: Tie handling into yaw rate
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//This rotation limit will be used to know if the ride has exceeded it and its needs correcting.
val rotMin = 15.0
val rotLimit = max(handling * sqrt(normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed)), rotMin)
var yawDeltaDeg = deltaTime * handling * sin(Math.toRadians(controller.roll.toDouble())) //sin(Math.toRadians(controller.roll.toDouble())) * abs(cos(Math.toRadians(controller.pitch.toDouble())))
val trueYawDelt = yawDeltaDeg * abs(cos(Math.toRadians(controller.pitch.toDouble())))
//val pitchedYawDelt = yawDeltaDeg - trueYawDelt
controller.applyGlobalYaw(trueYawDelt.toFloat())
//Calculate correcting roll force.
val correctionRate = 5.0
if(abs(controller.roll) > rotLimit && state.rideVelocity.get().length() < 0.2) {
state.currRollCorrectionForce.set(
state.currRollCorrectionForce.get() + ((abs(controller.roll) - rotLimit) / 180 - rotLimit) * controller.roll.sign * correctionRate * deltaTime * 0.01
)
} else {
state.currRollCorrectionForce.set(
//reduce rollCorrection gradually
lerp(state.currRollCorrectionForce.get(), 0.0, deltaTime * 0.98)
)
}
//yaw, pitch, roll
return Vec3(0.0, 0.0, state.currRollCorrectionForce.get())
}
override fun rotationOnMouseXY(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
//TODO: hook up handling in parity with normal bird
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//Smooth out mouse input.
val smoothingSpeed = 4.0
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
val mouseXc = (mouseX).coerceIn(-60.0, 60.0)
val mouseYc = (mouseY).coerceIn(-60.0, 60.0)
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1 * invertRoll, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1 * invertPitch, deltaTime * smoothingSpeed);
//Give the ability to yaw with x mouse input when at low speeds.
val yawForce = xInput * ( 1.0 - min(sqrt(normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed)), 0.5))
//Apply yaw globally as we don't want roll or pitch changes due to local yaw when looking up or down.
controller.applyGlobalYaw(yawForce.toFloat())
var pitchRot = yInput
// Pitch up globally
if (abs(controller.pitch + pitchRot) >= 89.5 ) {
pitchRot = 0.0
mouseYSmoother.reset()
} else {
controller.applyGlobalPitch(pitchRot.toFloat() * -1.0f)
}
//yaw, pitch, roll
return Vec3.ZERO
}
/*
* 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.
*/
private 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)
}
override fun canJump(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player
): Float {
return (state.stamina.get() / 1.0f)
}
override fun jumpForce(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val glideTopSpeed = vehicle.runtime.resolveDouble(settings.glidespeedExpr)
//Must I ensure that topspeed is greater than minimum?
val normalizedGlideSpeed = normalizeVal(state.rideVelocity.get().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(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun useRidingAltPose(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
if (state.gliding.get()) {
return cobblemonResource("gliding")
}
return cobblemonResource("no_pose")
}
override fun inertia(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity): Double {
return 0.5
}
override fun shouldRoll(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity): Boolean {
return true
}
override fun turnOffOnGround(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun getRideSounds(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: BirdNoRollSettings) = BirdNoRollState()
}
class BirdNoRollSettings : RidingBehaviourSettings {
override val key = BirdNoRollBehaviour.KEY
var infiniteAltitude: Expression = "false".asExpression()
private set
var infiniteStamina: Expression = "false".asExpression()
private set
//max y level for the ride
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 200.0, 128.0)".asExpression()
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 135.0, 45.0)".asExpression()
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 1.0, 0.35)".asExpression()
var accelerationExpr: Expression = "q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 3.0)), (1.0 / (20.0 * 8.0)))".asExpression()
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 120.0, 20.0)".asExpression()
var glidespeedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 2.0, 1.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(infiniteAltitude)
buffer.writeExpression(infiniteStamina)
buffer.writeExpression(glidespeedExpr)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteAltitude = buffer.readExpression()
infiniteStamina = buffer.readExpression()
glidespeedExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
}
}
class BirdNoRollState : RidingBehaviourState() {
var gliding = ridingState(false, Side.CLIENT)
var lastGlide = ridingState(-100L, Side.CLIENT)
var currRollCorrectionForce = ridingState(0.0, Side.CLIENT)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeFloat(stamina.get())
buffer.writeBoolean(gliding.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
gliding.set(buffer.readBoolean(), forced = true)
}
override fun reset() {
super.reset()
gliding.set(false, forced = true)
lastGlide.set(-100L, forced = true)
currRollCorrectionForce.set(0.0, forced = true)
}
override fun toString(): String {
return "BirdNoRollState(rideVelocity=${rideVelocity.get()}, stamina=${stamina.get()}, gliding=${gliding.get()})"
}
override fun copy() = BirdNoRollState().also {
it.rideVelocity.set(this.rideVelocity.get(), forced = true)
it.stamina.set(this.stamina.get(), forced = true)
it.gliding.set(this.gliding.get(), forced = true)
it.lastGlide.set(this.lastGlide.get(), forced = true)
it.currRollCorrectionForce.set(this.currRollCorrectionForce.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is BirdNoRollState) return false
if (previous.gliding.get() != gliding.get()) return true
return super.shouldSync(previous)
}
}
@@ -138,14 +138,6 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
//If stamina has run out then initiate forced glide down.
upForce = if (state.stamina.get() > 0.0) upForce else -0.7
val altitudeLimit = vehicle.runtime.resolveDouble(settings.jumpExpr)
//Only limit altitude if altitude is not infinite
if (!vehicle.runtime.resolveBoolean(settings.infiniteAltitude)) {
//Provide a hard limit on altitude
upForce = if (vehicle.y >= altitudeLimit && upForce > 0) 0.0 else upForce
}
val velocity = Vec3(0.0, upForce, forwardForce)
return velocity
@@ -162,17 +154,11 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
) {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val altitudeLimit = vehicle.runtime.resolveDouble(settings.jumpExpr)
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
val speed = state.rideVelocity.get().length()
//Give no altitude limit if at max jump stat.
val pushingHeightLimit = if (vehicle.runtime.resolveBoolean(settings.infiniteStamina)) false
else (vehicle.y >= altitudeLimit && vehicle.xRot <= 0)
//speed up and slow down based on input
if (driver.zza > 0.0 && speed < topSpeed && state.stamina.get() > 0.0f && !pushingHeightLimit) {
if (driver.zza > 0.0 && speed < topSpeed && state.stamina.get() > 0.0f) {
//modify acceleration to be slower when at closer speeds to top speed
val accelMod = max(-(normalizeSpeed(speed, minSpeed, topSpeed)) + 1, 0.0)
state.rideVelocity.set(
@@ -182,7 +168,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
min(state.rideVelocity.get().z + (accel * accelMod), topSpeed)
)
)
} else if (driver.zza >= 0.0 && (state.stamina.get() == 0.0f || pushingHeightLimit)) {
} else if (driver.zza >= 0.0 && (state.stamina.get() == 0.0f)) {
state.rideVelocity.set(
Vec3(
state.rideVelocity.get().x,
@@ -427,9 +413,6 @@ class JetSettings : RidingBehaviourSettings {
var infiniteStamina: Expression = "false".asExpression()
private set
var infiniteAltitude: Expression = "false".asExpression()
private set
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 300.0, 128.0)".asExpression()
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 140.0, 20.0)".asExpression()
@@ -451,7 +434,6 @@ class JetSettings : RidingBehaviourSettings {
buffer.writeExpression(minSpeed)
buffer.writeExpression(handlingYawExpr)
buffer.writeExpression(infiniteStamina)
buffer.writeExpression(infiniteAltitude)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(speedExpr)
@@ -465,7 +447,6 @@ class JetSettings : RidingBehaviourSettings {
minSpeed = buffer.readExpression()
handlingYawExpr = buffer.readExpression()
infiniteStamina = buffer.readExpression()
infiniteAltitude = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
speedExpr = buffer.readExpression()
@@ -501,8 +482,6 @@ class JetState : RidingBehaviourState() {
it.currSpeed.set(currSpeed.get(), forced = true)
it.stamina.set(stamina.get(), forced = true)
it.rideVelocity.set(rideVelocity.get(), forced = true)
// it.currMouseXForce.set(currMouseXForce.get(), forced = true)
// it.currMouseYForce.set(currMouseYForce.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
@@ -0,0 +1,477 @@
/*
* 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.behaviour.types.air
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.*
class JetNoRollBehaviour : RidingBehaviour<JetNoRollSettings, JetNoRollState> {
companion object {
val KEY = cobblemonResource("air/jetnoroll")
}
override val key = KEY
override fun getRidingStyle(settings: JetNoRollSettings, state: JetNoRollState): RidingStyle {
return RidingStyle.AIR
}
val poseProvider = PoseProvider<JetNoRollSettings, JetNoRollState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > 0.1 })
override fun isActive(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
//Need to check other fluids
if (vehicle.isInWater || vehicle.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() == (vehicle.position().y)) {
val blockState = vehicle.level().getBlockState(it.below())
return@any !(!blockState.isAir && blockState.fluidState.isEmpty)
}
true
}
}
override fun pose(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity, driver: Player): Float {
//retrieve stats
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val staminaStat = vehicle.runtime.resolveDouble(settings.staminaExpr)
//retrieve minSpeed
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
//TODO: Reintroduce stamina drain once stats start to be polished and tweaked
state.stamina.set(1.0f)
return state.rideVelocity.get().length().toFloat()
}
//TODO: Move these functions to a riding util class.
/*
* 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.
*/
private 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)
}
override fun rotation(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
var upForce = 0.0
var forwardForce = 0.0
val controller = (driver as? OrientationControllable)?.orientationController
//Calculate ride space velocity
calculateRideSpaceVel(settings, state, vehicle, driver)
//Translate ride space velocity to world space velocity.
if (controller != null) {
upForce = -1.0 * sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().z
forwardForce = cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().z
}
//If stamina has run out then initiate forced glide down.
upForce = if (state.stamina.get() > 0.0) upForce else -0.7
val velocity = Vec3(0.0, upForce, forwardForce)
return velocity
}
/*
* Calculates the change in the ride space vector due to player input and ride state
*/
private fun calculateRideSpaceVel(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player
) {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
val speed = state.rideVelocity.get().length()
//speed up and slow down based on input
if (driver.zza > 0.0 && speed < topSpeed && state.stamina.get() > 0.0f) {
//modify acceleration to be slower when at closer speeds to top speed
val accelMod = max(-(normalizeSpeed(speed, minSpeed, topSpeed)) + 1, 0.0)
state.rideVelocity.set(
Vec3(
state.rideVelocity.get().x,
state.rideVelocity.get().y,
min(state.rideVelocity.get().z + (accel * accelMod), topSpeed)
)
)
} else if (driver.zza >= 0.0 && (state.stamina.get() == 0.0f)) {
state.rideVelocity.set(
Vec3(
state.rideVelocity.get().x,
state.rideVelocity.get().y,
max(state.rideVelocity.get().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).pow(2) * 4, 0.1)
//Decelerate currently always a constant half of max acceleration.
state.rideVelocity.set(
Vec3(
state.rideVelocity.get().x,
state.rideVelocity.get().y,
max(state.rideVelocity.get().z - ((accel * deccelMod) / 2), minSpeed)
)
)
}
}
override fun angRollVel(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
// //Cap at a rate of 5fps so frame skips dont lead to huge jumps
// val cappedDeltaTime = min(deltaTime, 0.2)
//
// //Get handling in degrees per second
// val yawRotRate = vehicle.runtime.resolveDouble(settings.handlingYawExpr)
//
// //Base the change off of deltatime.
// var handlingYaw = yawRotRate * (cappedDeltaTime)
//
// //apply stamina debuff if applicable
// handlingYaw *= if (state.stamina.get() > 0.0) 1.0 else 0.5
//
// //A+D to yaw
// val yawForce = driver.xxa * handlingYaw * -1
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//TODO: figure out a cleaner solution to this issue of large jumps when skipping frames or lagging
//Cap at a rate of 5fps so frame skips dont lead to huge jumps
val cappedDeltaTime = min(deltaTime, 0.2)
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
// Accumulate the mouse input
state.currMouseXForce.set((state.currMouseXForce.get() + (0.0015 * mouseX * invertRoll)).coerceIn(-1.0, 1.0))
state.currMouseYForce.set((state.currMouseYForce.get() + (0.0015 * mouseY * invertPitch)).coerceIn(-1.0, 1.0))
//Get handling in degrees per second
var handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
//convert it to delta time
handling *= (cappedDeltaTime)
//apply stamina debuff if applicable
handling *= if (state.stamina.get() > 0.0) 1.0 else 0.5
var pitchRot = handling * state.currMouseYForce.get()
// Yaw
val yawRot = handling * 0.5 * state.currMouseXForce.get()
val controller = (driver as OrientationControllable).orientationController
controller.applyGlobalYaw(yawRot.toFloat())
if (abs(controller.pitch + pitchRot) >= 89.5 ) {
pitchRot = 0.0
state.currMouseYForce.set(0.0)
mouseYSmoother.reset()
} else {
controller.applyGlobalPitch(pitchRot.toFloat() * -1.0f)
}
val mouseRotation = Vec3(0.0, pitchRot, 0.0)
// Have accumulated input begin decay when no input detected
if (abs(mouseX) == 0.0) {
// Have decay on roll be much stronger.
state.currMouseXForce.set(lerp(state.currMouseXForce.get(), 0.0, 0.02))
}
if (mouseY == 0.0) {
state.currMouseYForce.set(lerp(state.currMouseYForce.get(), 0.0, 0.005))
}
//yaw, pitch, roll
return Vec3.ZERO
}
override fun canJump(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player
): Float {
return (state.stamina.get() / 1.0f)
}
override fun jumpForce(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
//Must I ensure that topspeed is greater than minimum?
val normalizedSpeed = normalizeSpeed(state.rideVelocity.get().length(), minSpeed, topSpeed)
//TODO: Determine if this should be based on max possible speed instead of top speed.
//Only ever want the fov change to be a max of 0.2 and for it to have non linear scaling.
return 1.0f + normalizedSpeed.pow(2).toFloat() * 0.2f
}
override fun useAngVelSmoothing(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): Boolean {
return true
}
override fun useRidingAltPose(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
return cobblemonResource("no_pose")
}
override fun inertia(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): Double {
return 1.0
}
override fun shouldRoll(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): Boolean {
return true
}
override fun turnOffOnGround(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun getRideSounds(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: JetNoRollSettings) = JetNoRollState()
override fun damageOnCollision(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
impactVec: Vec3
): Boolean {
val impactSpeed = impactVec.horizontalDistance().toFloat() * 10f
return vehicle.causeFallDamage(impactSpeed, 1f, vehicle.damageSources().flyIntoWall())
}
}
class JetNoRollSettings : RidingBehaviourSettings {
override val key = JetNoRollBehaviour.KEY
var gravity: Expression = "0".asExpression()
private set
var minSpeed: Expression = "1.2".asExpression()
private set
var handlingYawExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 50.0, 25.0)".asExpression()
private set
// Make configurable by json
var infiniteStamina: Expression = "false".asExpression()
private set
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 300.0, 128.0)".asExpression()
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 140.0, 20.0)".asExpression()
private set
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 1.8, 1.0)".asExpression()
private set
var accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 1.0)), (1.0 / (20.0 * 5.0)))".asExpression()
private set
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 60.0, 10.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(gravity)
buffer.writeExpression(minSpeed)
buffer.writeExpression(handlingYawExpr)
buffer.writeExpression(infiniteStamina)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
gravity = buffer.readExpression()
minSpeed = buffer.readExpression()
handlingYawExpr = buffer.readExpression()
infiniteStamina = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
}
}
class JetNoRollState : RidingBehaviourState() {
var currSpeed = ridingState(0.0, Side.CLIENT)
var currMouseXForce = ridingState(0.0, Side.CLIENT)
var currMouseYForce = ridingState(0.0, Side.CLIENT)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeDouble(currSpeed.get())
buffer.writeFloat(stamina.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
currSpeed.set(buffer.readDouble(), forced = true)
}
override fun reset() {
super.reset()
currSpeed.set(0.0, forced = true)
currMouseXForce.set(0.0, forced = true)
currMouseYForce.set(0.0, forced = true)
}
override fun copy() = JetNoRollState().also {
it.currSpeed.set(currSpeed.get(), forced = true)
it.stamina.set(stamina.get(), forced = true)
it.rideVelocity.set(rideVelocity.get(), forced = true)
// it.currMouseXForce.set(currMouseXForce.get(), forced = true)
// it.currMouseYForce.set(currMouseYForce.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is JetNoRollState) return false
if (previous.currSpeed != currSpeed) return true
return super.shouldSync(previous)
}
}
@@ -0,0 +1,566 @@
/*
* 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.behaviour.types.air
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.client.Minecraft
import net.minecraft.network.FriendlyByteBuf
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
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import org.joml.Matrix3f
import kotlin.math.*
class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
companion object {
val KEY = cobblemonResource("air/rocket")
}
override val key = KEY
override fun getRidingStyle(settings: RocketSettings, state: RocketState): RidingStyle {
return RidingStyle.AIR
}
val poseProvider = PoseProvider<RocketSettings, RocketState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ ->
return@PoseOption state.boosting.get()
})
override fun isActive(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
//Need to check other fluids
if (vehicle.isInWater || vehicle.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() == (vehicle.position().y)) {
val blockState = vehicle.level().getBlockState(it.below())
return@any !blockState.isAir && blockState.fluidState.isEmpty
}
true
}
}
override fun pose(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): PoseType {
return this.poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
): Float {
// Use this as a "tick" function and check to see if the driver is "boosting"
if(vehicle.level().isClientSide) {
//If the forward key is not held then it cannot be boosting
if(Minecraft.getInstance().options.keyUp.isDown()) {
val boostKeyPressed = Minecraft.getInstance().options.keySprint.isDown()
//If on the previous tick the boost key was held then don't change if the ride is boosting
if(state.boostIsToggleable.get() && boostKeyPressed) {
//flip the boosting state if boost key is pressed
state.boosting.set(!state.boosting.get())
}
//If the boost key is not held then next tick boosting is toggleable
state.boostIsToggleable.set(!boostKeyPressed)
} else {
//Turn off boost and reset boost params
state.boostIsToggleable.set(true)
state.boosting.set(false)
state.canSpeedBurst.set(true)
}
}
return state.rideVelocity.get().length().toFloat()
}
override fun updatePassengerRotation(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: LivingEntity
) {
return
}
override fun clampPassengerRotation(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: LivingEntity
) {
val f = Mth.wrapDegrees(driver.yRot - vehicle.yRot)
val lookYawLimit = 90.0f
val g = Mth.clamp(f, -lookYawLimit, lookYawLimit)
val ticksBeforeCorrection = 40
if(state.prevCamYRot.get() == driver.yRot){
state.ticksNoInput.set(state.ticksNoInput.get() + 1)
} else {
state.ticksNoInput.set(0)
}
val correctionForce = if(state.ticksNoInput.get() >= ticksBeforeCorrection && f != 0.0f && state.boosting.get()) 1f * min(sqrt(lookYawLimit / (abs(f))), 10.0f) else 0.0f
driver.yRotO += g - f
driver.yRot = driver.yRot + g - f //- ( clamp(f.sign * correctionForce, -abs(f), abs(f)) )
//driver.yRot = driver.yRot + g - f - ( min )
state.prevCamYRot.set(driver.yRot)
driver.setYHeadRot(driver.yRot)
}
override fun rotation(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
var newMomentum = state.turnMomentum.get().toDouble()
// Grab turningAcceleration and divide 20 twice to get
val turningAcceleration = (vehicle.runtime.resolveDouble(settings.handlingExpr) / 20.0f) / 20.0f * 2
val turnInput = (driver.xxa *-1.0f) * turningAcceleration
val maxTurnMomentum = 80.0f / 20.0f
// Base boost stats off of normal turning stats
val boostMaxTurnMomentum = maxTurnMomentum * 0.1f
val boostTurnInput = turnInput * 0.15f
if(state.boosting.get()) {
if(driver.xxa != 0.0f && abs(newMomentum + turnInput) < (boostMaxTurnMomentum)) { //If max momentum will not be exceeded then modulate
newMomentum += boostTurnInput
} else {
newMomentum = lerp(newMomentum, 0.0, 0.05)
}
} else {
if(driver.xxa == 0.0f) { //If no turning input then lerp to 0
newMomentum = lerp(newMomentum, 0.0, 0.05)
} else if(abs(newMomentum + turnInput) < maxTurnMomentum) { //If max momentum will not be exceeded then modulate
newMomentum += turnInput
}
}
state.turnMomentum.set(newMomentum.toFloat())
driver.yRot += newMomentum.toFloat()
return Vec2(driver.xRot, vehicle.yRot + newMomentum.toFloat())
}
override fun velocity(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val retVel = calculateRideSpaceVel(settings, state, vehicle, driver)
state.rideVelocity.set(retVel)
return retVel
}
private fun calculateRideSpaceVel(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
): Vec3 {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) * 2
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr) * 0.5 * 2
var newVelocity = vehicle.deltaMovement
// Align the direction of movement with the world coordinate space so as not to modify it through turning
val yawAligned = Matrix3f().rotateY(vehicle.yRot.toRadians())
newVelocity = (newVelocity.toVector3f().mul(yawAligned)).toVec3d()
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0 && !state.boosting.get()) {
//make sure it can't exceed top speed
val forwardInput = when {
vehicle.deltaMovement.horizontalDistance() > topSpeed && (driver.zza.sign == newVelocity.z.sign.toFloat()) -> 0.0
else -> driver.zza.sign
}
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble())))
} else if ((state.stamina.get() > 0.0 && state.boosting.get())) {
val boostSpeed = topSpeed * 5
val boostAccel = if(newVelocity.length() < boostSpeed) accel * 3 else 0.0
val forwardInput = 1.0f
var burst = 0.0f
if(state.canSpeedBurst.get()) {
burst = 0.25f
state.canSpeedBurst.set(false)
}
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
newVelocity.z + (boostAccel * forwardInput.toDouble()) + burst)
}
//Vertical movement based on driver input.
val maxVertSpeed = 0.5f
var vertInput = 0.0
//If boosting then don't lose altitude
//otherwise propel up or fall
if (state.boosting.get() && !driver.jumping && vehicle.deltaMovement.y > (-maxVertSpeed * 0.25)) {
vertInput = -0.7
newVelocity = Vec3(
newVelocity.x,
newVelocity.y + accel * vertInput,
newVelocity.z)
} else if(state.boosting.get() && vehicle.deltaMovement.y < maxVertSpeed) {
vertInput = when {
driver.jumping -> 2.0
else -> 0.0
}
newVelocity = Vec3(
newVelocity.x,
(newVelocity.y + (accel * 0.5 * vertInput)),
newVelocity.z)
} else {
if (driver.jumping && !(vehicle.deltaMovement.y > maxVertSpeed && (newVelocity.y.sign.toFloat() > 0.0))) {
vertInput = 1.0
// Reset falldistance if upward motion is detected
vehicle.resetFallDistance()
} else {
vertInput = -2.5
}
if (state.stamina.get() > 0.0) {
newVelocity = Vec3(
newVelocity.x,
(newVelocity.y + (accel * vertInput)),
newVelocity.z)
}
}
return newVelocity
}
/*
* 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.
*/
private fun normalizeVal(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)
}
override fun angRollVel(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
//Smooth out mouse input.
val smoothingSpeed = 4
val xInput = mouseXSmoother.getNewDeltaValue(mouseX * 0.1, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseY * 0.1, deltaTime * smoothingSpeed);
//yaw, pitch, roll
return Vec3(0.0, yInput, xInput)
}
override fun canJump(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
): Float {
//Retrieve stamina from state and use it to set the "stamina bar"
return (state.stamina.get() / 1.0f)
}
override fun jumpForce(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
): Float {
if (state.boosting.get()) {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//val exceededSpeed = max(vehicle.deltaMovement.length() - topSpeed, 0.0)
//TODO: Remove this magic number and get a better comparison for boost top speed
val normalizedBoostSpeed = normalizeVal(state.rideVelocity.get().length(), topSpeed, topSpeed * 3)
return 1.0f + normalizedBoostSpeed.pow(2).toFloat() * 0.2f
} else {
return 1.0f
}
}
override fun useAngVelSmoothing(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun useRidingAltPose(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
return cobblemonResource("no_pose")
}
override fun inertia(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Double {
return 1.0
}
override fun shouldRoll(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun turnOffOnGround(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun dismountOnShift(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun getRideSounds(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: RocketSettings) = RocketState()
}
class RocketSettings : RidingBehaviourSettings {
override val key = RocketBehaviour.KEY
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 0.65, 0.3) * 0.25".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 accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 1.5)), (1.0 / (20.0 * 3.0))) * 0.5".asExpression()
private set
// Between 30 seconds and 10 seconds at the lowest when at full speed.
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 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', 'AIR', 10.0, 1.0)".asExpression()
private set
// Controls the acceleration of the turning speed since the max turning speed is locked at 60 degrees/second
// So this is a range of 60 degrees/second per second to 20. Meaning at max stats when holding a direction it
// takes a full second to be turning at max speed.
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 60.0, 20.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
}
}
class RocketState : RidingBehaviourState() {
var boosting = ridingState(false, Side.BOTH)
var boostIsToggleable = ridingState(false, Side.BOTH)
var canSpeedBurst = ridingState(false, Side.BOTH)
var prevCamYRot: SidedRidingState<Float> = ridingState(0.0f, Side.CLIENT)
var ticksNoInput: SidedRidingState<Int> = ridingState(0, Side.CLIENT)
val turnMomentum: SidedRidingState<Float> = ridingState(0.0f, Side.CLIENT)
override fun reset() {
super.reset()
boosting.set(false, forced = true)
boostIsToggleable.set(false, forced = true)
canSpeedBurst.set(true, forced = true)
prevCamYRot.set(0.0f, forced = true)
ticksNoInput.set(0, forced = true)
turnMomentum.set(0.0f, forced = true)
}
override fun copy() = RocketState().also {
it.rideVelocity.set(this.rideVelocity.get(), forced = true)
it.stamina.set(this.stamina.get(), forced = true)
it.boosting.set(this.boosting.get(), forced = true)
it.boostIsToggleable.set(this.boosting.get(), forced = true)
it.canSpeedBurst.set(this.canSpeedBurst.get(), forced = true)
it.prevCamYRot.set(this.prevCamYRot.get(), forced = true)
it.ticksNoInput.set(this.ticksNoInput.get(), forced = true)
it.turnMomentum.set(this.turnMomentum.get(), forced = true)
}
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeBoolean(boosting.get())
buffer.writeBoolean(boostIsToggleable.get())
buffer.writeBoolean(canSpeedBurst.get())
buffer.writeFloat(prevCamYRot.get())
buffer.writeInt(ticksNoInput.get())
buffer.writeFloat(turnMomentum.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
boosting.set(buffer.readBoolean(), forced = true)
boostIsToggleable.set(buffer.readBoolean(), forced = true)
canSpeedBurst.set(buffer.readBoolean(), forced = true)
prevCamYRot.set(buffer.readFloat(), forced = true)
ticksNoInput.set(buffer.readInt(), forced = true)
turnMomentum.set(buffer.readFloat(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is RocketState) return false
if (previous.boosting.get() != boosting.get()) return true
if (previous.boostIsToggleable.get() != boostIsToggleable.get()) return true
if (previous.canSpeedBurst.get() != canSpeedBurst.get()) return true
if (previous.prevCamYRot.get() != prevCamYRot.get()) return true
if (previous.ticksNoInput.get() != ticksNoInput.get()) return true
if (previous.turnMomentum.get() != turnMomentum.get()) return true
return super.shouldSync(previous)
}
}
@@ -45,7 +45,7 @@ object JumpStrategy : CompositeRidingStrategy<CompositeSettings> {
): Boolean {
if (state.activeBehaviour.get() == defaultSettings.key) return false
if (!vehicle.onGround()) return false
if (state.lastTransition.get() + 20 >= vehicle.level().gameTime) return false
if (state.lastTransition.get() + 5 >= vehicle.level().gameTime) return false
val defaultBehaviour = RidingBehaviours.get(defaultSettings.key)
return defaultBehaviour.isActive(defaultSettings, state.defaultBehaviourState, vehicle)
}
@@ -58,7 +58,7 @@ object JumpStrategy : CompositeRidingStrategy<CompositeSettings> {
): Boolean {
if (state.activeBehaviour.get() == alternativeSettings.key) return false
if (!driver.jumping) return false
if (state.lastTransition.get() + 20 >= vehicle.level().gameTime) return false
if (state.lastTransition.get() + 5 >= vehicle.level().gameTime) return false
val alternativeBehaviour = RidingBehaviours.get(alternativeSettings.key)
return alternativeBehaviour.isActive(alternativeSettings, state.alternateBehaviourState, vehicle)
}
@@ -83,7 +83,15 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
// Use this as a "tick" function and calculate sprinting and inAir state here
state.sprinting.set(driver.isSprinting)
inAirCheck(state, vehicle)
return state.rideVelocity.get().length().toFloat()
}
fun inAirCheck(
state: HorseState,
vehicle: PokemonEntity,
) {
// Check both vertical movement and if there are blocks below.
val posBelow = vehicle.blockPosition().below()
val blockStateBelow = vehicle.level().getBlockState(posBelow)
@@ -92,14 +100,10 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
val canSupportEntity = blockStateBelow.isFaceSturdy(vehicle.level(), posBelow, Direction.UP)
val standingOnSolid = canSupportEntity && !isAirOrLiquid
// val level = vehicle.level()
// val toesBox = vehicle.boundingBox.move(0.0, -0.1, 0.0)
// inAir if not on the ground
val inAir = !(vehicle.deltaMovement.y == 0.0 || standingOnSolid)
state.inAir.set(inAir)
return state.rideVelocity.get().length().toFloat()
}
override fun updatePassengerRotation(
@@ -107,26 +111,7 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
state: HorseState,
vehicle: PokemonEntity,
driver: LivingEntity
) {
//Take the inverse so that it cancels out how
//much the entity rotates.
val turnAmount = calcRotAmount(settings, state, vehicle, driver)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val maxYawDiff = 90.0f
//Normalize the current rotation diff
val rotMod = Mth.wrapDegrees(driver.yRot - vehicle.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.rideVelocity.get().length(), 0.0, topSpeed).toFloat()
//driver.yRot += (entity.riding.deltaRotation.y - turnAmount)
//driver.setYHeadRot(driver.yHeadRot + (entity.riding.deltaRotation.y) - turnAmount)
}
) { return }
override fun clampPassengerRotation(
settings: HorseSettings,
@@ -149,9 +134,9 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
val turnAmount = calcRotAmount(settings, state, vehicle, driver)
var turnAmount = calcRotAmount(settings, state, vehicle, driver)
return Vec2(vehicle.xRot, vehicle.yRot + turnAmount )
return Vec2(vehicle.xRot, vehicle.yRot + turnAmount * 0.5f )
}
@@ -169,9 +154,9 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//In degrees per second
val walkHandling = 140.0 * 2
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr) * 2
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr) * 3
val walkHandlingBoost = 5
val walkHandling = handling * walkHandlingBoost
val maxYawDiff = 90.0f
@@ -185,13 +170,14 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
val rotDiffMod = (sqrt(abs(rotDiffNorm)) * rotDiffNorm.sign)
//Take the inverse so that you turn less at higher speeds
val normSpeed = 1.0f // = 1.0f - 0.5f*normalizeVal(state.rideVelocityocity.length(), 0.0, topSpeed).toFloat()
val normSpeed = 1.0f
// TurnRate should always be quick if not sprinting
val turnRate = if(state.sprinting.get()) (handling.toFloat() / 20.0f) else (walkHandling.toFloat() / 20.0f)
val invRelSpeed = (inverseLerp(vehicle.deltaMovement.horizontalDistance(), 0.0, topSpeed ).pow(0.5) - 1.0f) * -1.0f
val turnRate = ((handling.toFloat() / 20.0f) * max(walkHandlingBoost * invRelSpeed, 1.0)).toFloat()
//Ensure you only ever rotate as much difference as there is between the angles.
val turnSpeed = turnRate * rotDiffMod * normSpeed
val turnSpeed = turnRate * rotDiffMod * normSpeed
val rotAmount = turnSpeed.coerceIn(-abs(rotDiff), abs(rotDiff))
return rotAmount
@@ -218,16 +204,19 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
// Check to see if the ride should be walking or sprinting
val walkSpeed = getWalkSpeed(vehicle)
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) * 1.3
val topSpeed = if(state.sprinting.get()) rideTopSpeed else walkSpeed
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr) * 2
//Flag for determining if player is actively inputting
var activeInput = false
var newVelocity = Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, state.rideVelocity.get().z)
// Gather the previous velocity and set the horizontal fields of the velocity vector to the magnitude of the
// ones from deltaMovement. This is important as when a collision happens the Deltamovement will reflect it
// but rideVelocity won't.
var newVelocity = Vec3(state.rideVelocity.get().x, 0.0, state.rideVelocity.get().z).normalize().scale(vehicle.deltaMovement.horizontalDistance())
newVelocity = newVelocity.add(0.0, state.rideVelocity.get().y, 0.0)
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0) {
@@ -246,11 +235,14 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
activeInput = true
}
//Gravity logic
// Gravity logic
if (vehicle.onGround()) {
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
} else {
val gravity = (9.8 / ( 20.0)) * 0.2
//TODO: Should we just go back to standard minecraft gravity or do the lerp modifications prevent that?
//I think minecrafts gravity logic is also too harsh and isn't gamefied enough for mounts maybe? Need
//to do some testing and get other's opinions
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.8
val terminalVel = 2.0
val fallingForce = gravity - ( newVelocity.z.sign *gravity *(abs(newVelocity.z) / 2.0))
@@ -262,27 +254,34 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
newVelocity = newVelocity.subtract(0.0, 0.0, min(0.03 * newVelocity.z.sign, newVelocity.z))
}
//TODO: Change this so its tied to a jumping stat and representative of the amount of jumps
val canJump = vehicle.runtime.resolveBoolean(settings.canJump)
//Jump the thang!
if (driver.jumping && vehicle.onGround()) {
if (driver.jumping && vehicle.onGround() && canJump) {
val jumpForce = 1.0
val horz = state.rideVelocity.get().horizontalDistance()
val velMag = newVelocity.length()
newVelocity = newVelocity.add(0.0, jumpForce, 0.0)
val test = 0.0
//Ensure this doesn't add unwanted forward velocity
//val mag = if(newVelocity.length() < rideTopSpeed) newVelocity.length() else rideTopSpeed
//newVelocity = newVelocity.normalize().scale(mag)
}
//Zero out lateral velocity possibly picked up from a controller transition
newVelocity = Vec3(0.0, newVelocity.y, newVelocity.z)
return newVelocity
}
/*
* 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.
* lerps the current val between minVal and maxVal.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minVal and 1.0 represents maxVal.
*/
private fun normalizeVal(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)
private fun inverseLerp(currVal: Double, minVal: Double, maxVal: Double): Double {
require(maxVal > minVal) { "minVal must be greater than maxVal" }
return ((currVal - minVal) / (maxVal - minVal)).coerceIn(0.0, 1.0)
}
private fun getWalkSpeed( vehicle: PokemonEntity ): Double {
@@ -496,7 +495,6 @@ class HorseSettings : RidingBehaviourSettings {
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
}
}
class HorseState : RidingBehaviourState() {
@@ -0,0 +1,563 @@
/*
* 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.behaviour.types.land
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import org.joml.Matrix3f
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sign
class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
companion object {
val KEY = cobblemonResource("land/minekart")
}
override val key = KEY
override fun getRidingStyle(settings: MinekartSettings, state: MinekartState): RidingStyle {
return RidingStyle.LAND
}
val poseProvider = PoseProvider<MinekartSettings, MinekartState>(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { _, state, _ ->
return@PoseOption abs(state.rideVelocity.get().horizontalDistance()) > 0.0
})
// val poseProvider = PoseProvider<MinekartSettings, MinekartState>(PoseType.STAND)
// .with(PoseOption(PoseType.WALK) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: MinekartSettings, state: MinekartState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
//Need to check other fluids
if (vehicle.isInWater || vehicle.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() == (vehicle.position().y)) {
val blockState = vehicle.level().getBlockState(it.below())
return@any !(!blockState.isAir && blockState.fluidState.isEmpty)
}
true
}
}
override fun pose(settings: MinekartSettings, state: MinekartState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
): Float {
val boostMax = vehicle.runtime.resolveFloat(settings.boostLimit)
//TODO: should this be by how much turn has accumulated or simply time turning... or both?
val boostPerSecond = 20.0f
val boostLoss = 40.0f
if (driver.jumping && driver.zza > 0.0f) {
if (!state.drifting.get()) {
if (driver.xxa != 0.0f) {
state.clockwiseDrift.set(driver.xxa.sign > 0.0)
state.turnMomentum.set(0.0f)
state.drifting.set(true)
state.boost.set(0.0f)
} else {
//TODO: Add jumping logic
}
} else {
state.boost.set((state.boost.get() + boostPerSecond / 20.0f).coerceIn(0.0f, boostMax))
}
} else {
if(driver.zza <= 0.0 && state.boost.get() != 0.0f){
state.boost.set(0.0f)
} else if( state.boost.get() > 0.0f ) {
state.boost.set(max(state.boost.get() - boostLoss, 0.0f))
}
state.drifting.set(false)
}
inAirCheck(state, vehicle)
return driver.deltaMovement.length().toFloat()
}
fun inAirCheck(
state: MinekartState,
vehicle: PokemonEntity,
) {
// Check both vertical movement and if there are blocks below.
val posBelow = vehicle.blockPosition().below()
val blockStateBelow = vehicle.level().getBlockState(posBelow)
val isAirOrLiquid = blockStateBelow.isAir || !blockStateBelow.fluidState.isEmpty
val canSupportEntity = blockStateBelow.isFaceSturdy(vehicle.level(), posBelow, Direction.UP)
val standingOnSolid = canSupportEntity && !isAirOrLiquid
// inAir if not on the ground
val inAir = !(vehicle.deltaMovement.y == 0.0 || standingOnSolid)
state.inAir.set(inAir)
}
override fun rotation(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
var newMomentum = state.turnMomentum.get().toDouble()
//TODO: maybe tie this to max turnspeeds rather than the turn acceleration
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val turningAcceleration = (vehicle.runtime.resolveDouble(settings.handlingExpr) * 1.5 / 20.0f) / 20.0f * 4.0
val turnInput = (driver.xxa *-1.0f) * turningAcceleration
val driftInput = turnInput * 0.25f
val lowSpeedTurnBoost = (inverseLerp(vehicle.deltaMovement.horizontalDistance(), 0.0, topSpeed ).pow(0.5) - 2.0f) * -1.0f
val forcedDriftTurnMomentum = (80.0f / 20.0f) * lowSpeedTurnBoost
val maxTurnMomentum = (60.0f / 20.0f) * lowSpeedTurnBoost
val maxDriftMomentum = forcedDriftTurnMomentum * 0.6f
if (!state.drifting.get()){
// perform non drift turning
if(abs(newMomentum) <= maxTurnMomentum && turnInput != 0.0 ) {
newMomentum += turnInput
newMomentum = min(max(newMomentum, maxTurnMomentum * -1.0), maxTurnMomentum.toDouble())
} else {
newMomentum = lerp(newMomentum, 0.0, 0.15)
}
state.turnMomentum.set(newMomentum.toFloat())
driver.yRot += newMomentum.toFloat()
return Vec2(driver.xRot, vehicle.yRot + newMomentum.toFloat())
} else {
// perform drift turning
if(abs(newMomentum) <= maxDriftMomentum && driftInput != 0.0 ) {
newMomentum += driftInput
newMomentum = min(max(newMomentum, maxDriftMomentum * -1.0), maxDriftMomentum.toDouble())
} else {
newMomentum = lerp(newMomentum, 0.0, 0.15)
}
val signedDriftTurn = if(state.clockwiseDrift.get()) forcedDriftTurnMomentum * -1.0f else forcedDriftTurnMomentum
state.turnMomentum.set(newMomentum.toFloat())
driver.yRot += newMomentum.toFloat() + signedDriftTurn.toFloat()
return Vec2(driver.xRot, vehicle.yRot + newMomentum.toFloat() + signedDriftTurn.toFloat())
}
}
override fun velocity(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
state.rideVelocity.set(calculateRideSpaceVel(settings, state, vehicle, driver))
return state.rideVelocity.get()
}
private fun calculateRideSpaceVel(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
): Vec3 {
// Check to see if the ride should be walking or sprinting
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) * 0.6 * 2.0
val topSpeed = if (state.drifting.get()) rideTopSpeed * 0.7 else rideTopSpeed
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr) * 0.3 * 3 * 2.0
val driftAngle = 20.0f
//Align the direction of this vector with the vehicles current heading.
var newVelocity = vehicle.deltaMovement
newVelocity = newVelocity.yRot(vehicle.yRot.toRadians())
// rotate the angle of force if drifting
if (state.drifting.get()) {
val signedDriftAngle = if(state.clockwiseDrift.get()) driftAngle else driftAngle * -1.0f
newVelocity = newVelocity.yRot(signedDriftAngle.toRadians())
}
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0 && !state.inAir.get()) {
//make sure it can't exceed top speed
var forwardInput = when {
driver.zza > 0 && newVelocity.z > topSpeed -> 0.0
driver.zza < 0 && newVelocity.z <= 0.0 -> 0.0
else -> driver.zza.sign
}
if (!state.drifting.get() && state.boost.get() != 0.0f) {
forwardInput = 2.0;
}
// If drifting we need more acceleration due to the constant turn
val driftAccelBoost = if (state.drifting.get()) 1.5 else 1.0
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble() * driftAccelBoost)))
}
// Gravity logic
if (vehicle.onGround()) {
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
} else {
//TODO: Should we just go back to standard minecraft gravity or do the lerp modifications prevent that?
//I think minecrafts gravity logic is also too harsh and isn't gamefied enough for mounts maybe? Need
//to do some testing and get other's opinions
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.25 * 3.0
val terminalVel = 2.0
val fallingForce = gravity - ( newVelocity.z.sign *gravity *(abs(newVelocity.z) / 2.0))
newVelocity = Vec3(newVelocity.x, max(newVelocity.y - fallingForce, -terminalVel), newVelocity.z)
}
//ground Friction
var friction = 0.003 * 2.0
if (state.drifting.get()) {friction *= 0.5}
if (!state.inAir.get()) {
newVelocity = newVelocity.subtract(
min(friction , abs(newVelocity.x)) * newVelocity.x.sign,
0.0,
min(friction , abs(newVelocity.z)) * newVelocity.z.sign
)
// TEST FOR LATERAL SLIP OR WHATEVER
//TODO: calc lateral slip and friction better!
if (!state.drifting.get()) {
newVelocity = newVelocity.subtract(
min(friction * 10, abs(newVelocity.x)) * newVelocity.x.sign,
0.0,
0.0,
)
}
}
//TODO: Change this so its tied to a jumping stat and representative of the amount of jumps
// val canJump = vehicle.runtime.resolveBoolean(settings.canJump)
// //Jump the thang!
// if (driver.jumping && vehicle.onGround() && canJump) {
// val jumpForce = 0.5
//
// newVelocity = newVelocity.add(0.0, jumpForce, 0.0)
//
// }
// unrotate the angle of force if drifting
if (state.drifting.get()) {
val inverseSignedDriftAngle = if(state.clockwiseDrift.get()) driftAngle * -1.0f else driftAngle
newVelocity = newVelocity.yRot(inverseSignedDriftAngle.toRadians())
}
return newVelocity
}
/*
* lerps the current val between minVal and maxVal.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minVal and 1.0 represents maxVal.
*/
private fun inverseLerp(currVal: Double, minVal: Double, maxVal: Double): Double {
require(maxVal > minVal) { "minVal must be greater than maxVal" }
return ((currVal - minVal) / (maxVal - minVal)).coerceIn(0.0, 1.0)
}
override fun angRollVel(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//Might need to add the smoothing here for default.
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
return Vec3(0.0, mouseY * invertPitch, mouseX * invertRoll)
}
override fun canJump(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return vehicle.runtime.resolveBoolean(settings.canJump)
}
override fun setRideBar(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 0.0f
}
override fun jumpForce(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
): Float {
var fov = 1.0f
if(!state.drifting.get() && state.boost.get() != 0.0f) {
//TODO: is this too abrupt? Possibly caluclate differently?
fov = 1.2f
}
return fov
}
override fun useAngVelSmoothing(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun useRidingAltPose(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
when {
state.drifting.get() -> return cobblemonResource("drifting")
}
return cobblemonResource("no_pose")
}
override fun inertia(settings: MinekartSettings, state: MinekartState, vehicle: PokemonEntity): Double {
return 1.0
}
override fun shouldRoll(settings: MinekartSettings, state: MinekartState, vehicle: PokemonEntity): Boolean {
return false
}
override fun turnOffOnGround(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun dismountOnShift(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): Boolean {
return true
}
override fun getRideSounds(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: MinekartSettings) = MinekartState()
}
class MinekartSettings : RidingBehaviourSettings {
override val key = MinekartBehaviour.KEY
var canJump = "true".asExpression()
private set
var boostLimit = "100.0f".asExpression()
private set
var speedExpr: 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 accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'LAND', (1.0 / (20.0 * 1.5)), (1.0 / (20.0 * 5.0)))".asExpression()
private set
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
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 140.0, 20.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(canJump)
buffer.writeExpression(boostLimit)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
canJump = buffer.readExpression()
boostLimit = buffer.readExpression()
}
}
class MinekartState : RidingBehaviourState() {
var currSpeed = ridingState(0.0, Side.BOTH)
var deltaRotation = ridingState(Vec2.ZERO, Side.BOTH)
var drifting = ridingState(false, Side.CLIENT)
var clockwiseDrift = ridingState(false, Side.CLIENT)
val boost: SidedRidingState<Float> = ridingState(0.0f, Side.CLIENT)
val turnMomentum: SidedRidingState<Float> = ridingState(0.0f, Side.CLIENT)
var inAir = ridingState(false, Side.CLIENT)
override fun reset() {
super.reset()
currSpeed.set(0.0, forced = true)
deltaRotation.set(Vec2.ZERO, forced = true)
drifting.set(false, forced = true)
clockwiseDrift.set(false, forced = true)
boost.set(0.0f, forced = true)
turnMomentum.set(0.0f, forced = true)
inAir.set(false, forced = true)
}
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeBoolean(drifting.get())
buffer.writeBoolean(clockwiseDrift.get())
buffer.writeFloat(boost.get())
buffer.writeFloat(turnMomentum.get())
buffer.writeBoolean(inAir.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
drifting.set(buffer.readBoolean(), forced = true)
clockwiseDrift.set(buffer.readBoolean(), forced = true)
boost.set(buffer.readFloat(), forced = true)
turnMomentum.set(buffer.readFloat(), forced = true)
inAir.set(buffer.readBoolean(), forced = true)
}
override fun toString(): String {
return "MinekartState(currSpeed=${currSpeed.get()}, deltaRotation=${deltaRotation.get()})"
}
override fun copy() = MinekartState().also {
it.rideVelocity.set(rideVelocity.get(), forced = true)
it.stamina.set(stamina.get(), forced = true)
it.currSpeed.set(currSpeed.get(), forced = true)
it.deltaRotation.set(deltaRotation.get(), forced = true)
it.drifting.set(drifting.get(), forced = true)
it.clockwiseDrift.set(clockwiseDrift.get(), forced = true)
it.boost.set(boost.get(), forced = true)
it.turnMomentum.set(turnMomentum.get(), forced = true)
it.inAir.set(inAir.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is MinekartState) return false
if (previous.drifting.get() != drifting.get()) return true
if (previous.clockwiseDrift.get() != clockwiseDrift.get()) return true
if (previous.boost.get() != boost.get()) return true
if (previous.turnMomentum.get() != turnMomentum.get()) return true
if (previous.inAir.get() != inAir.get()) return true
return super.shouldSync(previous)
}
}
@@ -8,7 +8,8 @@
package com.cobblemon.mod.common.api.riding.behaviour.types.land
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
@@ -16,10 +17,12 @@ import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
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.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
@@ -28,29 +31,15 @@ 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.absoluteValue
import org.joml.Matrix3f
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sign
class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
companion object {
val KEY = cobblemonResource("land/vehicle")
val MAX_TOP_SPEED = 1.0 // 20 bl/s
val MIN_TOP_SPEED = 0.35 // 7 bl/s
//val MIN_SPEED = 0.25 // 5 bl/s
//Accel will lie between 1.0 second and 5.0 seconds
val MAX_ACCEL = (MAX_TOP_SPEED) / (20*3) //3.0 second to max speed
val MIN_ACCEL = (MAX_TOP_SPEED) / (20*8) // 8 seconds to max speed
//Can rotate 90 degrees
val MAX_HANDLING = 90.0
val MIN_HANDLING = 30.0
val MAX_YAW_HANDLING = 16.0
val MIN_YAW_HANDLING = 8.0
}
override val key = KEY
@@ -60,7 +49,12 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
}
val poseProvider = PoseProvider<VehicleSettings, VehicleState>(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
.with(PoseOption(PoseType.WALK) { _, state, _ ->
return@PoseOption abs(state.rideVelocity.get().horizontalDistance()) > 0.0
})
// val poseProvider = PoseProvider<VehicleSettings, VehicleState>(PoseType.STAND)
// .with(PoseOption(PoseType.WALK) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: VehicleSettings, state: VehicleState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
@@ -87,18 +81,38 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
vehicle: PokemonEntity,
driver: Player
): Float {
val topSpeed = vehicle.getRideStat(RidingStat.SPEED, RidingStyle.AIR, MIN_TOP_SPEED, MAX_TOP_SPEED)
val accel = vehicle.getRideStat(RidingStat.ACCELERATION, RidingStyle.AIR, MIN_ACCEL, MAX_ACCEL)
//speed up and slow down based on input
if (driver.zza > 0.0 && state.currSpeed.get() < topSpeed) {
state.currSpeed.set(min(state.currSpeed.get() + accel , topSpeed))
} else if (driver.zza < 0.0 && state.currSpeed.get() > 0.0) {
//Decelerate is now always a constant half of max acceleration.
state.currSpeed.set(max(state.currSpeed.get() - (MAX_ACCEL / 2), 0.0))
if (driver.isShiftKeyDown) {
state.drifting.set(true)
if(driver.zza != 0.0f) {
state.poweredDrifting.set(true)
} else {
state.poweredDrifting.set(false)
}
} else {
state.drifting.set(false)
state.poweredDrifting.set(false)
}
return state.currSpeed.get().toFloat()
inAirCheck(state, vehicle)
return driver.deltaMovement.length().toFloat()
}
fun inAirCheck(
state: VehicleState,
vehicle: PokemonEntity,
) {
// Check both vertical movement and if there are blocks below.
val posBelow = vehicle.blockPosition().below()
val blockStateBelow = vehicle.level().getBlockState(posBelow)
val isAirOrLiquid = blockStateBelow.isAir || !blockStateBelow.fluidState.isEmpty
val canSupportEntity = blockStateBelow.isFaceSturdy(vehicle.level(), posBelow, Direction.UP)
val standingOnSolid = canSupportEntity && !isAirOrLiquid
// inAir if not on the ground
val inAir = !(vehicle.deltaMovement.y == 0.0 || standingOnSolid)
state.inAir.set(inAir)
}
override fun rotation(
@@ -107,10 +121,38 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
val rotationDegrees = driver.xxa * vehicle.runtime.resolveFloat(settings.rotationSpeed)
val rotation = Vec2(driver.xRot, vehicle.yRot - rotationDegrees)
state.deltaRotation.set(Vec2(rotation.x - driver.rotationVector.x, rotation.y - vehicle.rotationVector.y))
return rotation
var newMomentum = state.turnMomentum.get().toDouble()
// Grab turningAcceleration and divide 20 twice to get
val turningAcceleration = (vehicle.runtime.resolveDouble(settings.handlingExpr) * 1.5 / 20.0f) / 20.0f * 4.0
val turnInput = (driver.xxa *-1.0f) * turningAcceleration
val maxTurnMomentum = 80.0f / 20.0f
// Base boost stats off of normal turning stats
val driftMaxTurnMomentum = maxTurnMomentum * 3.0f
var driftTurnInput = turnInput * 0.4f
if(state.drifting.get() || state.inAir.get()) {
if( newMomentum < driftMaxTurnMomentum) {
driftTurnInput = driftTurnInput * (vehicle.deltaMovement.horizontalDistance() / vehicle.runtime.resolveDouble(settings.speedExpr))
newMomentum += driftTurnInput
}
newMomentum = lerp(newMomentum, 0.0, 0.03)
if( abs(newMomentum) < 0.05 ) {newMomentum = 0.0}
} else {
if(abs(newMomentum) <= maxTurnMomentum && turnInput != 0.0 ) {
newMomentum += turnInput
newMomentum.coerceIn(-maxTurnMomentum.toDouble(), maxTurnMomentum.toDouble())
} else {
newMomentum = lerp(newMomentum, 0.0, 0.15)
}
}
state.turnMomentum.set(newMomentum.toFloat())
driver.yRot += newMomentum.toFloat()
return Vec2(driver.xRot, vehicle.yRot + newMomentum.toFloat())
}
override fun velocity(
@@ -120,18 +162,106 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
driver: Player,
input: Vec3
): Vec3 {
val runtime = vehicle.runtime
val driveFactor = runtime.resolveFloat(settings.driveFactor)
var g = driver.zza * driveFactor
if (g <= 0.0f) {
g *= runtime.resolveFloat(settings.reverseDriveFactor)
}
if (driver.xxa != 0F && g.absoluteValue < runtime.resolveFloat(settings.minimumSpeedToTurn)) {
driver.xxa = 0F
}
val velocity = Vec3(0.0 /* Maybe do drifting here later */, 0.0, g.toDouble() * state.currSpeed.get())
state.rideVelocity.set(calculateRideSpaceVel(settings, state, vehicle, driver))
return state.rideVelocity.get()
}
return velocity
private fun calculateRideSpaceVel(
settings: VehicleSettings,
state: VehicleState,
vehicle: PokemonEntity,
driver: Player
): Vec3 {
// Check to see if the ride should be walking or sprinting
//val walkSpeed = getWalkSpeed(vehicle)
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) * 0.6 * 2.0
val topSpeed = if (state.drifting.get()) rideTopSpeed * 0.5 else rideTopSpeed
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr) * 0.3 * 3 * 2.0
//Flag for determining if player is actively inputting
var activeInput = false
var newVelocity = Vec3.ZERO
if (!state.drifting.get()) {
newVelocity = vehicle.deltaMovement
newVelocity = newVelocity.yRot(vehicle.yRot.toRadians())
} else {
state
newVelocity = vehicle.deltaMovement
// Align the direction of movement with the world coordinate space so as not to modify it through turning
val yawAligned = Matrix3f().rotateY(vehicle.yRot.toRadians())
newVelocity = (newVelocity.toVector3f().mul(yawAligned)).toVec3d()
}
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0 && !state.inAir.get()) {
//make sure it can't exceed top speed
val forwardInput = when {
driver.zza > 0 && newVelocity.z > topSpeed -> 0.0
driver.zza < 0 && newVelocity.z <= 0.0 -> 0.0
else -> driver.zza.sign
}
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble())))
activeInput = true
}
// Gravity logic
if (vehicle.onGround()) {
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
} else {
//TODO: Should we just go back to standard minecraft gravity or do the lerp modifications prevent that?
//I think minecrafts gravity logic is also too harsh and isn't gamefied enough for mounts maybe? Need
//to do some testing and get other's opinions
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.25 * 3.0
val terminalVel = 2.0
val fallingForce = gravity - ( newVelocity.z.sign *gravity *(abs(newVelocity.z) / 2.0))
newVelocity = Vec3(newVelocity.x, max(newVelocity.y - fallingForce, -terminalVel), newVelocity.z)
}
//ground Friction
var friction = 0.003 * 2.0
if (state.drifting.get()) {friction *= 0.1}
if (!state.inAir.get()) {
newVelocity = newVelocity.subtract(
min(friction , abs(newVelocity.x)) * newVelocity.x.sign,
0.0,
min(friction , abs(newVelocity.z)) * newVelocity.z.sign
)
// TEST FOR LATERAL SLIP OR WHATEVER
//TODO: calc lateral slip and friction better!
if (!state.drifting.get()) {
newVelocity = newVelocity.subtract(
min(friction * 10, abs(newVelocity.x)) * newVelocity.x.sign,
0.0,
0.0,
)
}
}
//TODO: Change this so its tied to a jumping stat and representative of the amount of jumps
val canJump = vehicle.runtime.resolveBoolean(settings.canJump)
//Jump the thang!
if (driver.jumping && vehicle.onGround() && canJump) {
val jumpForce = 0.5
newVelocity = newVelocity.add(0.0, jumpForce, 0.0)
}
return newVelocity
}
override fun angRollVel(
@@ -189,10 +319,7 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
driver: Player,
jumpStrength: Int
): Vec3 {
val runtime = vehicle.runtime
runtime.environment.query.addFunction("jump_strength") { DoubleValue(jumpStrength.toDouble()) }
val jumpVector = settings.jumpVector.map { runtime.resolveFloat(it) }
return Vec3(jumpVector[0].toDouble(), jumpVector[1].toDouble(), jumpVector[2].toDouble())
return Vec3.ZERO
}
override fun gravity(
@@ -201,7 +328,7 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return regularGravity
return 0.0
}
override fun rideFovMultiplier(
@@ -227,11 +354,15 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
when {
state.poweredDrifting.get() -> return cobblemonResource("powered_drifting")
state.drifting.get() -> return cobblemonResource("drifting")
}
return cobblemonResource("no_pose")
}
override fun inertia(settings: VehicleSettings, state: VehicleState, vehicle: PokemonEntity): Double {
return 0.5
return 1.0
}
override fun shouldRoll(settings: VehicleSettings, state: VehicleState, vehicle: PokemonEntity): Boolean {
@@ -267,7 +398,7 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
state: VehicleState,
vehicle: PokemonEntity
): Boolean {
return false
return true
}
override fun getRideSounds(
@@ -287,9 +418,6 @@ class VehicleSettings : RidingBehaviourSettings {
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
@@ -299,28 +427,40 @@ class VehicleSettings : RidingBehaviourSettings {
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 speedExpr: 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 accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'LAND', (1.0 / (20.0 * 1.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
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 140.0, 20.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(canJump)
buffer.writeExpression(jumpVector[0])
buffer.writeExpression(jumpVector[1])
buffer.writeExpression(jumpVector[2])
buffer.writeExpression(speed)
buffer.writeExpression(driveFactor)
buffer.writeExpression(reverseDriveFactor)
buffer.writeExpression(minimumSpeedToTurn)
buffer.writeExpression(rotationSpeed)
buffer.writeExpression(lookYawLimit)
}
@@ -328,15 +468,9 @@ class VehicleSettings : RidingBehaviourSettings {
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
canJump = buffer.readExpression()
jumpVector = listOf(
buffer.readExpression(),
buffer.readExpression(),
buffer.readExpression()
)
speed = buffer.readExpression()
driveFactor = buffer.readExpression()
reverseDriveFactor = buffer.readExpression()
minimumSpeedToTurn = buffer.readExpression()
rotationSpeed = buffer.readExpression()
lookYawLimit = buffer.readExpression()
}
@@ -345,11 +479,36 @@ class VehicleSettings : RidingBehaviourSettings {
class VehicleState : RidingBehaviourState() {
var currSpeed = ridingState(0.0, Side.BOTH)
var deltaRotation = ridingState(Vec2.ZERO, Side.BOTH)
var drifting = ridingState(false, Side.CLIENT)
var poweredDrifting = ridingState(false, Side.CLIENT)
val turnMomentum: SidedRidingState<Float> = ridingState(0.0f, Side.CLIENT)
var inAir = ridingState(false, Side.CLIENT)
override fun reset() {
super.reset()
currSpeed.set(0.0, forced = true)
deltaRotation.set(Vec2.ZERO, forced = true)
drifting.set(false, forced = true)
poweredDrifting.set(false, forced = true)
turnMomentum.set(0.0f, forced = true)
inAir.set(false, forced = true)
}
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeBoolean(drifting.get())
buffer.writeBoolean(poweredDrifting.get())
buffer.writeFloat(turnMomentum.get())
buffer.writeBoolean(inAir.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
drifting.set(buffer.readBoolean(), forced = true)
poweredDrifting.set(buffer.readBoolean(), forced = true)
turnMomentum.set(buffer.readFloat(), forced = true)
inAir.set(buffer.readBoolean(), forced = true)
}
override fun toString(): String {
@@ -361,5 +520,18 @@ class VehicleState : RidingBehaviourState() {
it.stamina.set(stamina.get(), forced = true)
it.currSpeed.set(currSpeed.get(), forced = true)
it.deltaRotation.set(deltaRotation.get(), forced = true)
it.drifting.set(drifting.get(), forced = true)
it.poweredDrifting.set(poweredDrifting.get(), forced = true)
it.turnMomentum.set(turnMomentum.get(), forced = true)
it.inAir.set(inAir.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is VehicleState) return false
if (previous.drifting.get() != drifting.get()) return true
if (previous.poweredDrifting.get() != poweredDrifting.get()) return true
if (previous.turnMomentum.get() != turnMomentum.get()) return true
if (previous.inAir.get() != inAir.get()) return true
return super.shouldSync(previous)
}
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.riding.behaviour.types.liquid
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
@@ -19,6 +20,7 @@ import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
@@ -29,7 +31,9 @@ import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.min
import kotlin.math.sin
import kotlin.math.sqrt
class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
companion object {
@@ -43,7 +47,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
}
val poseProvider = PoseProvider<DolphinSettings, DolphinState>(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.deltaMovement.length() > 0.1 })
override fun isActive(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
@@ -161,11 +165,59 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
//Might need to add the smoothing here for default.
//TODO:hook up handling
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//Interpret mouse input
val smoothingSpeed = 4.0
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
return Vec3(0.0, mouseY * invertPitch, mouseX * invertRoll)
val mouseXc = (mouseX).coerceIn(-60.0, 60.0)
val mouseYc = (mouseY).coerceIn(-60.0, 60.0)
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1 * invertRoll, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1 * invertPitch, deltaTime * smoothingSpeed);
val rollRot = xInput
val pitchRot = yInput
// Roll
// if (abs(controller.pitch + rollRot) >= 89.5 ) {
// mouseXSmoother.reset()
// } else {
//
// }
controller.rotateRoll(rollRot.toFloat())
// Yaw
// TODO:move all magic numbers to handling expression
val yawSpeed = handling * deltaTime * 0.25
val yawForce = sin(controller.roll.toRadians()) * yawSpeed * ( 1.0 - min(sqrt(normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed)), 0.5))
//Apply yaw globally as we don't want roll or pitch changes due to local yaw when looking up or down.
controller.applyGlobalYaw(yawForce.toFloat())
// Pitch
if (abs(controller.pitch + pitchRot) >= 89.5 ) {
mouseYSmoother.reset()
} else {
//controller.applyGlobalPitch(pitchRot.toFloat() * -1.0f)
controller.rotatePitch(pitchRot.toFloat())
}
//yaw, pitch, roll
return Vec3.ZERO
}
/*
* 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.
*/
private 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)
}
override fun canJump(
@@ -295,6 +347,12 @@ class DolphinSettings : RidingBehaviourSettings {
var strafeFactor = "0.2".asExpression()
private set
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 200.0, 128.0)".asExpression()
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 135.0, 45.0)".asExpression()
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 1.0, 0.35)".asExpression()
var accelerationExpr: Expression = "q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 3.0)), (1.0 / (20.0 * 8.0)))".asExpression()
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 120.0, 20.0)".asExpression()
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
@@ -50,7 +50,7 @@ class RideLoopSound(val ride: PokemonEntity, val soundSettings: RideSoundSetting
this.looping = true
this.delay = 0
this.volume = 0.1f
this.attenuation = SoundInstance.Attenuation.NONE // This class performs its own attenuation
this.attenuation = SoundInstance.Attenuation.LINEAR // This class performs its own attenuation
if (this.isPassenger) this.relative = true
}
@@ -16,6 +16,7 @@ import net.minecraft.world.phys.Vec3
import org.joml.Matrix3f
import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.sign
/**
* Class to handle the updating and containment of ride data used in animation.
@@ -33,6 +34,7 @@ class RidingAnimationData(val ride: PokemonEntity)
var rotSpring: Vec3Spring = Vec3Spring()
var rotDeltaSpring: Vec3Spring = Vec3Spring()
var localVelocitySpring: Vec3Spring = Vec3Spring()
var driverInputSpring: Vec3Spring = Vec3Spring()
fun update() {
val activeRide = ride.hasControllingPassenger()
@@ -136,6 +138,14 @@ class RidingAnimationData(val ride: PokemonEntity)
/******************************************************
* Update springs
*****************************************************/
val driver = ride.controllingPassenger
val vertInput = when {
driver != null && driver.jumping-> 1.0
driver != null && driver.isShiftKeyDown -> -1.0
else -> 0.0
}
val input = if(driver != null) Vec3(driver.xxa.toDouble() * -1, vertInput, driver.zza.toDouble()) else Vec3.ZERO
driverInputSpring.update(input, stiffness * 2, damping)
rotDeltaSpring.update(angDelta, stiffness, damping)
velocitySpring.update(currentVelocity, stiffness, damping)
rotSpring.update(currRot, stiffness, damping)
@@ -14,6 +14,7 @@ import com.cobblemon.mod.common.api.riding.behaviour.types.air.*
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeState
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.MinekartBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BoatBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BurstBehaviour
@@ -133,25 +134,34 @@ class RideControlsOverlay : Gui(Minecraft.getInstance()) {
// Configure what controls to show for each behaviour
when (currentBehaviourKey) {
BirdBehaviour.KEY -> {}
BoatBehaviour.KEY -> {}
BurstBehaviour.KEY -> {}
DolphinBehaviour.KEY -> {}
GliderBehaviour.KEY -> {}
HelicopterBehaviour.KEY -> {}
HorseBehaviour.KEY -> {
showVerticalMouse = false
disableHorizontalMovementKeys = true
showSneakKey = false
}
BoatBehaviour.KEY -> {}
GliderBehaviour.KEY -> {}
HelicopterBehaviour.KEY -> {}
HoverBehaviour.KEY -> {
showVerticalMouse = false
}
JetBehaviour.KEY -> {
showMovementKeys = false
showSneakKey = false
showJumpKey = false
}
BurstBehaviour.KEY -> {}
VehicleBehaviour.KEY -> {}
HoverBehaviour.KEY -> {
MinekartBehaviour.KEY -> {
showVerticalMouse = false
showHorizontalMouse = false
showSneakKey = false
}
RocketBehaviour.KEY -> {
showVerticalMouse = false
showHorizontalMouse = false
}
VehicleBehaviour.KEY -> {}
}
val centerX = minecraft.window.guiScaledWidth / 2
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.net.riding
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateDriverInputPacket
import net.minecraft.client.Minecraft
import net.minecraft.client.player.RemotePlayer
object ClientboundUpdateDriverInputHandler : ClientNetworkPacketHandler<ClientboundUpdateDriverInputPacket> {
override fun handle(packet: ClientboundUpdateDriverInputPacket, client: Minecraft) {
client.executeIfPossible {
val level = client.level ?: return@executeIfPossible
val entity = level.getEntity(packet.entityId)
if (entity is RemotePlayer) {
entity.xxa = packet.driverInput.x
entity.zza = packet.driverInput.z
entity.jumping = packet.driverInput.y == 1.0f
entity.isShiftKeyDown = packet.driverInput.y == -1.0f
}
}
}
}
@@ -255,6 +255,36 @@ abstract class PosableState : Schedulable {
//?: return@addFunction DoubleValue(0.0)
return@addFunction DoubleValue((partialTickZVel / topSpeed).coerceIn(-1.0,1.0))
}
.addFunction("input_right") { params ->
val lookBackTick = params.getInt(0)
val pokemon = getEntity() as? PokemonEntity ?: return@addFunction DoubleValue(0.0)
val partialTickZVel = pokemon.ridingAnimationData.driverInputSpring.getInterpolated(currentPartialTicks.toDouble(), lookBackTick).x
val topSpeed = 1.0//pokemon.riding.getController(pokemon)?.getStat(pokemon, RidingStat.SPEED)
//?: return@addFunction DoubleValue(0.0)
return@addFunction DoubleValue((partialTickZVel / topSpeed).coerceIn(-1.0,1.0) * -1.0)
}
.addFunction("input_up") { params ->
val lookBackTick = params.getInt(0)
val pokemon = getEntity() as? PokemonEntity ?: return@addFunction DoubleValue(0.0)
val partialTickZVel = pokemon.ridingAnimationData.driverInputSpring.getInterpolated(currentPartialTicks.toDouble(), lookBackTick).y
val topSpeed = 1.0//pokemon.riding.getController(pokemon)?.getStat(pokemon, RidingStat.SPEED)
//?: return@addFunction DoubleValue(0.0)
return@addFunction DoubleValue((partialTickZVel / topSpeed).coerceIn(-1.0,1.0))
}
.addFunction("input_forward") { params ->
val lookBackTick = params.getInt(0)
val pokemon = getEntity() as? PokemonEntity ?: return@addFunction DoubleValue(0.0)
val partialTickZVel = pokemon.ridingAnimationData.driverInputSpring.getInterpolated(currentPartialTicks.toDouble(), lookBackTick).z
val topSpeed = 1.0//pokemon.riding.getController(pokemon)?.getStat(pokemon, RidingStat.SPEED)
//?: return@addFunction DoubleValue(0.0)
return@addFunction DoubleValue((partialTickZVel / topSpeed).coerceIn(-1.0,1.0))
}
/** All of the MoLang functions that can be applied to something with this state. */
@@ -1,73 +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.gen5
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.animation.ActiveAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
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 GolurkModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
override val rootPart = root.registerChildWithAllChildren("golurk")
override val head = getPart("head")
override var portraitScale = 3.0F
override var portraitTranslation = Vec3(-0.3, 5.4, 0.0)
override var profileScale = 0.4F
override var profileTranslation = Vec3(0.0, 1.15, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
lateinit var sleep: Pose
override val cryAnimation = CryProvider { bedrockStateful("golurk", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("golurk", "blink") }
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.STATIONARY_POSES + PoseType.UI_POSES,
transformTicks = 20,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("golurk", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = PoseType.MOVING_POSES,
transformTicks = 20,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("golurk", "ground_walk")
)
)
sleep = registerPose(
poseName = "sleep",
transformTicks = 20,
poseType = PoseType.SLEEP,
animations = arrayOf(bedrock("golurk", "sleep"))
)
}
override fun getFaintAnimation(state: PosableState): ActiveAnimation? {
return if (state.isNotPosedIn(sleep)) bedrockStateful("golurk", "faint") else null
}
}
@@ -405,7 +405,6 @@ object VaryingModelRepository {
inbuilt("lombre", :: LombreModel)
inbuilt("ludicolo", :: LudicoloModel)
inbuilt("golett", :: GolettModel)
inbuilt("golurk", :: GolurkModel)
inbuilt("stantler", :: StantlerModel)
inbuilt("sneasel", :: SneaselModel)
inbuilt("weavile", :: WeavileModel)
@@ -101,9 +101,16 @@ import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolut
import com.cobblemon.mod.common.pokemon.feature.StashHandler
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
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 com.mojang.serialization.Dynamic
import java.util.Optional
import java.util.UUID
import java.util.concurrent.CompletableFuture
import kotlin.math.PI
import kotlin.math.ceil
import net.minecraft.client.Minecraft
import net.minecraft.core.BlockPos
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
@@ -158,10 +165,7 @@ import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.pathfinder.PathType
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
import java.util.*
import java.util.concurrent.CompletableFuture
import kotlin.math.PI
import kotlin.math.ceil
import kotlin.math.min
@Suppress("unused")
open class PokemonEntity(
@@ -301,6 +305,7 @@ open class PokemonEntity(
.also {
it.environment.query.addFunction("passenger_count") { DoubleValue(passengers.size.toDouble()) }
it.environment.query.addFunction("ride_velocity") { DoubleValue(getRideVelocity().length()) }
it.environment.query.addFunction("driver_input") { DoubleValue(min(ridingAnimationData.driverInputSpring.value.length(),1.0)) }
it.environment.query.addFunction("get_ride_stats") { params ->
val rideStat = RidingStat.valueOf(params.getString(0).uppercase())
val rideStyle = RidingStyle.valueOf(params.getString(1).uppercase())
@@ -560,9 +565,12 @@ open class PokemonEntity(
isPokemonFlying = flyDist - flyDistO > 0.005F
isPokemonWalking = walkDist - walkDistO > 0.005F
if (passengers.isNotEmpty()) {
if (passengers.isNotEmpty() && level().isClientSide) {
rideSoundManager.tick()
ridingAnimationData.update()
} else if (!passengers.isNotEmpty() && level().isClientSide)
{
rideSoundManager.stop()
}
flyDistO = flyDist
@@ -1766,8 +1774,8 @@ open class PokemonEntity(
}
// Rotate velocity vector to face the current y rotation
val f = Mth.sin(this.yRot * 0.017453292f)
val g = Mth.cos(this.yRot * 0.017453292f)
val f = Mth.sin(this.yRot.toRadians())
val g = Mth.cos(this.yRot.toRadians())
val v = Vec3(
inp.x * g.toDouble() - inp.z * f.toDouble(),
inp.y,
@@ -2128,8 +2136,9 @@ open class PokemonEntity(
}
}
val rotation = behaviour.rotation(settings, state, this, driver)
this.yRotO = this.yRot
val rotation = behaviour.rotation(settings, state, this, driver)
setRot(rotation.y, rotation.x)
this.yHeadRot = this.yRot
this.yBodyRot = this.yRot
@@ -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.net.messages.client.orientation
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.net.messages.client.pokemon.update.ClientboundUpdateRidingStatePacket.Companion.ID
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readMatrix3f
import com.cobblemon.mod.common.util.writeMatrix3f
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.world.entity.player.Player
import org.joml.Matrix3f
import org.joml.Vector3f
/**
* Packet sent from the server to update the current input from
* the driver of a ridden pokemon. This data is needed for client
* side animation data updates found in RidingAnimationData
*
* @author Jackowes
* @since August 1st, 2025
*/
class ClientboundUpdateDriverInputPacket internal constructor(
val driverInput: Vector3f,
val entityId: Int
) : NetworkPacket<ClientboundUpdateDriverInputPacket> {
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeVarInt(entityId)
buffer.writeVector3f(driverInput)
}
companion object {
val ID = cobblemonResource("s2c_update_driver_input")
fun decode(buffer: RegistryFriendlyByteBuf): ClientboundUpdateDriverInputPacket {
val entityId = buffer.readVarInt()
val driverInput = buffer.readVector3f()
return ClientboundUpdateDriverInputPacket(driverInput, entityId)
}
}
}
@@ -0,0 +1,39 @@
/*
* 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.server.riding
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.RegistryFriendlyByteBuf
class ServerboundUpdateDriverInputPacket internal constructor(
val xxa: Float,
val zza: Float,
val jumping: Boolean,
val crouching: Boolean
) : NetworkPacket<ServerboundUpdateDriverInputPacket> {
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeFloat(xxa)
buffer.writeFloat(zza)
buffer.writeBoolean(jumping)
buffer.writeBoolean(crouching)
}
companion object {
val ID = cobblemonResource("c2s_update_driver_input")
fun decode(buffer: RegistryFriendlyByteBuf): ServerboundUpdateDriverInputPacket {
val xxa = buffer.readFloat()
val zza = buffer.readFloat()
val jumping = buffer.readBoolean()
val crouching = buffer.readBoolean()
return ServerboundUpdateDriverInputPacket(xxa, zza, jumping, crouching)
}
}
}
@@ -0,0 +1,46 @@
/*
* 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.serverhandling.riding
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.duck.ServerEntityDuck
import com.cobblemon.mod.common.mixin.accessor.ChunkMapAccessor
import com.cobblemon.mod.common.mixin.accessor.TrackedEntityAccessor
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateDriverInputPacket
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
import org.joml.Vector3f
import kotlin.math.sign
object DriverInputPacketHandler : ServerNetworkPacketHandler<ServerboundUpdateDriverInputPacket> {
override fun handle(
packet: ServerboundUpdateDriverInputPacket,
server: MinecraftServer,
player: ServerPlayer
) {
val chunkMap = player.serverLevel().chunkSource.chunkMap
val entityMap = (chunkMap as ChunkMapAccessor).entityMap
val trackedEntity = entityMap[player.id]
if (trackedEntity is TrackedEntityAccessor) {
val serverEntity = trackedEntity.getServerEntity()
if (serverEntity is ServerEntityDuck) {
val vertInput = when {
packet.jumping-> 1.0f
packet.crouching -> -1.0f
else -> 0.0f
}
val driverInput = Vector3f(packet.xxa.sign, vertInput, packet.zza.sign)
serverEntity.setDriverInput(driverInput)
}
}
}
}
@@ -93,7 +93,13 @@ class SentOutState() : ActivePokemonState() {
this.dimension = entity.level().dimension()
}
override fun getIcon(pokemon: Pokemon) = cobblemonResource("textures/gui/party/party_icon_released.png")
override fun getIcon(pokemon: Pokemon): ResourceLocation {
val isBeingRidden = (pokemon.entity?.countPlayerPassengers() ?: 0) > 0
val icon = if (isBeingRidden) "mounted" else "released"
return cobblemonResource("textures/gui/party/party_icon_${icon}.png")
}
override fun writeToNBT(nbt: CompoundTag) = null
override fun writeToJSON(json: JsonObject) = null
@@ -19,6 +19,8 @@ import com.cobblemon.mod.common.api.riding.behaviour.types.composite.strategies.
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.strategies.RunStrategy
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.land.MinekartBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.MinekartSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.*
@@ -37,15 +39,19 @@ import java.lang.reflect.Type
object RidingBehaviourSettingsAdapter : JsonDeserializer<RidingBehaviourSettings?> {
val types: MutableMap<ResourceLocation, Class<out RidingBehaviourSettings>> = mutableMapOf(
BirdBehaviour.KEY to BirdSettings::class.java,
BirdNoRollBehaviour.KEY to BirdNoRollSettings::class.java,
DolphinBehaviour.KEY to DolphinSettings::class.java,
HorseBehaviour.KEY to HorseSettings::class.java,
BoatBehaviour.KEY to BoatSettings::class.java,
GliderBehaviour.KEY to GliderSettings::class.java,
HelicopterBehaviour.KEY to HelicopterSettings::class.java,
JetBehaviour.KEY to JetSettings::class.java,
JetNoRollBehaviour.KEY to JetNoRollSettings::class.java,
BurstBehaviour.KEY to BurstSettings::class.java,
VehicleBehaviour.KEY to VehicleSettings::class.java,
MinekartBehaviour.KEY to MinekartSettings::class.java,
HoverBehaviour.KEY to HoverSettings::class.java,
RocketBehaviour.KEY to RocketSettings::class.java,
CompositeBehaviour.KEY to CompositeSettings::class.java,
/*
@@ -6,9 +6,9 @@
"bones": {
"body": {
"rotation": [
"45*q.riding.velocity_forward(0)",
"45*math.clamp(q.riding.velocity_forward(0)* 0.5 + q.riding.input_forward(10), -1.0, 1.0)",
"0 - Math.Sin(((q.anim_time + 0.1) * 180 * 0.5) * 1) * 5",
"Math.Sin(((q.anim_time + 0.4) * 180 * 0.5) * 1) * 5 + 45*q.riding.velocity_right(0)"
"Math.Sin(((q.anim_time + 0.4) * 180 * 0.5) * 1) * 5 + 45*math.clamp(q.riding.velocity_right(0)* 0.5 + q.riding.input_right(10)* 0.5,-1.0,1.0)"
],
"position": ["Math.Sin((q.anim_time * 180 * 0.5) * 1) * 1.5", "7+Math.Sin((q.anim_time * 90 * 0.5) * 1) * 1.5", 0]
},
@@ -50,9 +50,9 @@
"bones": {
"body": {
"rotation": [
"2.5 + 45*q.riding.velocity_forward(0)",
"2.5 + 45*math.clamp(q.riding.velocity_forward(0) + q.riding.input_forward(5),-1.0,1.0)",
"- Math.Sin(((q.anim_time + 0.1) * 180 * 0.5) * 1) * 5",
"Math.Sin(((q.anim_time + 0.4) * 180 * 0.5) * 1) * 5 - 45*q.riding.velocity_right(0)"
"Math.Sin(((q.anim_time + 0.4) * 180 * 0.5) * 1) * 5 - 45*math.clamp(q.riding.velocity_right(0) + q.riding.right(5),-1.0,1.0)"
],
"position": ["Math.Sin((q.anim_time * 180 * 0.5) * 1) * 1.5", "7+Math.Sin((q.anim_time * 90 * 0.5) * 1) * 1.5", 0]
},
@@ -407,7 +407,7 @@
]
},
{
"name": "locator_seat_1",
"name": "locator_seat_2",
"parent": "body",
"pivot": [0, 0, 0],
"locators": {
@@ -4,11 +4,11 @@
{
"description": {
"identifier": "geometry.golurk",
"texture_width": 128,
"texture_height": 128,
"texture_width": 512,
"texture_height": 256,
"visible_bounds_width": 7,
"visible_bounds_height": 4.5,
"visible_bounds_offset": [0, 1.75, 0]
"visible_bounds_height": 7.5,
"visible_bounds_offset": [0, 3.25, 0]
},
"bones": [
{
@@ -18,342 +18,743 @@
{
"name": "body",
"parent": "golurk",
"pivot": [0, 38, 0]
"pivot": [0, 61, 0]
},
{
"name": "shoulder_pivot",
"parent": "body",
"pivot": [0, 68.5, 0]
},
{
"name": "torso",
"parent": "body",
"pivot": [0, 21.5, 0],
"parent": "shoulder_pivot",
"pivot": [0, 44.5, 0],
"cubes": [
{"origin": [-9, 19, -9], "size": [18, 5, 18], "uv": [0, 40]},
{"origin": [-7.5, 24, -7.5], "size": [15, 2, 15], "uv": [54, 40]},
{"origin": [-10, 26, -10], "size": [20, 20, 20], "uv": [0, 0]},
{"origin": [-8, 33, -11], "size": [11, 5, 1], "pivot": [-1.5, 35.5, -10.5], "rotation": [0, 0, 45], "uv": [98, 11]}
{"origin": [-18, 45, -18], "size": [36, 32, 36], "uv": [0, 0]},
{"origin": [-18, 51, -18], "size": [36, 24, 7], "inflate": -0.25, "uv": [170, 194]}
]
},
{
"name": "chest_glow_red",
"parent": "torso",
"pivot": [0, 63, -14.5],
"cubes": [
{"origin": [-18, 51, -18], "size": [36, 24, 7], "inflate": -0.3, "uv": [170, 225]}
]
},
{
"name": "chest_glow_off",
"parent": "torso",
"pivot": [0, 63, -14.5],
"cubes": [
{"origin": [-18, 51, -18], "size": [36, 24, 7], "inflate": -0.3, "uv": [170, 163]}
]
},
{
"name": "waist",
"parent": "torso",
"pivot": [0, 45.25, 0]
},
{
"name": "skirt",
"parent": "waist",
"pivot": [0, 45.25, 0],
"cubes": [
{"origin": [-14, 41, -14], "size": [28, 8, 28], "uv": [0, 96]},
{"origin": [-14, 41, -0.5], "size": [28, 3, 1], "inflate": 0.01, "pivot": [0, 42.5, 0], "rotation": [0, -90, 0], "uv": [109, 192]},
{"origin": [-14, 41, -0.5], "size": [28, 3, 1], "inflate": 0.01, "uv": [109, 197]},
{"origin": [-17, 34, -17], "size": [34, 7, 34], "uv": [0, 212]},
{"origin": [-17, 34, -0.5], "size": [34, 7, 1], "inflate": 0.01, "pivot": [0, 37.5, 0], "rotation": [0, -90, 0], "uv": [177, 145]},
{"origin": [-17, 34, -0.5], "size": [34, 7, 1], "inflate": 0.01, "uv": [177, 154]}
]
},
{
"name": "fire_waist_base",
"parent": "skirt",
"pivot": [0, 34.05, 0],
"cubes": [
{"origin": [-12, 34.05, -12], "size": [24, 0, 24], "uv": [244, 1]}
]
},
{
"name": "fire_waist",
"parent": "skirt",
"pivot": [0, 38.25, 0]
},
{
"name": "fire_waist_interior",
"parent": "fire_waist",
"pivot": [0, 38.25, 0],
"cubes": [
{"origin": [-16, 4.25, 0], "size": [32, 34, 0], "pivot": [0, 32.25, 0], "rotation": [0, 45, 0], "uv": [447, 35]}
]
},
{
"name": "fire_waist_interior2",
"parent": "fire_waist",
"pivot": [0, 38.25, 0],
"cubes": [
{"origin": [0, 4.25, -16], "size": [0, 34, 32], "pivot": [0, 32.25, 0], "rotation": [0, 45, 0], "uv": [447, -31]}
]
},
{
"name": "fire_waist_front",
"parent": "fire_waist",
"pivot": [0, 34, -12],
"rotation": [-10, 0, 0],
"cubes": [
{"origin": [-16, 8.25, -12], "size": [32, 30, 0], "uv": [317, 1]}
]
},
{
"name": "fire_waist_back",
"parent": "fire_waist",
"pivot": [0, 34, 12],
"rotation": [10, 0, 0],
"cubes": [
{"origin": [-16, 8.25, 12], "size": [32, 30, 0], "uv": [317, 32]}
]
},
{
"name": "fire_waist_right",
"parent": "fire_waist",
"pivot": [-12, 34, 0],
"rotation": [0, 0, 10],
"cubes": [
{"origin": [-12, 8.25, -16], "size": [0, 30, 32], "uv": [382, -31]}
]
},
{
"name": "fire_waist_left",
"parent": "fire_waist",
"pivot": [12, 34, 0],
"rotation": [0, 0, -10],
"cubes": [
{"origin": [12, 8.25, -16], "size": [0, 30, 32], "uv": [382, 0]}
]
},
{
"name": "bandage",
"parent": "torso",
"pivot": [-3.8, 64.6, -19],
"rotation": [0, 0, 45],
"cubes": [
{"origin": [-11.8, 60.6, -20], "size": [16, 8, 2], "uv": [119, 132]}
]
},
{
"name": "head",
"parent": "torso",
"pivot": [0.5, 46, 0.5],
"pivot": [0, 76, 0],
"cubes": [
{"origin": [-3, 44.75, -3], "size": [6, 6, 6], "uv": [83, 99]},
{"origin": [-4.5, 49.5, 0.5], "size": [6, 5, 0], "pivot": [1, 50.5, 0.5], "rotation": [0, 0, 10], "uv": [47, 63]}
{"origin": [-5, 74, -5], "size": [10, 10, 10], "uv": [136, 142]},
{"origin": [-1.5, 82, -1], "size": [4, 5, 2], "pivot": [1, 84.5, 0.5], "rotation": [0, 0, 10], "uv": [19, 20]},
{"origin": [-5.5, 84, 0], "size": [4, 3, 0], "pivot": [1, 84.5, 0.5], "rotation": [0, 0, 10], "uv": [26, 27]}
]
},
{
"name": "eyes",
"parent": "head",
"pivot": [0, 80, -5.01]
},
{
"name": "eye_left",
"parent": "head",
"pivot": [1.4, 47.5, -2.51],
"parent": "eyes",
"pivot": [2, 80, -5.01],
"cubes": [
{"origin": [0.65, 46.5, -3.01], "size": [1, 3, 1], "uv": [15, 12]},
{"origin": [1.15, 46.5, -3.01], "size": [1, 1, 1], "uv": [4, 18]}
]
},
{
"name": "eyelid_left",
"parent": "eye_left",
"pivot": [1.4, 47.5, -2.41],
"cubes": [
{"origin": [0.65, 46.5, -2.91], "size": [1, 3, 1], "inflate": 0.001, "uv": [15, 0]},
{"origin": [1.15, 46.5, -2.91], "size": [1, 1, 1], "inflate": 0.001, "uv": [0, 18]}
{"origin": [1, 78, -5.01], "size": [2, 4, 0], "uv": [32, 0]},
{"origin": [3, 78, -5.01], "size": [1, 2, 0], "uv": [24, 2]}
]
},
{
"name": "eye_right",
"parent": "head",
"pivot": [-1.65, 48.25, -2.51],
"parent": "eyes",
"pivot": [-2, 80, -5.01],
"cubes": [
{"origin": [-1.65, 46.5, -3.01], "size": [1, 3, 1], "uv": [0, 12]}
{"origin": [-3, 78, -5.01], "size": [2, 4, 0], "uv": [28, 0]}
]
},
{
"name": "eyelid_right",
"parent": "eye_right",
"pivot": [-1.15, 48, -2.41],
"name": "eyes_red",
"parent": "head",
"pivot": [0, 80, -4.91]
},
{
"name": "eye_red_left",
"parent": "eyes_red",
"pivot": [2, 80, -4.91],
"cubes": [
{"origin": [-1.65, 46.5, -2.91], "size": [1, 3, 1], "inflate": 0.001, "uv": [0, 0]}
{"origin": [1, 78, -4.91], "size": [2, 4, 0], "uv": [32, 4]},
{"origin": [3, 78, -4.91], "size": [1, 2, 0], "uv": [2, 2]}
]
},
{
"name": "eye_red_right",
"parent": "eyes_red",
"pivot": [-2, 80, -4.91],
"cubes": [
{"origin": [-3, 78, -4.91], "size": [2, 4, 0], "uv": [28, 4]}
]
},
{
"name": "eyes_off",
"parent": "head",
"pivot": [0, 80, -4.91]
},
{
"name": "eye_off_left",
"parent": "eyes_off",
"pivot": [2, 80, -4.91],
"cubes": [
{"origin": [1, 78, -4.91], "size": [2, 4, 0], "uv": [32, 8]},
{"origin": [3, 78, -4.91], "size": [1, 2, 0], "uv": [2, 22]}
]
},
{
"name": "eye_off_right",
"parent": "eyes_off",
"pivot": [-2, 80, -4.91],
"cubes": [
{"origin": [-3, 78, -4.91], "size": [2, 4, 0], "uv": [28, 8]}
]
},
{
"name": "shoulder_left",
"parent": "torso",
"pivot": [25.6, 67, 0],
"cubes": [
{"origin": [19.1, 76.4, -2.5], "size": [9, 5, 5], "pivot": [25.6, 68.4, 0], "rotation": [0, 0, 45], "uv": [102, 236]},
{"origin": [22.1, 81.4, -2.5], "size": [6, 5, 5], "pivot": [25.6, 68.4, 0], "rotation": [0, 0, 45], "uv": [177, 132]},
{"origin": [16.1, 59.4, -8.5], "size": [17, 17, 17], "pivot": [25.6, 68.4, 0], "rotation": [0, 0, 45], "uv": [68, 132]},
{"origin": [19.1, 62.4, -10.5], "size": [11, 11, 2], "pivot": [25.6, 68.4, -2], "rotation": [0, 0, 45], "uv": [0, 109]},
{"origin": [19.1, 62.4, 8.5], "size": [11, 11, 2], "pivot": [25.6, 68.4, 17], "rotation": [0, 0, 45], "uv": [46, 191]}
]
},
{
"name": "shoulder_glow_red_left",
"parent": "shoulder_left",
"pivot": [25.6, 68.4, -10.4],
"cubes": [
{"origin": [20.1, 63.4, -10.41], "size": [9, 9, 0], "pivot": [25.6, 68.4, -1.9], "rotation": [0, 0, 45], "uv": [200, 122]}
]
},
{
"name": "shoulder_glow_off_left",
"parent": "shoulder_left",
"pivot": [25.6, 68.4, -10.4],
"cubes": [
{"origin": [20.1, 63.4, -10.41], "size": [9, 9, 0], "pivot": [25.6, 68.4, -1.9], "rotation": [0, 0, 45], "uv": [200, 131]}
]
},
{
"name": "arm_left",
"parent": "torso",
"pivot": [13.6, 44, 0]
"parent": "shoulder_left",
"pivot": [24.1, 59.4, 0],
"cubes": [
{"origin": [20.1, 50.4, -3], "size": [8, 14, 6], "uv": [108, 0]}
]
},
{
"name": "arm_left2",
"name": "forearm_left",
"parent": "arm_left",
"pivot": [16.1, 43.9, 0],
"pivot": [24.1, 52.4, 0],
"cubes": [
{"origin": [11.1, 40.9, 4.5], "size": [7, 7, 2], "pivot": [13.6, 45.4, -1], "rotation": [0, 0, 45], "uv": [107, 17]},
{"origin": [4.6, 41.4, -2.5], "size": [5, 5, 5], "pivot": [13.6, 45.4, 0], "rotation": [0, 0, 45], "uv": [102, 106]},
{"origin": [11.1, 40.9, -6.5], "size": [7, 7, 2], "pivot": [13.6, 45.4, -1], "rotation": [0, 0, 45], "uv": [29, 63]},
{"origin": [9.6, 39.4, -4.5], "size": [10, 10, 9], "pivot": [13.6, 45.4, 0], "rotation": [0, 0, 45], "uv": [0, 63]},
{"origin": [16.6, 41.4, -2.5], "size": [9, 5, 5], "uv": [99, 42]}
{"origin": [16.6, 47.4, -8], "size": [15, 5, 16], "uv": [62, 191]},
{"origin": [13.6, 39.9, -10], "size": [21, 8, 20], "uv": [0, 68]}
]
},
{
"name": "fire_hand_base_left",
"parent": "forearm_left",
"pivot": [24.1, 39.95, 0],
"cubes": [
{"origin": [16.1, 39.95, -8], "size": [16, 0, 16], "uv": [326, 124], "mirror": true}
]
},
{
"name": "fire_hand_left",
"parent": "forearm_left",
"pivot": [24.1, 42.15, 0]
},
{
"name": "fire_hand_interior_left1",
"parent": "fire_hand_left",
"pivot": [24.1, 44.15, 0],
"cubes": [
{"origin": [12.6, 17.15, 0], "size": [23, 25, 0], "pivot": [24.1, 38.15, 0], "rotation": [0, -45, 0], "uv": [465, 150], "mirror": true}
]
},
{
"name": "fire_hand_interior_left2",
"parent": "fire_hand_left",
"pivot": [24.1, 44.15, 0],
"cubes": [
{"origin": [24.1, 17.15, -11.5], "size": [0, 25, 23], "pivot": [24.1, 38.15, 0], "rotation": [0, -45, 0], "uv": [465, 101], "mirror": true}
]
},
{
"name": "fire_hand_front_left",
"parent": "fire_hand_left",
"pivot": [24.1, 39.9, -8],
"rotation": [-10, 0, 0],
"cubes": [
{"origin": [13.1, 20.15, -8], "size": [22, 22, 0], "uv": [375, 124], "mirror": true}
]
},
{
"name": "fire_hand_back_left",
"parent": "fire_hand_left",
"pivot": [24.1, 39.9, 8],
"rotation": [10, 0, 0],
"cubes": [
{"origin": [13.1, 20.15, 8], "size": [22, 22, 0], "uv": [375, 147], "mirror": true}
]
},
{
"name": "fire_hand_outer_left",
"parent": "fire_hand_left",
"pivot": [32.1, 39.9, 0],
"rotation": [0, 0, -10],
"cubes": [
{"origin": [32.1, 20.15, -11], "size": [0, 22, 22], "uv": [420, 102], "mirror": true}
]
},
{
"name": "fire_hand_inner_left",
"parent": "fire_hand_left",
"pivot": [16.1, 39.9, 0],
"rotation": [0, 0, 10],
"cubes": [
{"origin": [16.1, 20.15, -11], "size": [0, 22, 22], "uv": [420, 125], "mirror": true}
]
},
{
"name": "hand_left",
"parent": "arm_left2",
"pivot": [24.85, 44, 0],
"parent": "forearm_left",
"pivot": [24.1, 39.9, 0],
"cubes": [
{"origin": [26.6, 38.4, -5.5], "size": [5, 11, 11], "uv": [38, 68]},
{"origin": [23.6, 39.4, -4.5], "size": [3, 9, 9], "uv": [0, 96]}
{"origin": [19.6, 27.9, -8], "size": [9, 12, 16], "inflate": 0.02, "uv": [186, 0]},
{"origin": [27.6, 28.9, -3.3], "size": [3, 9, 9], "pivot": [26.1, 34.9, 0.7], "rotation": [-45, 0, 0], "uv": [102, 218]}
]
},
{
"name": "hand_left2",
"name": "hand_glow_red_left",
"parent": "hand_left",
"pivot": [31.85, 42.5, 0],
"pivot": [30.5, 34.9, 0.7],
"cubes": [
{"origin": [32.25, 46.9, -2.75], "size": [5, 1, 5], "pivot": [34.5, 47.4, 0], "rotation": [0, -45, 0], "uv": [101, 99]},
{"origin": [31.1, 40.9, -4.5], "size": [7, 6, 9], "uv": [27, 90]}
{"origin": [30.51, 29.9, -2.3], "size": [0, 7, 7], "pivot": [26, 34.9, 0.7], "rotation": [-45, 0, 0], "uv": [238, 99]}
]
},
{
"name": "finger_left",
"parent": "hand_left2",
"pivot": [38.1, 46.4, -3.5],
"name": "hand_glow_off_left",
"parent": "hand_left",
"pivot": [30.5, 34.9, 0.7],
"cubes": [
{"origin": [38.1, 44.4, -4.5], "size": [3, 2, 2], "inflate": 0.001, "uv": [92, 32]}
{"origin": [30.51, 29.9, -2.3], "size": [0, 7, 7], "pivot": [26, 34.9, 0.7], "rotation": [-45, 0, 0], "uv": [238, 106]}
]
},
{
"name": "finger_left2",
"parent": "finger_left",
"pivot": [41.1, 46.4, -3.5],
"name": "finger_thumb_left",
"parent": "hand_left",
"pivot": [19.6, 34.9, -8],
"rotation": [-12.5, 0, 25],
"cubes": [
{"origin": [41.1, 44.4, -4.5], "size": [2, 2, 2], "uv": [60, 94]}
{"origin": [16.6, 29.9, -10.5], "size": [6, 9, 7], "uv": [108, 20]}
]
},
{
"name": "finger_left3",
"parent": "hand_left2",
"pivot": [38.1, 46.4, -1.25],
"name": "finger_thumb_left2",
"parent": "finger_thumb_left",
"pivot": [19.1, 29.9, -10.5],
"cubes": [
{"origin": [38.1, 44.4, -2.25], "size": [3, 2, 2], "inflate": 0.001, "uv": [86, 84]}
{"origin": [17.1, 23.9, -10.5], "size": [5, 6, 5], "uv": [0, 68]}
]
},
{
"name": "finger_left4",
"parent": "finger_left3",
"pivot": [41.1, 46.4, -1.25],
"name": "finger_index_left",
"parent": "hand_left",
"pivot": [26.6, 27.9, -6],
"cubes": [
{"origin": [41.1, 44.4, -2.25], "size": [3, 2, 2], "uv": [27, 86]}
{"origin": [21.6, 21.9, -8], "size": [6, 6, 4], "inflate": 0.015, "uv": [132, 122]}
]
},
{
"name": "finger_left5",
"parent": "hand_left2",
"pivot": [38.1, 46.4, 1],
"name": "finger_index_left2",
"parent": "finger_index_left",
"pivot": [27.1, 21.9, -6],
"cubes": [
{"origin": [38.1, 44.4, 0], "size": [3, 2, 2], "inflate": 0.001, "uv": [27, 82]}
{"origin": [22.1, 15.9, -8], "size": [5, 6, 4], "uv": [154, 96]}
]
},
{
"name": "finger_left6",
"parent": "finger_left5",
"pivot": [41.1, 46.4, 1],
"name": "finger_middle_left",
"parent": "hand_left",
"pivot": [26.6, 27.9, -2],
"cubes": [
{"origin": [41.1, 44.4, 0], "size": [2, 2, 2], "uv": [90, 36]}
{"origin": [21.6, 21.9, -4], "size": [6, 6, 4], "inflate": 0.01, "uv": [221, 29]}
]
},
{
"name": "finger_left7",
"parent": "hand_left2",
"pivot": [38.1, 46.4, 3.25],
"name": "finger_middle_left2",
"parent": "finger_middle_left",
"pivot": [27.1, 21.9, -2],
"cubes": [
{"origin": [38.1, 44.4, 2.25], "size": [2, 2, 2], "inflate": 0.001, "uv": [0, 86]}
{"origin": [22.1, 15.9, -4], "size": [5, 6, 4], "uv": [62, 78]}
]
},
{
"name": "finger_left8",
"parent": "finger_left7",
"pivot": [40.1, 46.4, 3.25],
"name": "finger_ring_left",
"parent": "hand_left",
"pivot": [26.6, 27.9, 2],
"cubes": [
{"origin": [40.1, 44.4, 2.25], "size": [2, 2, 2], "uv": [0, 82]}
{"origin": [21.6, 21.9, 0], "size": [6, 6, 4], "inflate": 0.015, "uv": [172, 122]}
]
},
{
"name": "finger_left9",
"parent": "hand_left2",
"pivot": [35.6, 43.15, -3.5],
"name": "finger_ring_left2",
"parent": "finger_ring_left",
"pivot": [27.1, 21.9, 2],
"cubes": [
{"origin": [35.1, 41.65, -5.5], "size": [3, 3, 3], "inflate": 0.001, "uv": [50, 90]}
{"origin": [22.1, 15.9, 0], "size": [5, 6, 4], "uv": [62, 68]}
]
},
{
"name": "finger_left10",
"parent": "finger_left9",
"pivot": [38.1, 43.15, -5.5],
"name": "finger_pinky_left",
"parent": "hand_left",
"pivot": [26.6, 27.9, 6],
"cubes": [
{"origin": [38.1, 41.65, -5.5], "size": [3, 3, 3], "uv": [89, 0]}
{"origin": [21.6, 21.9, 4], "size": [6, 6, 4], "inflate": 0.01, "uv": [226, 96]}
]
},
{
"name": "finger_pinky_left2",
"parent": "finger_pinky_left",
"pivot": [27.1, 21.9, 6],
"cubes": [
{"origin": [22.1, 16.9, 4], "size": [5, 5, 4], "uv": [2, 79]}
]
},
{
"name": "hand_fire_left",
"parent": "forearm_left",
"pivot": [24, 40.25, 0]
},
{
"name": "shoulder_right",
"parent": "torso",
"pivot": [-25.6, 67, 0],
"cubes": [
{"origin": [-28.1, 76.4, -2.5], "size": [9, 5, 5], "pivot": [-25.6, 68.4, 0], "rotation": [0, 0, -45], "uv": [0, 236]},
{"origin": [-28.1, 81.4, -2.5], "size": [6, 5, 5], "pivot": [-25.6, 68.4, 0], "rotation": [0, 0, -45], "uv": [155, 132]},
{"origin": [-33.1, 59.4, -8.5], "size": [17, 17, 17], "pivot": [-25.6, 68.4, 0], "rotation": [0, 0, -45], "uv": [0, 132]},
{"origin": [-30.1, 62.4, -10.5], "size": [11, 11, 2], "pivot": [-25.6, 68.4, -2], "rotation": [0, 0, -45], "uv": [0, 96]},
{"origin": [-30.1, 62.4, 8.5], "size": [11, 11, 2], "pivot": [-25.6, 68.4, 17], "rotation": [0, 0, -45], "uv": [51, 132]}
]
},
{
"name": "shoulder_glow_red_right",
"parent": "shoulder_right",
"pivot": [-25.6, 68.4, -10.4],
"cubes": [
{"origin": [-29.1, 63.4, -10.41], "size": [9, 9, 0], "pivot": [-25.6, 68.4, -1.9], "rotation": [0, 0, -45], "uv": [218, 122]}
]
},
{
"name": "shoulder_glow_off_right",
"parent": "shoulder_right",
"pivot": [-25.6, 68.4, -10.4],
"cubes": [
{"origin": [-29.1, 63.4, -10.41], "size": [9, 9, 0], "pivot": [-25.6, 68.4, -1.9], "rotation": [0, 0, -45], "uv": [218, 131]}
]
},
{
"name": "arm_right",
"parent": "torso",
"pivot": [-13.6, 44, 0]
"parent": "shoulder_right",
"pivot": [-24.1, 59.4, 0],
"cubes": [
{"origin": [-28.1, 50.4, -3], "size": [8, 14, 6], "uv": [0, 0]}
]
},
{
"name": "arm_right2",
"name": "forearm_right",
"parent": "arm_right",
"pivot": [-16.1, 43.9, 0],
"pivot": [-24.1, 52.4, 0],
"cubes": [
{"origin": [-25.6, 41.4, -2.5], "size": [9, 5, 5], "uv": [99, 32]},
{"origin": [-19.6, 39.4, -4.5], "size": [10, 10, 9], "pivot": [-13.6, 45.4, 0], "rotation": [0, 0, -45], "uv": [60, 0]},
{"origin": [-18.1, 40.9, -6.5], "size": [7, 7, 2], "pivot": [-13.6, 45.4, -1], "rotation": [0, 0, -45], "uv": [0, 40]},
{"origin": [-9.6, 41.4, -2.5], "size": [5, 5, 5], "pivot": [-13.6, 45.4, 0], "rotation": [0, 0, -45], "uv": [44, 105]},
{"origin": [-18.1, 40.9, 4.5], "size": [7, 7, 2], "pivot": [-13.6, 45.4, -1], "rotation": [0, 0, -45], "uv": [0, 49]}
{"origin": [-31.6, 47.4, -8], "size": [15, 5, 16], "uv": [0, 191]},
{"origin": [-34.6, 39.9, -10], "size": [21, 8, 20], "uv": [82, 68]}
]
},
{
"name": "fire_hand_base_right",
"parent": "forearm_right",
"pivot": [-24.1, 39.95, 0],
"cubes": [
{"origin": [-32.1, 39.95, -8], "size": [16, 0, 16], "uv": [326, 71]}
]
},
{
"name": "fire_hand_right",
"parent": "forearm_right",
"pivot": [-24.1, 42.15, 0]
},
{
"name": "fire_hand_interior_right1",
"parent": "fire_hand_right",
"pivot": [-24.1, 44.15, 0],
"cubes": [
{"origin": [-35.6, 17.15, 0], "size": [23, 25, 0], "pivot": [-24.1, 38.15, 0], "rotation": [0, 45, 0], "uv": [465, 97]}
]
},
{
"name": "fire_hand_interior_right2",
"parent": "fire_hand_right",
"pivot": [-24.1, 44.15, 0],
"cubes": [
{"origin": [-24.1, 17.15, -11.5], "size": [0, 25, 23], "pivot": [-24.1, 38.15, 0], "rotation": [0, 45, 0], "uv": [465, 48]}
]
},
{
"name": "fire_hand_front_right",
"parent": "fire_hand_right",
"pivot": [-24.1, 39.9, -8],
"rotation": [-10, 0, 0],
"cubes": [
{"origin": [-35.1, 20.15, -8], "size": [22, 22, 0], "uv": [375, 71]}
]
},
{
"name": "fire_hand_back_right",
"parent": "fire_hand_right",
"pivot": [-24.1, 39.9, 8],
"rotation": [10, 0, 0],
"cubes": [
{"origin": [-35.1, 20.15, 8], "size": [22, 22, 0], "uv": [375, 94]}
]
},
{
"name": "fire_hand_outer_right",
"parent": "fire_hand_right",
"pivot": [-32.1, 39.9, 0],
"rotation": [0, 0, 10],
"cubes": [
{"origin": [-32.1, 20.15, -11], "size": [0, 22, 22], "uv": [420, 49]}
]
},
{
"name": "fire_hand_inner_right",
"parent": "fire_hand_right",
"pivot": [-16.1, 39.9, 0],
"rotation": [0, 0, -10],
"cubes": [
{"origin": [-16.1, 20.15, -11], "size": [0, 22, 22], "uv": [420, 72]}
]
},
{
"name": "hand_right",
"parent": "arm_right2",
"pivot": [-24.85, 44, 0],
"parent": "forearm_right",
"pivot": [-24.1, 39.9, 0],
"cubes": [
{"origin": [-31.6, 38.4, -5.5], "size": [5, 11, 11], "uv": [61, 57]},
{"origin": [-26.6, 39.4, -4.5], "size": [3, 9, 9], "uv": [59, 92]}
{"origin": [-28.6, 27.9, -8], "size": [9, 12, 16], "inflate": 0.02, "uv": [136, 0]},
{"origin": [-30.6, 28.9, -3.3], "size": [3, 9, 9], "pivot": [-26.1, 34.9, 0.7], "rotation": [-45, 0, 0], "uv": [0, 218]}
]
},
{
"name": "hand_right2",
"name": "hand_glow_red_right",
"parent": "hand_right",
"pivot": [-31.85, 42.5, 0],
"pivot": [-30.5, 34.9, 0.7],
"cubes": [
{"origin": [-37.25, 46.9, -2.75], "size": [5, 1, 5], "pivot": [-34.5, 47.4, 0], "rotation": [0, 45, 0], "uv": [0, 12]},
{"origin": [-38.1, 40.9, -4.5], "size": [7, 6, 9], "uv": [88, 84]}
{"origin": [-30.51, 29.9, -2.3], "size": [0, 7, 7], "pivot": [-26, 34.9, 0.7], "rotation": [-45, 0, 0], "uv": [224, 99]}
]
},
{
"name": "finger_right",
"parent": "hand_right2",
"pivot": [-38.1, 46.4, -3.5],
"name": "hand_glow_off_right",
"parent": "hand_right",
"pivot": [-30.5, 34.9, 0.7],
"cubes": [
{"origin": [-41.1, 44.4, -4.5], "size": [3, 2, 2], "inflate": 0.001, "uv": [80, 23]}
{"origin": [-30.51, 29.9, -2.3], "size": [0, 7, 7], "pivot": [-26, 34.9, 0.7], "rotation": [-45, 0, 0], "uv": [224, 106]}
]
},
{
"name": "finger_right2",
"parent": "finger_right",
"pivot": [-41.1, 46.4, -3.5],
"name": "finger_thumb_right",
"parent": "hand_right",
"pivot": [-19.6, 34.9, -8],
"rotation": [-12.5, 0, -25],
"cubes": [
{"origin": [-43.1, 44.4, -4.5], "size": [2, 2, 2], "uv": [0, 67]}
{"origin": [-22.6, 29.9, -10.5], "size": [6, 9, 7], "uv": [0, 20]}
]
},
{
"name": "finger_right3",
"parent": "hand_right2",
"pivot": [-38.1, 46.4, -1.25],
"name": "finger_thumb_right2",
"parent": "finger_thumb_right",
"pivot": [-19.1, 29.9, -10.5],
"cubes": [
{"origin": [-41.1, 44.4, -2.25], "size": [3, 2, 2], "inflate": 0.001, "uv": [80, 19]}
{"origin": [-22.1, 23.9, -10.5], "size": [5, 6, 5], "uv": [82, 68]}
]
},
{
"name": "finger_right4",
"parent": "finger_right3",
"pivot": [-41.1, 46.4, -1.25],
"name": "finger_index_right",
"parent": "hand_right",
"pivot": [-26.6, 27.9, -6],
"cubes": [
{"origin": [-44.1, 44.4, -2.25], "size": [3, 2, 2], "uv": [38, 72]}
{"origin": [-27.6, 21.9, -8], "size": [6, 6, 4], "inflate": 0.015, "uv": [112, 122]}
]
},
{
"name": "finger_right5",
"parent": "hand_right2",
"pivot": [-38.1, 46.4, 1],
"name": "finger_index_right2",
"parent": "finger_index_right",
"pivot": [-27.1, 21.9, -6],
"cubes": [
{"origin": [-41.1, 44.4, 0], "size": [3, 2, 2], "inflate": 0.001, "uv": [59, 63]}
{"origin": [-27.1, 15.9, -8], "size": [5, 6, 4], "uv": [136, 96]}
]
},
{
"name": "finger_right6",
"parent": "finger_right5",
"pivot": [-41.1, 46.4, 1],
"name": "finger_middle_right",
"parent": "hand_right",
"pivot": [-26.6, 27.9, -2],
"cubes": [
{"origin": [-43.1, 44.4, 0], "size": [2, 2, 2], "uv": [0, 63]}
{"origin": [-27.6, 21.9, -4], "size": [6, 6, 4], "inflate": 0.01, "uv": [177, 29]}
]
},
{
"name": "finger_right7",
"parent": "hand_right2",
"pivot": [-38.1, 46.4, 3.25],
"name": "finger_middle_right2",
"parent": "finger_middle_right",
"pivot": [-27.1, 21.9, -2],
"cubes": [
{"origin": [-40.1, 44.4, 2.25], "size": [2, 2, 2], "inflate": 0.001, "uv": [60, 4]}
{"origin": [-27.1, 15.9, -4], "size": [5, 6, 4], "uv": [144, 78]}
]
},
{
"name": "finger_right8",
"parent": "finger_right7",
"pivot": [-40.1, 46.4, 3.25],
"name": "finger_ring_right",
"parent": "hand_right",
"pivot": [-26.6, 27.9, 2],
"cubes": [
{"origin": [-42.1, 44.4, 2.25], "size": [2, 2, 2], "uv": [60, 0]}
{"origin": [-27.6, 21.9, 0], "size": [6, 6, 4], "inflate": 0.015, "uv": [152, 122]}
]
},
{
"name": "finger_right9",
"parent": "hand_right2",
"pivot": [-35.6, 43.15, -3.5],
"name": "finger_ring_right2",
"parent": "finger_ring_right",
"pivot": [-27.1, 21.9, 2],
"cubes": [
{"origin": [-38.1, 41.65, -5.5], "size": [3, 3, 3], "inflate": 0.001, "uv": [80, 32]}
{"origin": [-27.1, 15.9, 0], "size": [5, 6, 4], "uv": [144, 68]}
]
},
{
"name": "finger_right10",
"parent": "finger_right9",
"pivot": [-38.1, 43.15, -5.5],
"name": "finger_pinky_right",
"parent": "hand_right",
"pivot": [-26.6, 27.9, 6],
"cubes": [
{"origin": [-41.1, 41.65, -5.5], "size": [3, 3, 3], "uv": [54, 49]}
{"origin": [-27.6, 21.9, 4], "size": [6, 6, 4], "inflate": 0.01, "uv": [206, 96]}
]
},
{
"name": "finger_pinky_right2",
"parent": "finger_pinky_right",
"pivot": [-27.1, 21.9, 6],
"cubes": [
{"origin": [-27.1, 16.9, 4], "size": [5, 5, 4], "uv": [84, 79]}
]
},
{
"name": "hand_fire_right",
"parent": "forearm_right",
"pivot": [-24, 40.25, 0]
},
{
"name": "seat_1_locator",
"parent": "torso",
"pivot": [0, 77, 9],
"locators": {
"seat_1": [0, 77, 10]
}
},
{
"name": "seat_2_locator",
"parent": "torso",
"pivot": [-14, 77, 13],
"locators": {
"seat_2": [-14, 77, 13]
}
},
{
"name": "seat_3_locator",
"parent": "torso",
"pivot": [14, 77, 13],
"locators": {
"seat_3": [14, 77, 13]
}
},
{
"name": "legs",
"parent": "shoulder_pivot",
"pivot": [0, 37, 0]
},
{
"name": "leg_left",
"parent": "body",
"pivot": [5, 20, 0],
"parent": "legs",
"pivot": [9.5, 37, 0],
"cubes": [
{"origin": [2.5, 14, -2.5], "size": [5, 7, 5], "uv": [24, 105]}
{"origin": [4, 27, -5.5], "size": [11, 11, 11], "uv": [188, 28]}
]
},
{
"name": "knee_left",
"parent": "leg_left",
"pivot": [9.5, 28, -0.25],
"cubes": [
{"origin": [6, 23, -3.5], "size": [7, 7, 7], "uv": [172, 50]}
]
},
{
"name": "leg_left2",
"parent": "leg_left",
"pivot": [5, 15, -0.25],
"parent": "knee_left",
"pivot": [9.5, 22.5, 0],
"cubes": [
{"origin": [3.5, 10, -2.25], "size": [3, 5, 4], "uv": [74, 92]}
{"origin": [3, 16, -6.5], "size": [13, 8, 13], "uv": [201, 56]},
{"origin": [0, 8, -8.5], "size": [19, 8, 17], "uv": [72, 166]}
]
},
{
"name": "foot_left",
"parent": "leg_left2",
"pivot": [5, 9.5, 0],
"pivot": [9.5, 8, -2],
"cubes": [
{"origin": [1.5, 7, -3.5], "size": [7, 4, 7], "uv": [98, 0]},
{"origin": [0.5, 2, -4.5], "size": [9, 5, 9], "uv": [86, 70]},
{"origin": [1, 0, -7.5], "size": [8, 2, 11], "uv": [80, 19]}
{"origin": [1, 0, -9.5], "size": [17, 8, 18], "uv": [154, 96]},
{"origin": [1, 0, -14.5], "size": [17, 6, 5], "uv": [208, 85]}
]
},
{
"name": "leg_right",
"parent": "body",
"pivot": [-5, 20, 0],
"parent": "legs",
"pivot": [-9.5, 37, 0],
"cubes": [
{"origin": [-7.5, 14, -2.5], "size": [5, 7, 5], "uv": [0, 0]}
{"origin": [-15, 27, -5.5], "size": [11, 11, 11], "uv": [144, 28]}
]
},
{
"name": "knee_right",
"parent": "leg_right",
"pivot": [-9.5, 28, -0.25],
"cubes": [
{"origin": [-13, 23, -3.5], "size": [7, 7, 7], "uv": [144, 50]}
]
},
{
"name": "leg_right2",
"parent": "leg_right",
"pivot": [-5, 15, -0.25],
"parent": "knee_right",
"pivot": [-9.5, 22.5, 0],
"cubes": [
{"origin": [-6.5, 10, -2.25], "size": [3, 5, 4], "uv": [54, 40]}
{"origin": [-16, 16, -6.5], "size": [13, 8, 13], "uv": [162, 64]},
{"origin": [-19, 8, -8.5], "size": [19, 8, 17], "uv": [0, 166]}
]
},
{
"name": "foot_right",
"parent": "leg_right2",
"pivot": [-5, 10.5, 0],
"pivot": [-9.5, 8, -2],
"cubes": [
{"origin": [-8.5, 7, -3.5], "size": [7, 4, 7], "uv": [82, 57]},
{"origin": [-9.5, 2, -4.5], "size": [9, 5, 9], "uv": [0, 82]},
{"origin": [-9, 0, -7.5], "size": [8, 2, 11], "uv": [59, 79]}
{"origin": [-18, 0, -9.5], "size": [17, 8, 18], "uv": [84, 96]},
{"origin": [-18, 0, -14.5], "size": [17, 6, 5], "uv": [164, 85]}
]
}
]
@@ -18,19 +18,32 @@
{
"name": "body",
"parent": "cyclizar",
"pivot": [0, 0, 0]
"pivot": [0, 11, 3.5]
},
{
"name": "hips",
"parent": "body",
"pivot": [0, 13.4, 5.75]
},
{
"name": "torso",
"parent": "hips",
"pivot": [0, 13.4, 5.75],
"cubes": [
{"origin": [-3, 10.75, -4], "size": [6, 7, 11], "inflate": 0.01, "uv": [40, 0]}
]
},
{
"name": "seat_1_locator",
"parent": "torso",
"pivot": [0, 17.75, 2],
"locators": {
"seat_1": [0, 17.75, 2]
}
},
{
"name": "seat",
"parent": "hips",
"parent": "torso",
"pivot": [0, 17.75, 4.5],
"rotation": [22.5, 0, 0],
"cubes": [
@@ -40,8 +53,8 @@
]
},
{
"name": "torso",
"parent": "hips",
"name": "torso2",
"parent": "torso",
"pivot": [0, 14, -3.05],
"cubes": [
{"origin": [-3.5, 10.75, -10.5], "size": [7, 7, 8], "uv": [40, 54]}
@@ -49,7 +62,7 @@
},
{
"name": "neck",
"parent": "torso",
"parent": "torso2",
"pivot": [0, 14, -9.35],
"rotation": [82.5, 0, 0],
"cubes": [
@@ -233,185 +246,9 @@
{"origin": [1.9, 20.9, -16], "size": [1, 2, 3], "inflate": 0.03, "uv": [0, 118], "mirror": true}
]
},
{
"name": "arm_left",
"parent": "torso",
"pivot": [3.25, 13.05, -4.75],
"cubes": [
{"origin": [1.75, 7.25, -6.75], "size": [3, 8, 4], "uv": [13, 116]}
]
},
{
"name": "elbow_left",
"parent": "arm_left",
"pivot": [3.25, 7.05, -4.75],
"cubes": [
{"origin": [1.75, 0.55, -6.25], "size": [3, 7, 3], "inflate": 0.25, "uv": [44, 81]}
]
},
{
"name": "hand_left",
"parent": "elbow_left",
"pivot": [3.25, 0.55, -5],
"cubes": [
{"origin": [1.25, -0.95, -7.75], "size": [4, 3, 4], "inflate": 0.01, "uv": [74, 98]}
]
},
{
"name": "fingers_left",
"parent": "hand_left",
"pivot": [3.25, 0.05, -7.5]
},
{
"name": "finger_left_left2",
"parent": "fingers_left",
"pivot": [2, 1.05, -7.75],
"rotation": [0, 22.5, 0],
"cubes": [
{"origin": [1, -0.95, -9.25], "size": [1, 2, 3], "uv": [54, 107]}
]
},
{
"name": "finger_left_left3",
"parent": "finger_left_left2",
"pivot": [1.5, 1.05, -9.25],
"cubes": [
{"origin": [1.5, -0.95, -11.25], "size": [0, 2, 2], "uv": [36, 112]}
]
},
{
"name": "middle_fingers_left",
"parent": "fingers_left",
"pivot": [3.25, 0.3, -7],
"cubes": [
{"origin": [2.25, -0.7, -8.75], "size": [2, 2, 2], "inflate": 0.25, "uv": [108, 33]}
]
},
{
"name": "middle_fingers_left2",
"parent": "middle_fingers_left",
"pivot": [3.25, 1.53333, -9.01667],
"cubes": [
{"origin": [2.25, -0.7, -10.25], "size": [2, 2, 1], "inflate": 0.26, "uv": [18, 111]}
]
},
{
"name": "middle_fingers_left3",
"parent": "middle_fingers_left2",
"pivot": [3.25, 1.05, -10.5],
"cubes": [
{"origin": [3.75, -0.95, -12.5], "size": [0, 2, 2], "uv": [40, 112]},
{"origin": [2.75, -0.95, -12.5], "size": [0, 2, 2], "uv": [112, 41]}
]
},
{
"name": "finger_left_left",
"parent": "fingers_left",
"pivot": [4.5, 1.05, -7.75],
"rotation": [0, -22.5, 0],
"cubes": [
{"origin": [4.5, -0.95, -9.25], "size": [1, 2, 3], "uv": [62, 107]}
]
},
{
"name": "finger_right_left4",
"parent": "finger_left_left",
"pivot": [5, 1.05, -9.25],
"cubes": [
{"origin": [5, -0.95, -11.25], "size": [0, 2, 2], "uv": [44, 112]}
]
},
{
"name": "arm_right",
"parent": "torso",
"pivot": [-3.25, 13.05, -4.75],
"cubes": [
{"origin": [-4.75, 7.25, -6.75], "size": [3, 8, 4], "uv": [29, 116]}
]
},
{
"name": "elbow_right",
"parent": "arm_right",
"pivot": [-3.25, 7.05, -4.75],
"cubes": [
{"origin": [-4.75, 0.55, -6.25], "size": [3, 7, 3], "inflate": 0.25, "uv": [0, 101]}
]
},
{
"name": "hand_right",
"parent": "elbow_right",
"pivot": [-3.25, 0.55, -5],
"cubes": [
{"origin": [-5.25, -0.95, -7.75], "size": [4, 3, 4], "inflate": 0.01, "uv": [100, 22]}
]
},
{
"name": "fingers_right",
"parent": "hand_right",
"pivot": [-3.25, 0.05, -7.5]
},
{
"name": "finger_left_right2",
"parent": "fingers_right",
"pivot": [-2, 1.05, -7.75],
"rotation": [0, -22.5, 0],
"cubes": [
{"origin": [-2, -0.95, -9.25], "size": [1, 2, 3], "uv": [106, 106]}
]
},
{
"name": "finger_left_right3",
"parent": "finger_left_right2",
"pivot": [-1.5, 1.05, -9.25],
"cubes": [
{"origin": [-1.5, -0.95, -11.25], "size": [0, 2, 2], "uv": [119, 93]}
]
},
{
"name": "middle_fingers_right",
"parent": "fingers_right",
"pivot": [-3.25, 0.3, -7],
"cubes": [
{"origin": [-4.25, -0.7, -8.75], "size": [2, 2, 2], "inflate": 0.25, "uv": [108, 29]}
]
},
{
"name": "middle_fingers_right2",
"parent": "middle_fingers_right",
"pivot": [-3.25, 1.53333, -9.01667],
"cubes": [
{"origin": [-4.25, -0.7, -10.25], "size": [2, 2, 1], "inflate": 0.26, "uv": [106, 66]}
]
},
{
"name": "middle_fingers_right3",
"parent": "middle_fingers_right2",
"pivot": [-3.25, 1.05, -10.5],
"cubes": [
{"origin": [-3.75, -0.95, -12.5], "size": [0, 2, 2], "uv": [28, 112]},
{"origin": [-2.75, -0.95, -12.5], "size": [0, 2, 2], "uv": [123, 93]}
]
},
{
"name": "finger_right_left",
"parent": "fingers_right",
"pivot": [-4.5, 1.05, -7.75],
"rotation": [0, 22.5, 0],
"cubes": [
{"origin": [-5.5, -0.95, -9.25], "size": [1, 2, 3], "uv": [28, 107]}
]
},
{
"name": "finger_right_left2",
"parent": "finger_right_left",
"pivot": [-5, 1.05, -9.25],
"cubes": [
{"origin": [-5, -0.95, -11.25], "size": [0, 2, 2], "uv": [32, 112]}
]
},
{
"name": "tail",
"parent": "hips",
"parent": "torso",
"pivot": [0, 14.5, 6.75],
"rotation": [17.5, 0, 0],
"cubes": [
@@ -460,13 +297,194 @@
},
{
"name": "tail_wheel",
"parent": "hips",
"parent": "torso",
"pivot": [0, 14.5, 5.75],
"rotation": [7.5, 0, 0],
"cubes": [
{"origin": [-2, 1.5, 1.75], "size": [4, 16, 16], "inflate": 0.02, "uv": [0, 0]}
]
},
{
"name": "ftorso2",
"parent": "hips",
"pivot": [0, 14, -3.05]
},
{
"name": "arm_left",
"parent": "ftorso2",
"pivot": [3.25, 13.05, -4.75],
"cubes": [
{"origin": [1.75, 7.25, -6.75], "size": [3, 8, 4], "uv": [13, 116]}
]
},
{
"name": "elbow_left",
"parent": "arm_left",
"pivot": [3.25, 7.05, -4.75],
"cubes": [
{"origin": [1.75, 0.55, -6.25], "size": [3, 7, 3], "inflate": 0.25, "uv": [44, 81]}
]
},
{
"name": "hand_left",
"parent": "elbow_left",
"pivot": [3.25, 0.55, -5],
"cubes": [
{"origin": [1.25, -0.95, -7.75], "size": [4, 3, 4], "inflate": 0.01, "uv": [74, 98]}
]
},
{
"name": "fingers_left",
"parent": "hand_left",
"pivot": [3.25, 0.05, -7.5]
},
{
"name": "finger_left_right",
"parent": "fingers_left",
"pivot": [2, 1.05, -7.75],
"rotation": [0, 22.5, 0],
"cubes": [
{"origin": [1, -0.95, -9.25], "size": [1, 2, 3], "uv": [54, 107]}
]
},
{
"name": "finger_left_right2",
"parent": "finger_left_right",
"pivot": [1.5, 1.05, -9.25],
"cubes": [
{"origin": [1.5, -0.95, -11.25], "size": [0, 2, 2], "uv": [36, 112]}
]
},
{
"name": "middle_fingers_left",
"parent": "fingers_left",
"pivot": [3.25, 0.3, -7],
"cubes": [
{"origin": [2.25, -0.7, -8.75], "size": [2, 2, 2], "inflate": 0.25, "uv": [108, 33]}
]
},
{
"name": "middle_fingers_left2",
"parent": "middle_fingers_left",
"pivot": [3.25, 1.53333, -9.01667],
"cubes": [
{"origin": [2.25, -0.7, -10.25], "size": [2, 2, 1], "inflate": 0.26, "uv": [18, 111]}
]
},
{
"name": "middle_fingers_left3",
"parent": "middle_fingers_left2",
"pivot": [3.25, 1.05, -10.5],
"cubes": [
{"origin": [3.75, -0.95, -12.5], "size": [0, 2, 2], "uv": [40, 112]},
{"origin": [2.75, -0.95, -12.5], "size": [0, 2, 2], "uv": [112, 41]}
]
},
{
"name": "finger_left_left",
"parent": "fingers_left",
"pivot": [4.5, 1.05, -7.75],
"rotation": [0, -22.5, 0],
"cubes": [
{"origin": [4.5, -0.95, -9.25], "size": [1, 2, 3], "uv": [62, 107]}
]
},
{
"name": "finger_left_left2",
"parent": "finger_left_left",
"pivot": [5, 1.05, -9.25],
"cubes": [
{"origin": [5, -0.95, -11.25], "size": [0, 2, 2], "uv": [44, 112]}
]
},
{
"name": "arm_right",
"parent": "ftorso2",
"pivot": [-3.25, 13.05, -4.75],
"cubes": [
{"origin": [-4.75, 7.25, -6.75], "size": [3, 8, 4], "uv": [29, 116]}
]
},
{
"name": "elbow_right",
"parent": "arm_right",
"pivot": [-3.25, 7.05, -4.75],
"cubes": [
{"origin": [-4.75, 0.55, -6.25], "size": [3, 7, 3], "inflate": 0.25, "uv": [0, 101]}
]
},
{
"name": "hand_right",
"parent": "elbow_right",
"pivot": [-3.25, 0.55, -5],
"cubes": [
{"origin": [-5.25, -0.95, -7.75], "size": [4, 3, 4], "inflate": 0.01, "uv": [100, 22]}
]
},
{
"name": "fingers_right",
"parent": "hand_right",
"pivot": [-3.25, 0.05, -7.5]
},
{
"name": "finger_right_left",
"parent": "fingers_right",
"pivot": [-2, 1.05, -7.75],
"rotation": [0, -22.5, 0],
"cubes": [
{"origin": [-2, -0.95, -9.25], "size": [1, 2, 3], "uv": [106, 106]}
]
},
{
"name": "finger_right_left2",
"parent": "finger_right_left",
"pivot": [-1.5, 1.05, -9.25],
"cubes": [
{"origin": [-1.5, -0.95, -11.25], "size": [0, 2, 2], "uv": [119, 93]}
]
},
{
"name": "middle_fingers_right",
"parent": "fingers_right",
"pivot": [-3.25, 0.3, -7],
"cubes": [
{"origin": [-4.25, -0.7, -8.75], "size": [2, 2, 2], "inflate": 0.25, "uv": [108, 29]}
]
},
{
"name": "middle_fingers_right2",
"parent": "middle_fingers_right",
"pivot": [-3.25, 1.53333, -9.01667],
"cubes": [
{"origin": [-4.25, -0.7, -10.25], "size": [2, 2, 1], "inflate": 0.26, "uv": [106, 66]}
]
},
{
"name": "middle_fingers_right3",
"parent": "middle_fingers_right2",
"pivot": [-3.25, 1.05, -10.5],
"cubes": [
{"origin": [-3.75, -0.95, -12.5], "size": [0, 2, 2], "uv": [28, 112]},
{"origin": [-2.75, -0.95, -12.5], "size": [0, 2, 2], "uv": [123, 93]}
]
},
{
"name": "finger_right_right",
"parent": "fingers_right",
"pivot": [-4.5, 1.05, -7.75],
"rotation": [0, 22.5, 0],
"cubes": [
{"origin": [-5.5, -0.95, -9.25], "size": [1, 2, 3], "uv": [28, 107]}
]
},
{
"name": "finger_right_right2",
"parent": "finger_right_right",
"pivot": [-5, 1.05, -9.25],
"cubes": [
{"origin": [-5, -0.95, -11.25], "size": [0, 2, 2], "uv": [32, 112]}
]
},
{
"name": "leg_left_rotate",
"parent": "body",
@@ -0,0 +1,93 @@
{
"portraitScale": 1.66,
"portraitTranslation": [-0.14, -0.74, 0],
"profileScale": 1,
"profileTranslation": [0.0, 0.24, 0.0],
"rootBone": "golurk",
"firstPersonCameraOffset": {
"seat_1": [0, 0.25, 0]
},
"thirdPersonCameraOffset": {
"seat_1": [0, 5, 8],
"seat_1_reverse": [0, 3, 8]
},
"animations": {
"faint": "q.bedrock_primary('golurk', 'faint', q.curve('one'))",
"cry": "q.bedrock_stateful('golurk', 'cry')",
"recoil": "q.bedrock_stateful('golurk', 'recoil')"
},
"poses": {
"battle-standing": {
"poseTypes": ["STAND"],
"isBattle": true,
"animations": ["q.look('head')", "q.bedrock('golurk', 'battle_idle')"]
},
"standing": {
"poseTypes": ["STAND", "NONE", "PORTRAIT", "PROFILE"],
"isBattle": false,
"animations": ["q.look('head')", "q.bedrock('golurk', 'ground_idle')"]
},
"walk": {
"poseName": "walk",
"poseTypes": ["WALK"],
"isBattle": false,
"condition": "!q.in_air && !q.is_sprinting",
"animations": [
"q.look('head')",
"q.bedrock('golurk', 'ground_walk')",
{
"condition": "q.is_ridden",
"animation": "q.bedrock('golurk', 'ride_ground_run')"
}
],
"transformTicks": 5,
"transformToTicks": 5
},
"run": {
"poseName": "run",
"poseTypes": ["WALK"],
"isBattle": false,
"condition": "!q.in_air && q.is_sprinting",
"animations": [
"q.look('head')",
"q.bedrock('golurk', 'ground_run')",
{
"condition": "q.is_ridden",
"animation": "q.bedrock('golurk', 'ride_ground_run')"
}
],
"transformTicks": 5,
"transformToTicks": 5
},
"hover": {
"poseTypes": ["HOVER"],
"isBattle": false,
"animations": [
"q.look('head')", "q.bedrock('golurk', 'air_idle')",
{
"condition": "q.is_ridden",
"animation": "q.bedrock('golurk', 'ride_air_idle')"
}
],
"transformTicks": 5,
"transformToTicks": 5
},
"fly": {
"poseTypes": ["FLY"],
"isBattle": false,
"animations": [
"q.look('head')", "q.bedrock('golurk', 'air_fly')",
{
"condition": "q.is_ridden",
"animation": "q.bedrock('golurk', 'ride_air_fly')"
}
],
"transformTicks": 5,
"transformToTicks": 5
},
"sleep": {
"poseTypes": ["SLEEP"],
"animations": ["q.bedrock('golurk', 'sleep')"]
}
}
}
@@ -1,41 +1,106 @@
{
"portraitScale": 1.78,
"portraitTranslation": [-0.95,0.48,0],
"profileScale": 0.76,
"profileTranslation": [0,0.54,0],
"animations": {
"cry": "q.bedrock_stateful('cyclizar', 'cry')"
"portraitScale": 1.5,
"portraitTranslation": [0, 0.5, 0],
"profileScale": 0.5,
"profileTranslation": [0, 0.25, -5],
"rootBone": "cyclizar",
"firstPersonCameraOffset": {
"seat_1": [0, 0.25, 0]
},
"poses": {
"standing": {
"poseTypes": [
"STAND",
"NONE",
"PORTRAIT",
"PROFILE"
],
"isBattle": false,
"animations": [
"q.look('head', 1, 1, 25, -45, 45, -45)",
"q.bedrock('cyclizar', 'grounded')"
],
"quirks": [
"q.bedrock_quirk('cyclizar', 'blink')"
]
},
"walking": {
"poseTypes": [
"WALK",
"SWIM"
],
"animations": [
"q.look('head', 1, 1, 25, -45, 45, -45)",
"q.bedrock('cyclizar', 'grounded')",
"q.quadruped_walk(0.66, 0.6, 'arm_left', 'arm_right', 'leg_left_rotate', 'leg_right_rotate')"
],
"quirks": [
"q.bedrock_quirk('cyclizar', 'blink')"
]
}
"thirdPersonCameraOffset": {
"seat_1": [0, 2.5, 5],
"seat_1_reverse": [0, 2.5, 5]
},
"animations": {
},
"poses": {
"portrait": {
"poseTypes": ["PORTRAIT", "PROFILE"],
"isBattle": false,
"animations": ["q.look('head')", "q.bedrock('cyclizar', 'ground_idle')"],
"quirks": ["q.bedrock_quirk('cyclizar', 'blink')"]
},
"ride_jump": {
"poseTypes": ["WALK","STAND", "FLOAT", "NONE"],
"isBattle": false,
"condition": "q.in_air && q.is_ridden",
"animations": ["q.look('head')", "q.bedrock('cyclizar', 'ride_jump')"],
"quirks": ["q.bedrock_quirk('cyclizar', 'blink')"],
"transformTicks": 5,
"transformToTicks": 5
},
"standing": {
"poseTypes": ["STAND", "NONE"],
"isBattle": false,
"animations": ["q.look('head')", "q.bedrock('cyclizar', 'ground_idle')"],
"condition": "!q.is_ridden",
"quirks": [
"q.bedrock_quirk('cyclizar', 'blink')"
]
},
"walking": {
"poseTypes": ["WALK", "SWIM"],
"animations": ["q.look('head')", "q.bedrock('cyclizar', 'ground_walk')"],
"condition": "!q.is_ridden",
"quirks": ["q.bedrock_quirk('cyclizar', 'blink')"]
},
"mount_idle": {
"poseTypes": ["STAND"],
"isBattle": false,
"animations": [
"q.look('head')",
"q.bedrock('cyclizar', 'ride_drift')"
],
"condition": "q.is_ridden && !q.in_air",
"quirks": ["q.bedrock_quirk('cyclizar', 'blink')"]
},
"mount_walk": {
"poseTypes": ["WALK"],
"isBattle": false,
"animations": [
"q.look('head')",
"q.bedrock('cyclizar', 'ride_ground_run')",
"q.bedrock('cyclizar', 'ground_run')"
],
"condition": "q.is_ridden && !q.is_sprinting && !q.in_air && !q.is_drifting && !q.is_powered_drifting",
"quirks": ["q.bedrock_quirk('cyclizar', 'blink')"],
"transformTicks": 5,
"transformToTicks": 5
},
"mount_drift": {
"poseTypes": ["WALK"],
"isBattle": false,
"animations": [
"q.look('head')",
"q.bedrock('cyclizar', 'ride_ground_run')",
"q.bedrock('cyclizar', 'ride_drift')"
],
"condition": "q.is_ridden && !q.is_sprinting && !q.in_air && q.is_drifting && !q.is_powered_drifting",
"quirks": ["q.bedrock_quirk('cyclizar', 'blink')"],
"transformTicks": 5,
"transformToTicks": 5
},
"mount_powered_drift": {
"poseTypes": ["WALK"],
"isBattle": false,
"animations": [
"q.look('head')",
"q.bedrock('cyclizar', 'ride_ground_run')",
"q.bedrock('cyclizar', 'ride_drift')",
"q.bedrock('cyclizar', 'ride_drift_legs')",
"q.bedrock('cyclizar', 'ride_drift_override')"
],
"condition": "q.is_ridden && !q.is_sprinting && !q.in_air && !q.is_drifting && q.is_powered_drifting",
"quirks": ["q.bedrock_quirk('cyclizar', 'blink')"],
"transformTicks": 5,
"transformToTicks": 5
},
"mount_run": {
"poseTypes": ["WALK"],
"isBattle": false,
"condition": "!q.in_air && q.is_sprinting",
"animations": ["q.look('head')", "q.bedrock('cyclizar', 'ground_run')"],
"quirks": ["q.bedrock_quirk('cyclizar', 'blink')"]
}
}
}
}
@@ -6,22 +6,54 @@
"aspects": [],
"poser": "cobblemon:golurk",
"model": "cobblemon:golurk.geo",
"texture": "cobblemon:textures/pokemon/0623_golurk/golurk.png",
"texture": {
"frames": [
"cobblemon:textures/pokemon/0623_golurk/golurk_1.png",
"cobblemon:textures/pokemon/0623_golurk/golurk_2.png",
"cobblemon:textures/pokemon/0623_golurk/golurk_3.png"
],
"fps": 10,
"loop": true
},
"layers": [
{
"name": "color",
"texture": "cobblemon:textures/pokemon/0623_golurk/golurk_emissive.png",
"texture": {
"frames": [
"cobblemon:textures/pokemon/0623_golurk/golurk_emissive_1.png",
"cobblemon:textures/pokemon/0623_golurk/golurk_emissive_2.png",
"cobblemon:textures/pokemon/0623_golurk/golurk_emissive_3.png"
],
"fps": 10,
"loop": true
},
"emissive": true
}
]
},
{
"aspects": ["shiny"],
"texture": "cobblemon:textures/pokemon/0623_golurk/golurk_shiny.png",
"texture": {
"frames": [
"cobblemon:textures/pokemon/0623_golurk/golurk_shiny_1.png",
"cobblemon:textures/pokemon/0623_golurk/golurk_shiny_2.png",
"cobblemon:textures/pokemon/0623_golurk/golurk_shiny_3.png"
],
"fps": 10,
"loop": true
},
"layers": [
{
"name": "color",
"texture": "cobblemon:textures/pokemon/0623_golurk/golurk_shiny_emissive.png",
"texture": {
"frames": [
"cobblemon:textures/pokemon/0623_golurk/golurk_shiny_emissive_1.png",
"cobblemon:textures/pokemon/0623_golurk/golurk_shiny_emissive_2.png",
"cobblemon:textures/pokemon/0623_golurk/golurk_shiny_emissive_3.png"
],
"fps": 10,
"loop": true
},
"emissive": true
}
]
@@ -1175,6 +1175,14 @@
"sounds": [
{
"name": "cobblemon:ride/loop/saucer_flight_loop",
"attenuation_distance": 64.0
}
]
},
"ride.loop.rocket": {
"sounds": [
{
"name": "cobblemon:ride/loop/rocket_flight_loop",
"attenuation_distance": 48.0
}
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 441 B

After

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 530 B

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 541 B

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 B

After

Width:  |  Height:  |  Size: 323 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 252 B

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 249 B

After

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 261 B

After

Width:  |  Height:  |  Size: 252 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 529 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 538 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

@@ -96,24 +96,10 @@
"key": "cobblemon:composite",
"transitionStrategy": "cobblemon:strategy/jump",
"defaultBehaviour": {
"key": "cobblemon:land/horse",
"gravity": "q.gravity * (0.1 + (q.passenger_count - 1) * 0.1)",
"speed": "Math.clamp(q.entity.velocity.horizontal_magnitude + 0.33, 0, 1)",
"jumpVector": [
0,
"0.3*q.jump_strength/50",
"-1 * 0.5*q.jump_strength/50"
]
"key": "cobblemon:land/horse"
},
"alternateBehaviour": {
"key": "cobblemon:air/bird",
"gravity": "q.gravity * (0.1 + (q.passenger_count - 1) * 0.1)",
"speed": "Math.clamp(q.entity.velocity.horizontal_magnitude + 0.33, 0, 1)",
"jumpVector": [
0,
"0.3*q.jump_strength/50",
0
],
"rideSounds": [
{
"soundLocation": "cobblemon:ride.loop.leather",
@@ -60,6 +60,64 @@
}
}
},
"riding": {
"stats": {
"SPEED": {
"displayName": "Speed",
"description": "Its top speed",
"ranges": {
"LAND": "50-100"
}
},
"ACCELERATION": {
"displayName": "Acceleration",
"description": "How fast it can speed up",
"ranges": {
"LAND": "50-100"
}
},
"SKILL": {
"displayName": "Handling",
"description": "How fast it can turn",
"ranges": {
"LAND": "50-100"
}
},
"JUMP": {
"displayName": "Altitude",
"description": "How high it can jump",
"ranges": {
"LAND": "50-100"
}
},
"STAMINA": {
"displayName": "Fuel/Stamina",
"description": "How long it can sprint",
"ranges": {
"LAND": "50-100"
}
}
},
"behaviour": {
"key": "cobblemon:liquid/dolphin"
},
"seats": [
{
"offset": {
"x": 0,
"y": 0.4,
"z": 0
}
},
{
"offset": {
"x": 0,
"y": 0.4,
"z": 1
}
}
]
},
"drops": {
"amount": 4,
"entries": [
@@ -208,26 +266,5 @@
"legacy:toxic"
],
"preEvolution": "seel",
"evolutions": [],
"riding": {
"stats": {
"SPEED": {
"ranges": "15-30"
},
"ACCELERATION": {
"ranges": "25-50"
},
"SKILL": {
"ranges": "15-30"
},
"JUMP": {
"ranges": "20-40"
},
"STAMINA": {
"ranges": "30-60"
}
},
"seats": [],
"behaviour": null
}
"evolutions": []
}
@@ -43,16 +43,101 @@
"catchRate": 90,
"eggCycles": 25,
"baseFriendship": 50,
"baseScale": 1.65,
"baseScale": 1.0,
"hitbox": {
"width": 1.4,
"height": 3.5,
"fixed": false
},
"riding": {
"stats": {
"SPEED": {
"displayName": "Speed",
"description": "Its top speed",
"ranges": {
"LAND": "10-50",
"AIR": "50-100"
}
},
"ACCELERATION": {
"displayName": "Acceleration",
"description": "How fast it can speed up",
"ranges": {
"LAND": "50-100",
"AIR": "50-100"
}
},
"SKILL": {
"displayName": "Handling",
"description": "How fast it can turn",
"ranges": {
"LAND": "50-100",
"AIR": "50-100"
}
},
"JUMP": {
"displayName": "Altitude",
"description": "How high it can fly",
"ranges": {
"LAND": "50-100",
"AIR": "50-100"
}
},
"STAMINA": {
"displayName": "Fuel/Stamina",
"description": "How long it can stay off the ground",
"ranges": {
"LAND": "50-100",
"AIR": "50-100"
}
}
},
"behaviour": {
"key": "cobblemon:composite",
"transitionStrategy": "cobblemon:strategy/jump",
"defaultBehaviour": {
"key": "cobblemon:land/horse",
"canJump": false
},
"alternateBehaviour": {
"key": "cobblemon:air/rocket",
"rideSounds": [
{
"soundLocation": "cobblemon:ride.loop.rocket",
"volumeExpr": "math.pow(math.min((q.driver_input() / 1.5) + (q.ride_velocity() / 1.5), 1.0),2)",
"pitchExpr": "math.max(1.0 ,0.2 + math.pow(math.min(q.driver_input() / 1.5, 1.0),2))",
"playForNonPassengers": true,
"muffleEnabled": true
}
]
}
},
"seats": [
{
"offset": {
"x": 0,
"y": 0.4,
"z": 0
},
"poseOffsets": [
{
"poseTypes": [
"FLY"
],
"offset": {
"x": 0,
"y": 0.0,
"z": 0
}
}
]
}
]
},
"behaviour": {
"moving": {
"walk": {
"walkSpeed": 0.25
"walkSpeed": 0.35
},
"swim": {
"avoidsWater": true,
@@ -47,6 +47,64 @@
"height": 1.3,
"fixed": false
},
"riding": {
"stats": {
"SPEED": {
"displayName": "Speed",
"description": "Its top speed",
"ranges": {
"LAND": "50-100"
}
},
"ACCELERATION": {
"displayName": "Acceleration",
"description": "How fast it can speed up",
"ranges": {
"LAND": "50-100"
}
},
"SKILL": {
"displayName": "Handling",
"description": "How fast it can turn",
"ranges": {
"LAND": "50-100"
}
},
"JUMP": {
"displayName": "Altitude",
"description": "How high it can jump",
"ranges": {
"LAND": "50-100"
}
},
"STAMINA": {
"displayName": "Fuel/Stamina",
"description": "How long it can sprint",
"ranges": {
"LAND": "50-100"
}
}
},
"behaviour": {
"key": "cobblemon:land/vehicle"
},
"seats": [
{
"offset": {
"x": 0,
"y": 0.4,
"z": 0
}
},
{
"offset": {
"x": 0,
"y": 0.4,
"z": 1
}
}
]
},
"drops": {
"amount": 1,
"entries": [
@@ -55,6 +55,64 @@
}
}
},
"riding": {
"stats": {
"SPEED": {
"displayName": "Speed",
"description": "Its top speed",
"ranges": {
"LAND": "50-100"
}
},
"ACCELERATION": {
"displayName": "Acceleration",
"description": "How fast it can speed up",
"ranges": {
"LAND": "50-100"
}
},
"SKILL": {
"displayName": "Handling",
"description": "How fast it can turn",
"ranges": {
"LAND": "50-100"
}
},
"JUMP": {
"displayName": "Altitude",
"description": "How high it can jump",
"ranges": {
"LAND": "50-100"
}
},
"STAMINA": {
"displayName": "Fuel/Stamina",
"description": "How long it can sprint",
"ranges": {
"LAND": "50-100"
}
}
},
"behaviour": {
"key": "cobblemon:land/minekart"
},
"seats": [
{
"offset": {
"x": 0,
"y": 0.4,
"z": 0
}
},
{
"offset": {
"x": 0,
"y": 0.4,
"z": 1
}
}
]
},
"drops": {
"amount": 4,
"entries": [