mirror of
https://gitlab.com/cable-mc/cobblemon.git
synced 2026-10-26 17:16:25 -04:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef79cf79e3 | ||
|
|
2b0128aed0 |
@@ -75,9 +75,10 @@ public abstract class LivingEntityMixin extends Entity implements OrientationCon
|
||||
public @NotNull HitResult pick(double hitDistance, float partialTicks, boolean hitFluids) {
|
||||
Entity vehicle = this.getVehicle();
|
||||
if (vehicle instanceof PokemonEntity pokemonEntity && pokemonEntity.getDelegate() instanceof PokemonClientDelegate delegate) {
|
||||
int seatIndex = pokemonEntity.getPassengers().indexOf(this);
|
||||
Seat seat = pokemonEntity.getSeats().get(seatIndex);
|
||||
MatrixWrapper locator = delegate.getLocatorStates().get(seat.getLocator());
|
||||
// Passenger order isn't seat order, so look the seat up directly
|
||||
Seat seat = pokemonEntity.getSeatForPassenger(this);
|
||||
MatrixWrapper locator = seat != null ? delegate.getLocatorStates().get(seat.getLocator()) : null;
|
||||
if (locator == null) return super.pick(hitDistance, partialTicks, hitFluids);
|
||||
|
||||
Vec3 locatorOffset = new Vec3(locator.getMatrix().getTranslation(new Vector3f()));
|
||||
|
||||
@@ -99,7 +100,8 @@ public abstract class LivingEntityMixin extends Entity implements OrientationCon
|
||||
|
||||
@Unique
|
||||
private Vec3 cobblemon$getEyePosition(float partialTicks, PokemonEntity pokemonEntity, Vec3 locatorOffset) {
|
||||
OrientationController controller = this.getOrientationController();
|
||||
// The ride owns the orientation, the passenger's own controller is never active
|
||||
OrientationController controller = ((OrientationControllable) pokemonEntity).getOrientationController();
|
||||
|
||||
float currEyeHeight = this.getEyeHeight();
|
||||
Vec3 offset = locatorOffset.add(pokemonEntity.position());
|
||||
@@ -107,7 +109,7 @@ public abstract class LivingEntityMixin extends Entity implements OrientationCon
|
||||
Quaternionf orientation = controller.getRenderOrientation(partialTicks);
|
||||
Vec3 rotatedEyeHeight = new Vec3(orientation.transform(new Vector3f(0f, currEyeHeight - (this.getBbHeight() / 2), 0f)));
|
||||
|
||||
offset.add(rotatedEyeHeight);
|
||||
offset = offset.add(rotatedEyeHeight);
|
||||
}
|
||||
|
||||
return offset;
|
||||
|
||||
@@ -50,11 +50,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@Mixin(LocalPlayer.class)
|
||||
public abstract class LocalPlayerMixin extends AbstractClientPlayer implements OrientationControllable, DoubleJump {
|
||||
|
||||
@Unique Matrix3f cobblemon$lastOrientation;
|
||||
@Unique int cobblemon$lastOrientationVehicleId = -1;
|
||||
|
||||
@Unique float cobblemon$lastRideXRot;
|
||||
@Unique float cobblemon$lastRideYRot;
|
||||
@@ -81,9 +83,18 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
|
||||
private void cobblemon$updateRotationMatrixPassenger(CallbackInfo ci) {
|
||||
if (!(this.getVehicle() instanceof OrientationControllable controllableVehicle)) return;
|
||||
var vehicleController = controllableVehicle.getOrientationController();
|
||||
if (!vehicleController.isActive() || vehicleController.getOrientation() == cobblemon$lastOrientation) return;
|
||||
cobblemon$lastOrientation = vehicleController.getOrientation() != null ? new Matrix3f(vehicleController.getOrientation()) : null;
|
||||
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateOrientationPacket(this.getVehicle().getId(), vehicleController.getOrientation()));
|
||||
if (!vehicleController.isActive()) {
|
||||
// Forget the last sent value so the next ride always gets its first orientation sent
|
||||
cobblemon$lastOrientation = null;
|
||||
return;
|
||||
}
|
||||
var orientation = vehicleController.getOrientation();
|
||||
var vehicleId = this.getVehicle().getId();
|
||||
// Compare by value, the stored one is a copy so a reference check never matches
|
||||
if (vehicleId == cobblemon$lastOrientationVehicleId && Objects.equals(orientation, cobblemon$lastOrientation)) return;
|
||||
cobblemon$lastOrientation = orientation != null ? new Matrix3f(orientation) : null;
|
||||
cobblemon$lastOrientationVehicleId = vehicleId;
|
||||
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateOrientationPacket(vehicleId, orientation));
|
||||
}
|
||||
|
||||
@Inject(method = "rideTick", at = @At("HEAD"))
|
||||
|
||||
@@ -38,6 +38,9 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
@Mixin(Entity.class)
|
||||
public abstract class EntityMixin {
|
||||
|
||||
// How far the clientsent ride eye can sit from the players
|
||||
@Unique private static final double COBBLEMON$MAX_RIDE_EYE_OFFSET = 3.0;
|
||||
|
||||
@Shadow @Nullable public abstract Entity getVehicle();
|
||||
|
||||
@Inject(method = "updateInWaterStateAndDoWaterCurrentPushing", at = @At(value = "FIELD", target = "Lnet/minecraft/world/entity/Entity;wasTouchingWater:Z", ordinal = 1), cancellable = true)
|
||||
@@ -91,6 +94,9 @@ public abstract class EntityMixin {
|
||||
if (vehicleController == null) return;
|
||||
|
||||
Vec3 customEyePos = ridePassenger.cobblemon$getRideEyePos();
|
||||
Vec3 vanillaEyePos = new Vec3(player.getX(), player.getEyeY(), player.getZ());
|
||||
double maxSqr = COBBLEMON$MAX_RIDE_EYE_OFFSET * COBBLEMON$MAX_RIDE_EYE_OFFSET;
|
||||
if (customEyePos == null || !(customEyePos.distanceToSqr(vanillaEyePos) <= maxSqr)) return;
|
||||
cir.setReturnValue(customEyePos);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.cobblemon.mod.common.api.riding.behaviour.types.air.HelicopterSetting
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.air.HoverSettings
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.air.JetSettings
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.air.RocketSettings
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.air.BoardSettings
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseSettings
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.land.MinekartSettings
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleSettings
|
||||
@@ -56,6 +57,7 @@ object CobblemonRideSettings : DataRegistry {
|
||||
var hover = HoverSettings()
|
||||
var jet = JetSettings()
|
||||
var rocket = RocketSettings()
|
||||
var board = BoardSettings()
|
||||
|
||||
var horse = HorseSettings()
|
||||
var minekart = MinekartSettings()
|
||||
@@ -75,6 +77,7 @@ object CobblemonRideSettings : DataRegistry {
|
||||
hover = hover,
|
||||
jet = jet,
|
||||
rocket = rocket,
|
||||
board = board,
|
||||
horse = horse,
|
||||
minekart = minekart,
|
||||
vehicle = vehicle,
|
||||
@@ -93,6 +96,7 @@ object CobblemonRideSettings : DataRegistry {
|
||||
hover = loadStyle(manager, "hover", HoverSettings::class.java)
|
||||
jet = loadStyle(manager, "jet", JetSettings::class.java)
|
||||
rocket = loadStyle(manager, "rocket", RocketSettings::class.java)
|
||||
board = loadStyle(manager, "board", BoardSettings::class.java)
|
||||
horse = loadStyle(manager, "horse", HorseSettings::class.java)
|
||||
minekart = loadStyle(manager, "minekart", MinekartSettings::class.java)
|
||||
vehicle = loadStyle(manager, "vehicle", VehicleSettings::class.java)
|
||||
|
||||
+3
-2
@@ -29,7 +29,8 @@ open class OrientationController(val entity: LivingEntity) {
|
||||
val UP: Vector3f = Vector3f(0.0f, 1.0f, 0.0f)
|
||||
val LEFT: Vector3f = Vector3f(-1.0f, 0.0f, 0.0f)
|
||||
private const val TICK_DT = 1f / 20f // Minecraft server tick rate
|
||||
private const val RENDER_SMOOTH_HALFLIFE = 0.1f // seconds to close ~half the gap
|
||||
// Seconds for the render orientation to close half the gap to the real one. Raise for smoother but laggier visuals
|
||||
private const val RENDER_SMOOTH_HALFLIFE = 0.15f
|
||||
}
|
||||
|
||||
open var active: Boolean = false
|
||||
@@ -101,7 +102,7 @@ open class OrientationController(val entity: LivingEntity) {
|
||||
// Use a "quat spring" to smoothly move the render orientation towards the goal orientation.
|
||||
// It takes into account the orientations angular velocities and should be much more resilient
|
||||
// to sharp changes in goal orientation or late/missed packets.
|
||||
simpleSpringDamperQuat(renderSpring, targetQuat, 0.15f, TICK_DT)
|
||||
simpleSpringDamperQuat(renderSpring, targetQuat, RENDER_SMOOTH_HALFLIFE, TICK_DT)
|
||||
|
||||
val newRenderOrientation = Matrix3f()
|
||||
renderSpring.quat.get(newRenderOrientation)
|
||||
|
||||
+3
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.api.riding.behaviour
|
||||
|
||||
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.ClimbBehaviour
|
||||
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
|
||||
@@ -35,7 +36,9 @@ object RidingBehaviours {
|
||||
register(MinekartBehaviour.KEY, MinekartBehaviour())
|
||||
register(HoverBehaviour.KEY, HoverBehaviour())
|
||||
register(RocketBehaviour.KEY, RocketBehaviour())
|
||||
register(BoardBehaviour.KEY, BoardBehaviour())
|
||||
register(SubmarineBehaviour.KEY, SubmarineBehaviour())
|
||||
register(ClimbBehaviour.KEY, ClimbBehaviour())
|
||||
register(CompositeBehaviour.KEY, CompositeBehaviour())
|
||||
}
|
||||
|
||||
|
||||
+924
@@ -0,0 +1,924 @@
|
||||
/*
|
||||
* 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.value.DoubleValue
|
||||
import com.cobblemon.mod.common.CobblemonRideSettings
|
||||
import com.cobblemon.mod.common.OrientationControllable
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.api.orientation.OrientationController
|
||||
import com.cobblemon.mod.common.api.riding.RidingStyle
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.Side
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.ridingState
|
||||
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.api.riding.util.DistanceFieldLayer
|
||||
import com.cobblemon.mod.common.api.riding.util.DistanceFieldTuning
|
||||
import com.cobblemon.mod.common.api.riding.util.PrimitiveShape
|
||||
import com.cobblemon.mod.common.api.riding.util.collectPrimitivesFromBlocks
|
||||
import com.cobblemon.mod.common.api.riding.util.estimateGradient
|
||||
import com.cobblemon.mod.common.api.riding.util.sampleField
|
||||
import com.cobblemon.mod.common.api.riding.util.visualizeDistanceField
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.readNullableExpression
|
||||
import com.cobblemon.mod.common.util.readRidingStats
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import com.cobblemon.mod.common.util.resolveDouble
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.writeNullableExpression
|
||||
import com.cobblemon.mod.common.util.writeRidingStats
|
||||
import net.minecraft.client.Minecraft
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sign
|
||||
import kotlin.math.sqrt
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.core.particles.ParticleTypes
|
||||
import net.minecraft.network.FriendlyByteBuf
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.util.SmoothDouble
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.phys.AABB
|
||||
import net.minecraft.world.phys.Vec2
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import org.joml.Matrix3f
|
||||
import org.joml.Quaternionf
|
||||
import org.joml.Vector3f
|
||||
|
||||
class BoardBehaviour : RidingBehaviour<BoardSettings, BoardState> {
|
||||
companion object {
|
||||
val KEY = cobblemonResource("air/board")
|
||||
}
|
||||
|
||||
override val key = KEY
|
||||
val globalBoard: BoardSettings
|
||||
get() = CobblemonRideSettings.board
|
||||
|
||||
override fun getRidingStyle(settings: BoardSettings, state: BoardState): RidingStyle {
|
||||
return RidingStyle.LAND
|
||||
}
|
||||
|
||||
val poseProvider = PoseProvider<BoardSettings, BoardState>(PoseType.HOVER)
|
||||
.with(PoseOption(PoseType.FLY) { _, state, vehicle ->
|
||||
return@PoseOption state.walking.get()
|
||||
})
|
||||
|
||||
override fun isActive(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
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: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity
|
||||
): PoseType {
|
||||
return this.poseProvider.select(settings, state, vehicle)
|
||||
}
|
||||
|
||||
override fun speed(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
): Float {
|
||||
return state.rideVelocity.get().length().toFloat()
|
||||
}
|
||||
|
||||
override fun tick(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
) {
|
||||
if (vehicle.level().isClientSide) {
|
||||
handleSprinting(state, driver)
|
||||
inAirCheck(state, vehicle)
|
||||
tickStamina(settings, state, vehicle)
|
||||
state.walking.set(state.rideVelocity.get().horizontalDistance() > 0.1 || driver.zza != 0.0f)
|
||||
//surfDistCheck(state, vehicle)
|
||||
}
|
||||
|
||||
//TODO: should be a config somewhere
|
||||
val debugVisualizeField = true;
|
||||
|
||||
if (false && debugVisualizeField && !vehicle.level().isClientSide && vehicle.tickCount % 5 == 0) {
|
||||
visualizeDistanceField(
|
||||
referenceEntity = vehicle,
|
||||
center = vehicle.position().add(0.0, (vehicle.bbHeight * 0.5f).toDouble(), 0.0),
|
||||
layers = listOf(
|
||||
DistanceFieldLayer(isoValue = 2.5f, color = Vector3f(1f, 0.2f, 0.2f))
|
||||
),
|
||||
shape = PrimitiveShape.ELLIPSOID,
|
||||
horizontalRadius = 4.0,
|
||||
verticalRange = 4.0,
|
||||
sampleSpacing = 0.3,
|
||||
marchStep = 0.15,
|
||||
tuning = DistanceFieldTuning(smoothingRange = 0.25f)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check how far the vehicle is from the "surface"
|
||||
*/
|
||||
fun surfDistCheck(
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
) {
|
||||
val center = vehicle.position()
|
||||
val horizontalRadius = 4.0;
|
||||
val verticalRange = 4.0;
|
||||
val level = vehicle.level()
|
||||
|
||||
//if (level !is ServerLevel) return
|
||||
|
||||
// Take a cubic area around the vehicle and gather SDF primitives from it.
|
||||
val searchBox = AABB(
|
||||
center.x - horizontalRadius, center.y - verticalRange, center.z - horizontalRadius,
|
||||
center.x + horizontalRadius, center.y + verticalRange, center.z + horizontalRadius
|
||||
)
|
||||
val primitives = collectPrimitivesFromBlocks(level,
|
||||
searchBox,
|
||||
PrimitiveShape.ELLIPSOID,
|
||||
includeFluids = true
|
||||
)
|
||||
val sp = center.toVector3f() // Sample Point
|
||||
val tuning = DistanceFieldTuning(smoothingRange = 0.25f)
|
||||
val sd = sampleField(primitives, sp.x, sp.y, sp.z, tuning)
|
||||
state.surfDist.set(sd)
|
||||
}
|
||||
|
||||
fun inAirCheck(
|
||||
state: BoardState,
|
||||
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(true)
|
||||
}
|
||||
|
||||
fun handleSprinting(
|
||||
state: BoardState,
|
||||
driver: Player
|
||||
) {
|
||||
val sprintTimerTickLength = 7 // Emulate vanillas tick timer length
|
||||
var hasDoubleTapped = false
|
||||
|
||||
// If you are not sprinting and you just pressed down and theres no
|
||||
if (!state.forwardHeldLastTick.get() && Minecraft.getInstance().options.keyUp.isDown() && !state.sprinting.get() && state.sprintTickTimer.get() == 0) {
|
||||
state.sprintTickTimer.set(sprintTimerTickLength)
|
||||
} else if (!state.forwardHeldLastTick.get() && Minecraft.getInstance().options.keyUp.isDown() && state.sprintTickTimer.get() != 0) { // If has double tapped
|
||||
hasDoubleTapped = true
|
||||
}else if(!state.sprinting.get() && state.sprintTickTimer.get() > 0) {
|
||||
state.sprintTickTimer.set(state.sprintTickTimer.get() - 1)
|
||||
} else {
|
||||
state.sprintTickTimer.set(0)
|
||||
}
|
||||
|
||||
val tryingToSprint = (Minecraft.getInstance().options.keySprint.isDown() && Minecraft.getInstance().options.keyUp.isDown()) || hasDoubleTapped
|
||||
|
||||
if (state.stamina.get() <= 0.0f || !Minecraft.getInstance().options.keyUp.isDown()) {
|
||||
// If stamina runs out or the player is not holding forward then stop sprinting
|
||||
state.sprinting.set(false)
|
||||
if (state.stamina.get() <= 0.0f) {
|
||||
state.sprintToggleable.set(false)
|
||||
}
|
||||
} else if (!state.sprinting.get() && !state.sprintToggleable.get() && state.stamina.get() > 0.33f) {
|
||||
// If you are not sprinting and sprint is not toggleable and you're not trying to sprint and stamina is above 0.3 then enable the toggle
|
||||
state.sprintToggleable.set(true)
|
||||
} else if (tryingToSprint && state.sprintToggleable.get()) {
|
||||
// If you are trying to sprint and the toggle allows it then start sprinting and disable toggle
|
||||
state.sprinting.set(true)
|
||||
}
|
||||
|
||||
// Take not of whether forward was held last tick
|
||||
state.forwardHeldLastTick.set(Minecraft.getInstance().options.keyUp.isDown())
|
||||
}
|
||||
|
||||
fun tickStamina(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
) {
|
||||
val stam = state.stamina.get()
|
||||
|
||||
if (vehicle.runtime.resolveBoolean(settings.infiniteStamina ?: globalBoard.infiniteStamina!!)) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalBoard.staminaExpr!!)).toFloat() / 20.0f
|
||||
|
||||
val newStam = if (state.sprinting.get()) {
|
||||
max(0.0f,stam - stamDrainRate)
|
||||
} else {
|
||||
min(1.0f,stam + stamDrainRate * 4)
|
||||
}
|
||||
|
||||
state.stamina.set(newStam.coerceIn(0F, 1F))
|
||||
}
|
||||
|
||||
override fun updatePassengerRotation(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: LivingEntity
|
||||
) { return }
|
||||
|
||||
override fun clampPassengerRotation(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: LivingEntity
|
||||
) { }
|
||||
|
||||
|
||||
override fun rotation(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: LivingEntity
|
||||
): Vec2 {
|
||||
val turnAmount = calcRotAmount(settings, state, vehicle, driver)
|
||||
return Vec2(vehicle.xRot, vehicle.yRot + turnAmount )
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculates the rotation amount given the difference between
|
||||
* the current player y rot and entity y rot. This gives the
|
||||
* affect of influencing a rides rotation through the mouse
|
||||
* without it being instant.
|
||||
*/
|
||||
fun calcRotAmount(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: LivingEntity
|
||||
): Float {
|
||||
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBoard.speedExpr!!)
|
||||
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalBoard.handlingExpr!!)
|
||||
val walkHandlingBoost = 5
|
||||
val maxYawDiff = vehicle.runtime.resolveFloat(settings.lookYawLimit ?: globalBoard.lookYawLimit!!)
|
||||
|
||||
// Normalize the current rotation diff
|
||||
val rotDiff = Mth.wrapDegrees(driver.yRot - vehicle.yRot).coerceIn(-maxYawDiff,maxYawDiff)
|
||||
val rotDiffNorm = rotDiff / maxYawDiff//(rotDiff / maxYawDiff).coerceIn(-0.2f, 0.2f)
|
||||
|
||||
// Take the square root so that the ride levels out quicker when at lower differences between entity
|
||||
// y and driver y
|
||||
// This influences the speed of the turn based on how far in one direction you're looking
|
||||
val minRotMod = 0.4f // Min speed that the rotation modulation converges towards
|
||||
val rotDiffMod = ((sqrt(abs(rotDiffNorm)) * (1.0f - minRotMod)) + minRotMod) * rotDiffNorm.sign
|
||||
|
||||
// Turn rate should be quick when walking and slower when sprinting.
|
||||
// Smoothly move between these based on speed
|
||||
val walkSpeed = getWalkSpeed(vehicle)
|
||||
val w = max(walkSpeed, vehicle.deltaMovement.horizontalDistance())
|
||||
val invRelSpeed = (RidingBehaviour.scaleToRange(w, walkSpeed, topSpeed ) - 1.0f) * -1.0f
|
||||
val turnRate = ((handling.toFloat() / 20.0f) * max(walkHandlingBoost * sqrt(abs(invRelSpeed)), 1.0)).toFloat()
|
||||
|
||||
// Ensure you only ever rotate as much difference as there is between the angles.
|
||||
val turnSpeed = turnRate * rotDiffMod
|
||||
val rotAmount = turnSpeed.coerceIn(-abs(rotDiff), abs(rotDiff))
|
||||
|
||||
return rotAmount
|
||||
}
|
||||
|
||||
private fun spawnGradientDebugParticles(vehicle: PokemonEntity, normal: Vec3) {
|
||||
val level = vehicle.level()
|
||||
val headPos = vehicle.position().add(0.0, vehicle.bbHeight.toDouble(), 0.0)
|
||||
|
||||
val steps = 20
|
||||
val length = 2.0
|
||||
for (i in 0..steps) {
|
||||
val t = i.toDouble() / steps
|
||||
val point = headPos.add(normal.scale(length * t))
|
||||
level.addParticle(ParticleTypes.END_ROD, point.x, point.y, point.z, 0.0, 0.0, 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun velocity(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
): Vec3 {
|
||||
state.rideVelocity.set(calculateRideSpaceVel(settings, state, vehicle, driver))
|
||||
return state.rideVelocity.get()
|
||||
}
|
||||
|
||||
private fun calculateRideSpaceVel(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
): Vec3 {
|
||||
val canSprint = vehicle.runtime.resolveBoolean(settings.canSprint ?: globalBoard.canSprint!!)
|
||||
val canJump = vehicle.runtime.resolveBoolean(settings.canJump ?: globalBoard.canJump!!)
|
||||
val jumpForce = vehicle.runtime.resolveDouble(settings.jumpExpr ?: globalBoard.jumpExpr!!) * 0.75
|
||||
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBoard.speedExpr!!)
|
||||
val walkSpeed = getWalkSpeed(vehicle)
|
||||
val topSpeed = if(canSprint && state.sprinting.get()) rideTopSpeed else walkSpeed
|
||||
val accelTime = if(state.sprinting.get()) (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalBoard.accelerationExpr!!) * 20.0) else 0.5 * 20.0
|
||||
val accel = topSpeed / accelTime
|
||||
var activeInput = false
|
||||
|
||||
/******************************************************
|
||||
* Gather the previous velocity and check for horizontal
|
||||
* collisions
|
||||
*****************************************************/
|
||||
var newVelocity = state.rideVelocity.get() //.normalize().scale(vehicle.deltaMovement.horizontalDistance())
|
||||
|
||||
/******************************************************
|
||||
* Gather the previous velocity and check for horizontal
|
||||
* collisions
|
||||
*****************************************************/
|
||||
if (vehicle.horizontalCollision) {
|
||||
var horizontalVec = Vec3(newVelocity.x, 0.0, newVelocity.z)
|
||||
horizontalVec = horizontalVec.normalize().scale(min(horizontalVec.length(), 0.3))
|
||||
newVelocity = Vec3(horizontalVec.x, newVelocity.y, horizontalVec.z)
|
||||
}
|
||||
|
||||
/******************************************************
|
||||
* Speed up and slow down based on input
|
||||
*****************************************************/
|
||||
if (driver.zza != 0.0f) {
|
||||
|
||||
// If on a tight turn then do not speed up past half of top speed in order to turn quicker
|
||||
// Also determine how fast to be slowing down based on how far turned you are
|
||||
val lookYawLimit = vehicle.runtime.resolveFloat(settings.lookYawLimit ?: globalBoard.lookYawLimit!!)
|
||||
val percOfMaxTurnSpeed = abs(Mth.wrapDegrees(driver.yRot - vehicle.yRot) / lookYawLimit) * 100.0f
|
||||
val turnPercThresh = 0.0f
|
||||
val s = min(((percOfMaxTurnSpeed - turnPercThresh) / (100.0f - turnPercThresh)).pow(1),1.0f)
|
||||
val effectiveTopSpeed = if (percOfMaxTurnSpeed > turnPercThresh) topSpeed / max(2.0f*s,1.0f) else topSpeed
|
||||
val turningSlowDown = s * 0.1
|
||||
|
||||
//make sure it can't exceed top speed
|
||||
val forwardInput = when {
|
||||
driver.zza > 0 && newVelocity.z > effectiveTopSpeed -> 0.0
|
||||
driver.zza < 0 && newVelocity.z < (-effectiveTopSpeed / 3.0) -> 0.0
|
||||
else -> driver.zza.sign
|
||||
}
|
||||
|
||||
// Add extra friction if trying to slow down to turn faster
|
||||
if (newVelocity.z > effectiveTopSpeed) {
|
||||
newVelocity = newVelocity.subtract(0.0, 0.0, min(turningSlowDown * newVelocity.z.sign, newVelocity.z))
|
||||
}
|
||||
|
||||
newVelocity = Vec3(
|
||||
newVelocity.x,
|
||||
newVelocity.y,
|
||||
(newVelocity.z + (accel * forwardInput.toDouble())))
|
||||
|
||||
activeInput = true
|
||||
}
|
||||
|
||||
/******************************************************
|
||||
* Gravity logic
|
||||
*****************************************************/
|
||||
// if(state.surfDist.get() > 1.6f) {
|
||||
// val gravity = (9.8 / ( 20.0)) * 0.2 * 0.6
|
||||
// val terminalVel = 2.0
|
||||
// newVelocity = Vec3(newVelocity.x, max(newVelocity.y - gravity, -terminalVel), newVelocity.z)
|
||||
// } else if(state.surfDist.get() > 1.4f && state.surfDist.get() <= 1.6f) {
|
||||
// newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
|
||||
// } else if(state.surfDist.get() <= 1.4f) {
|
||||
// val gravity = -(9.8 / ( 20.0)) * 0.2 * 0.6
|
||||
// val terminalVel = -2.0
|
||||
// newVelocity = Vec3(newVelocity.x, min(newVelocity.y - gravity, -terminalVel), newVelocity.z)
|
||||
// }
|
||||
|
||||
/******************************************************
|
||||
* Hover logic - non-predictive: correct against current position
|
||||
*****************************************************/
|
||||
val correctionGain = 0.5
|
||||
val maxCorrectionSpeed = 0.3
|
||||
val center = vehicle.position()
|
||||
val horizontalRadius = 4.0;
|
||||
val verticalRange = 4.0;
|
||||
val level = vehicle.level()
|
||||
|
||||
// Take a cubic area around the vehicle and gather SDF primitives from it.
|
||||
val searchBox = AABB(
|
||||
center.x - horizontalRadius, center.y - verticalRange, center.z - horizontalRadius,
|
||||
center.x + horizontalRadius, center.y + vehicle.bbHeight, center.z + horizontalRadius
|
||||
)
|
||||
val primitives = collectPrimitivesFromBlocks(level,
|
||||
searchBox,
|
||||
PrimitiveShape.ELLIPSOID,
|
||||
includeFluids = true
|
||||
)
|
||||
val tuning = DistanceFieldTuning(smoothingRange = 0.25f)
|
||||
|
||||
val currentPos = vehicle.position()
|
||||
val currentDist = sampleField(primitives, currentPos.x.toFloat(), currentPos.y.toFloat(), currentPos.z.toFloat(), tuning)
|
||||
val normal = estimateGradient(primitives, currentPos, tuning)
|
||||
state.surfaceNormal.set(normal)
|
||||
//spawnGradientDebugParticles(vehicle, normal) // LETS SEE IT!
|
||||
|
||||
// Only correct when under the hover surface.
|
||||
// Otherwise allow gravity to do its thing
|
||||
// if (currentDist < targetHoverDist) {
|
||||
// val error = currentDist - targetHoverDist
|
||||
// val normalSpeed = newVelocity.dot(normal)
|
||||
// val tangentialVelocity = newVelocity.subtract(normal.scale(normalSpeed))
|
||||
//
|
||||
// val correctionSpeed = (-error * correctionGain).coerceIn(-maxCorrectionSpeed, maxCorrectionSpeed)
|
||||
// newVelocity = tangentialVelocity.add(normal.scale(correctionSpeed))
|
||||
// } else {
|
||||
// val hoverCoolnessLowGravFactor = 0.5
|
||||
// val gravity = (9.8 / ( 20.0)) * 0.2 * 0.6 * hoverCoolnessLowGravFactor
|
||||
// val terminalVel = 2.0 * hoverCoolnessLowGravFactor
|
||||
// newVelocity = Vec3(newVelocity.x, max(newVelocity.y - gravity, -terminalVel), newVelocity.z)
|
||||
// }
|
||||
|
||||
/******************************************************
|
||||
* Hover logic
|
||||
*****************************************************/
|
||||
val hoverActivationDist = 3.0f
|
||||
val targetHoverDist = 1.0f
|
||||
val hoverCoolnessLowGravFactor = 0.70
|
||||
val gravity = (9.8 / 20.0) * 0.2 * 0.6 * hoverCoolnessLowGravFactor
|
||||
val terminalVel = 2.0 * hoverCoolnessLowGravFactor
|
||||
// val repelStrength = 0.05 // tested and was alright
|
||||
// val hoverDamping = 0.15
|
||||
val repelStrength = 0.1
|
||||
val hoverDamping = 0.2
|
||||
|
||||
val surfDist = currentDist
|
||||
|
||||
if (surfDist > hoverActivationDist) {
|
||||
// Free fall - no surface influence yet, same as before.
|
||||
newVelocity = Vec3(newVelocity.x, max(newVelocity.y - gravity, -terminalVel), newVelocity.z)
|
||||
} else {
|
||||
// Getting close - push back toward targetHoverDist, harder the nearer we are.
|
||||
val closeness = ((hoverActivationDist - surfDist) / (hoverActivationDist - targetHoverDist)).coerceIn(0f, 1.5f)
|
||||
val repelForce = closeness * repelStrength
|
||||
val dampingForce = -newVelocity.y * hoverDamping // resists overshoot/bounce
|
||||
|
||||
newVelocity = Vec3(newVelocity.x, newVelocity.y + repelForce + dampingForce - gravity * 0.3, newVelocity.z)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/******************************************************
|
||||
* Ground Friction
|
||||
// *****************************************************/
|
||||
// if( (newVelocity.horizontalDistance() > 0 && vehicle.onGround() && !activeInput) || newVelocity.horizontalDistance() > topSpeed) {
|
||||
// newVelocity = newVelocity.subtract(0.0, 0.0, min(0.03 * newVelocity.z.sign, newVelocity.z))
|
||||
// }
|
||||
|
||||
/******************************************************
|
||||
* Jump Logic
|
||||
*****************************************************/
|
||||
if (state.jumpTicks.get() > 0 || (state.jumpTicks.get() >= 0 && driver.jumping && vehicle.onGround() && canJump && driver.deltaMovement.y <= 0.1)) {
|
||||
// Spread out jumpforce so that variable height jumps are possible
|
||||
val jumpInputTicks = 6
|
||||
if (driver.jumping && (state.jumpTicks.get() >= 0 && state.jumpTicks.get() < jumpInputTicks)) {
|
||||
val appliedJumpForce = ((jumpForce*1.5) / jumpInputTicks) //* (1 - state.jumpTicks.get() / jumpInputTicks)
|
||||
newVelocity = Vec3(newVelocity.x, newVelocity.y + appliedJumpForce, newVelocity.z)
|
||||
state.jumpTicks.set(state.jumpTicks.get() + 1)
|
||||
} else {
|
||||
// Set delay before next jump is possible after hitting ground
|
||||
val tickJumpDelay = 3
|
||||
state.jumpTicks.set(-tickJumpDelay)
|
||||
}
|
||||
} else if (vehicle.onGround() && state.jumpTicks.get() < 0) {
|
||||
// Tick off the delay once reaching the ground
|
||||
state.jumpTicks.set(state.jumpTicks.get() + 1)
|
||||
}
|
||||
|
||||
// Zero out lateral velocity possibly picked up from a controller transition
|
||||
newVelocity = Vec3(0.0, newVelocity.y, newVelocity.z)
|
||||
|
||||
|
||||
return newVelocity
|
||||
}
|
||||
|
||||
private fun getWalkSpeed( vehicle: PokemonEntity ): Double {
|
||||
val walkspeed = vehicle.runtime.resolveDouble(vehicle.behaviour.moving.walk.walkSpeed)
|
||||
val movementSpeed = vehicle.attributes.getValue(Attributes.MOVEMENT_SPEED)
|
||||
val speedModifier = 1.2 * 0.35
|
||||
return walkspeed * movementSpeed * speedModifier
|
||||
|
||||
}
|
||||
|
||||
override fun angRollVel(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
if (vehicle !is OrientationControllable) return Vec3.ZERO
|
||||
val controller = (vehicle as OrientationControllable).orientationController
|
||||
|
||||
// turning
|
||||
val turnMaxRate = 5.5
|
||||
val turnAccel = 1.0
|
||||
val turnFriction = 0.75
|
||||
|
||||
val targetYawRate = -driver.xxa.toDouble() * turnMaxRate
|
||||
val currentYawRate = state.yawAngularVelocity.get()
|
||||
val newYawRate = if (abs(targetYawRate) > 0.001) {
|
||||
moveToward(currentYawRate, targetYawRate, turnAccel * deltaTime * 20.0)
|
||||
} else {
|
||||
moveToward(currentYawRate, 0.0, turnFriction * deltaTime * 20.0)
|
||||
}
|
||||
state.yawAngularVelocity.set(newYawRate)
|
||||
controller.applyGlobalYaw((newYawRate * deltaTime * 20.0).toFloat())
|
||||
|
||||
// up-vector alignment framerate-independent
|
||||
val upAlignmentRatePerTick = 0.1 // fraction of the remaining gap closed per tick
|
||||
val frameBlend = 1.0 - (1.0 - upAlignmentRatePerTick).pow(deltaTime * 20.0)
|
||||
alignUpToSurfaceNormal(controller, state.surfaceNormal.get(), frameBlend.toFloat())
|
||||
|
||||
// lean into turns (cosmetic local roll)
|
||||
val maxLeanDeg = 35.0
|
||||
val leanPerYawRate = 4.0 * 3.0
|
||||
val leanRatePerTick = 0.25 * 1.5
|
||||
|
||||
val desiredLean = (newYawRate * leanPerYawRate).coerceIn(-maxLeanDeg, maxLeanDeg)
|
||||
val leanFrameBlend = 1.0 - (1.0 - leanRatePerTick).pow(deltaTime * 20.0)
|
||||
val leanError = controller.roll.angleDifference(desiredLean.toFloat())
|
||||
controller.rotateRoll((leanError * leanFrameBlend).toFloat())
|
||||
|
||||
//yaw, pitch, roll
|
||||
return Vec3.ZERO
|
||||
}
|
||||
|
||||
private fun moveToward(current: Double, target: Double, maxDelta: Double): Double {
|
||||
val diff = target - current
|
||||
return if (abs(diff) <= maxDelta) target else current + maxDelta * diff.sign
|
||||
}
|
||||
|
||||
private fun alignUpToSurfaceNormal(controller: OrientationController, targetUp: Vec3, alignmentBlend: Float) {
|
||||
if (targetUp.lengthSqr() < 1e-6) return
|
||||
val targetUpVec = Vector3f(targetUp.x.toFloat(), targetUp.y.toFloat(), targetUp.z.toFloat()).normalize()
|
||||
|
||||
controller.updateOrientation { current ->
|
||||
val currentUp = current.transform(Vector3f(OrientationController.UP), Vector3f())
|
||||
if (currentUp.lengthSquared() < 1e-6f) return@updateOrientation current
|
||||
|
||||
// Full rotation that would exactly align currentUp to targetUp...
|
||||
val fullCorrection = Quaternionf().rotationTo(currentUp, targetUpVec)
|
||||
// ...but only take a partial step toward it, so alignment is smooth, not instant.
|
||||
val steppedCorrection = Quaternionf().slerp(fullCorrection, alignmentBlend)
|
||||
|
||||
// Pre-multiply (world-space correction), same pattern applyGlobalYaw uses.
|
||||
Matrix3f().set(steppedCorrection).mul(current, Matrix3f())
|
||||
}
|
||||
}
|
||||
|
||||
override fun rotationOnMouseXY(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
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: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun setRideBar(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
): Float {
|
||||
return (state.stamina.get() / 1.0f)
|
||||
}
|
||||
|
||||
override fun jumpForce(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
jumpStrength: Int
|
||||
): Vec3 {
|
||||
return Vec3.ZERO
|
||||
}
|
||||
|
||||
override fun gravity(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
regularGravity: Double
|
||||
): Double {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
override fun rideFovMultiplier(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
): Float {
|
||||
return if (state.sprinting.get()) 1.15f else 1.0f
|
||||
}
|
||||
|
||||
override fun useAngVelSmoothing(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun inertia(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity
|
||||
): Double {
|
||||
return 1.0
|
||||
}
|
||||
|
||||
override fun shouldRoll(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun turnOffOnGround(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun dismountOnShift(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun shouldRotatePokemonHead(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun shouldRotateRiderHead(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun maxUpStep(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity
|
||||
): Float? {
|
||||
return if (state.inAir.get()) 0.0f else 1.2f
|
||||
}
|
||||
|
||||
override fun getRideSounds(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity
|
||||
): RideSoundSettingsList {
|
||||
return settings.rideSounds
|
||||
}
|
||||
|
||||
override fun createDefaultState(settings: BoardSettings) = BoardState()
|
||||
|
||||
override fun asMoLangValue(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity
|
||||
): ObjectValue<RidingBehaviour<BoardSettings, BoardState>> {
|
||||
val canJump = vehicle.runtime.resolveBoolean(settings.canJump ?: globalBoard.canJump!!)
|
||||
val value = super.asMoLangValue(settings, state, vehicle)
|
||||
value.functions.put("sprinting") { DoubleValue(state.sprinting.get()) }
|
||||
value.functions.put("walking") { DoubleValue(state.walking.get()) }
|
||||
value.functions.put("in_air") { DoubleValue(state.inAir.get() || (state.rideVelocity.get().y >= 0.0 && (vehicle.controllingPassenger as? Player)?.jumping == true && canJump) || (state.rideVelocity.get().y > 0.0)) }
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
class BoardSettings : RidingBehaviourSettings {
|
||||
override val key = BoardBehaviour.KEY
|
||||
override val stats = mutableMapOf<RidingStat, IntRange>()
|
||||
|
||||
var infiniteStamina: Expression? = null
|
||||
private set
|
||||
var canJump: Expression? = null
|
||||
private set
|
||||
|
||||
var canSprint: Expression? = null
|
||||
private set
|
||||
|
||||
var lookYawLimit: Expression? = null
|
||||
private set
|
||||
|
||||
var speedExpr: Expression? = null
|
||||
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? = null
|
||||
private set
|
||||
|
||||
// Between 30 seconds and 10 seconds at the lowest when at full speed.
|
||||
var staminaExpr: Expression? = null
|
||||
private set
|
||||
|
||||
//Between a one block jump and a six block jump
|
||||
var jumpExpr: Expression? = null
|
||||
private set
|
||||
|
||||
var handlingExpr: Expression? = null
|
||||
private set
|
||||
|
||||
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
|
||||
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeRidingStats(stats)
|
||||
rideSounds.encode(buffer)
|
||||
buffer.writeNullableExpression(infiniteStamina)
|
||||
buffer.writeNullableExpression(canJump)
|
||||
buffer.writeNullableExpression(canSprint)
|
||||
buffer.writeNullableExpression(lookYawLimit)
|
||||
buffer.writeNullableExpression(speedExpr)
|
||||
buffer.writeNullableExpression(accelerationExpr)
|
||||
buffer.writeNullableExpression(staminaExpr)
|
||||
buffer.writeNullableExpression(jumpExpr)
|
||||
buffer.writeNullableExpression(handlingExpr)
|
||||
buffer.writeNullableExpression(canJump)
|
||||
buffer.writeNullableExpression(canSprint)
|
||||
}
|
||||
|
||||
override fun decode(buffer: RegistryFriendlyByteBuf) {
|
||||
stats.putAll(buffer.readRidingStats())
|
||||
|
||||
|
||||
rideSounds = RideSoundSettingsList.decode(buffer)
|
||||
infiniteStamina = buffer.readNullableExpression()
|
||||
canJump = buffer.readNullableExpression()
|
||||
canSprint = buffer.readNullableExpression()
|
||||
lookYawLimit = buffer.readNullableExpression()
|
||||
speedExpr = buffer.readNullableExpression()
|
||||
accelerationExpr = buffer.readNullableExpression()
|
||||
staminaExpr = buffer.readNullableExpression()
|
||||
jumpExpr = buffer.readNullableExpression()
|
||||
handlingExpr = buffer.readNullableExpression()
|
||||
canJump = buffer.readNullableExpression()
|
||||
canSprint = buffer.readNullableExpression()
|
||||
}
|
||||
}
|
||||
|
||||
class BoardState : RidingBehaviourState() {
|
||||
var sprinting = ridingState(false, Side.CLIENT)
|
||||
var walking = ridingState(false, Side.BOTH)
|
||||
var sprintToggleable = ridingState(false, Side.CLIENT)
|
||||
var inAir = ridingState(false, Side.CLIENT)
|
||||
var jumpTicks = ridingState(0, Side.CLIENT)
|
||||
var sprintTickTimer = ridingState(0, Side.CLIENT)
|
||||
var forwardHeldLastTick = ridingState(false, Side.CLIENT)
|
||||
var surfDist = ridingState(Float.POSITIVE_INFINITY, Side.BOTH)
|
||||
var surfaceNormal = ridingState(Vec3(0.0, 1.0, 0.0), Side.CLIENT)
|
||||
var yawAngularVelocity = ridingState(0.0, Side.CLIENT)
|
||||
|
||||
override fun encode(buffer: FriendlyByteBuf) {
|
||||
super.encode(buffer)
|
||||
buffer.writeBoolean(sprinting.get())
|
||||
buffer.writeBoolean(walking.get())
|
||||
buffer.writeBoolean(sprintToggleable.get())
|
||||
buffer.writeBoolean(inAir.get())
|
||||
buffer.writeInt(jumpTicks.get())
|
||||
buffer.writeInt(sprintTickTimer.get())
|
||||
buffer.writeBoolean(forwardHeldLastTick.get())
|
||||
buffer.writeFloat(surfDist.get())
|
||||
buffer.writeVec3(surfaceNormal.get())
|
||||
buffer.writeDouble(yawAngularVelocity.get())
|
||||
}
|
||||
|
||||
override fun decode(buffer: FriendlyByteBuf) {
|
||||
super.decode(buffer)
|
||||
sprinting.set(buffer.readBoolean(), forced = true)
|
||||
walking.set(buffer.readBoolean(), forced = true)
|
||||
sprintToggleable.set(buffer.readBoolean(), forced = true)
|
||||
inAir.set(buffer.readBoolean(), forced = true)
|
||||
jumpTicks.set(buffer.readInt(), forced = true)
|
||||
sprintTickTimer.set(buffer.readInt(), forced = true)
|
||||
forwardHeldLastTick.set(buffer.readBoolean(), forced = true)
|
||||
surfDist.set(buffer.readFloat(), forced = true)
|
||||
surfaceNormal.set(buffer.readVec3(), forced = true)
|
||||
yawAngularVelocity.set(buffer.readDouble(), forced = true)
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
super.reset()
|
||||
sprinting.set(false, forced = true)
|
||||
walking.set(false, forced = true)
|
||||
sprintToggleable.set(false, forced = true)
|
||||
inAir.set(false, forced = true)
|
||||
jumpTicks.set(0, forced = true)
|
||||
sprintTickTimer.set(0, forced = true)
|
||||
forwardHeldLastTick.set(false, forced = true)
|
||||
surfDist.set(Float.POSITIVE_INFINITY, forced = true)
|
||||
surfaceNormal.set(Vec3(0.0, 1.0, 0.0), forced = true)
|
||||
yawAngularVelocity.set(0.0, forced = true)
|
||||
}
|
||||
|
||||
override fun copy() = BoardState().also {
|
||||
it.rideVelocity.set(this.rideVelocity.get(), forced = true)
|
||||
it.stamina.set(this.stamina.get(), forced = true)
|
||||
it.sprinting.set(this.sprinting.get(), forced = true)
|
||||
it.walking.set(this.sprinting.get(), forced = true)
|
||||
it.sprintToggleable.set(this.sprintToggleable.get(), forced = true)
|
||||
it.inAir.set(this.inAir.get(), forced = true)
|
||||
it.jumpTicks.set(this.jumpTicks.get(), forced = true)
|
||||
it.sprintTickTimer.set(this.sprintTickTimer.get(), forced = true)
|
||||
it.forwardHeldLastTick.set(this.forwardHeldLastTick.get(), forced = true)
|
||||
it.surfDist.set(this.surfDist.get(), forced = true)
|
||||
it.surfaceNormal.set(this.surfaceNormal.get(), forced = true)
|
||||
it.yawAngularVelocity.set(this.yawAngularVelocity.get(), forced = true)
|
||||
}
|
||||
|
||||
override fun shouldSync(previous: RidingBehaviourState): Boolean {
|
||||
if (previous !is BoardState) return false
|
||||
if (previous.sprinting.get() != sprinting.get()) return true
|
||||
if (previous.walking.get() != walking.get()) return true
|
||||
if (previous.sprintToggleable.get() != sprintToggleable.get()) return true
|
||||
if (previous.inAir.get() != inAir.get()) return true
|
||||
if (previous.surfDist.get() != surfDist.get()) return true
|
||||
return super.shouldSync(previous)
|
||||
}
|
||||
}
|
||||
+783
@@ -0,0 +1,783 @@
|
||||
/*
|
||||
* 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.cobblemon.mod.common.CobblemonRideSettings
|
||||
import com.cobblemon.mod.common.OrientationControllable
|
||||
import com.cobblemon.mod.common.api.orientation.OrientationController
|
||||
import com.cobblemon.mod.common.api.orientation.OrientationController.Companion.UP
|
||||
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.util.DistanceFieldLayer
|
||||
import com.cobblemon.mod.common.api.riding.util.FieldPrimitive
|
||||
import com.cobblemon.mod.common.api.riding.util.PrimitiveShape
|
||||
import com.cobblemon.mod.common.api.riding.util.collectPrimitivesFromBlocks
|
||||
import com.cobblemon.mod.common.api.riding.util.estimateGradient
|
||||
import com.cobblemon.mod.common.api.riding.util.sampleField
|
||||
import com.cobblemon.mod.common.api.riding.util.visualizeDistanceField
|
||||
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
|
||||
import com.cobblemon.mod.common.api.riding.stats.RidingStat
|
||||
import com.cobblemon.mod.common.api.riding.util.DistanceFieldTuning
|
||||
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.core.particles.ParticleTypes
|
||||
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.ai.attributes.Attributes
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.phys.AABB
|
||||
import net.minecraft.world.phys.Vec2
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import org.joml.Matrix3f
|
||||
import org.joml.Quaternionf
|
||||
import org.joml.Vector3f
|
||||
import kotlin.math.*
|
||||
|
||||
class ClimbBehaviour : RidingBehaviour<ClimbSettings, ClimbState> {
|
||||
companion object {
|
||||
val KEY = cobblemonResource("land/climb")
|
||||
}
|
||||
|
||||
override val key = KEY
|
||||
|
||||
override fun getRidingStyle(settings: ClimbSettings, state: ClimbState): RidingStyle {
|
||||
return RidingStyle.LAND
|
||||
}
|
||||
|
||||
val poseProvider = PoseProvider<ClimbSettings, ClimbState>(PoseType.STAND)
|
||||
.with(PoseOption(PoseType.WALK) { _, state, vehicle ->
|
||||
return@PoseOption abs(state.rideVelocity.get().horizontalDistance()) > 0.0
|
||||
})
|
||||
|
||||
override fun isActive(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
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: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity
|
||||
): PoseType {
|
||||
return this.poseProvider.select(settings, state, vehicle)
|
||||
}
|
||||
|
||||
override fun speed(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
): Float {
|
||||
return state.rideVelocity.get().length().toFloat()
|
||||
}
|
||||
|
||||
override fun tick(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
) {
|
||||
handleSprinting(state, driver)
|
||||
inAirCheck(state, vehicle)
|
||||
tickStamina(settings, state, vehicle)
|
||||
|
||||
// attachmentNormal: discrete, physics-facing - which face are we touching, used
|
||||
// for the sticking force (newDownForce) in velocity().
|
||||
// renderSurfaceNormal: smoothed field-gradient - purely cosmetic, used to slowly
|
||||
// rotate the model's up vector in angRollVel(). The two are intentionally
|
||||
// separate and should not be conflated.
|
||||
updateAttachment(state, vehicle)
|
||||
updateRenderSurfaceNormal(state, vehicle)
|
||||
|
||||
//TODO: should be a config somewhere
|
||||
val debugVisualizeField = true;
|
||||
|
||||
if (debugVisualizeField && !vehicle.level().isClientSide && vehicle.tickCount % 5 == 0) {
|
||||
visualizeDistanceField(
|
||||
referenceEntity = vehicle,
|
||||
center = vehicle.position().add(0.0, (vehicle.bbHeight * 0.5f).toDouble(), 0.0),
|
||||
layers = listOf(
|
||||
DistanceFieldLayer(isoValue = 0.0f, color = Vector3f(1f, 0.2f, 0.2f)),
|
||||
DistanceFieldLayer(isoValue = 1.0f, color = Vector3f(0.2f, 0.6f, 1f))
|
||||
),
|
||||
horizontalRadius = 2.0,
|
||||
verticalRange = 2.0,
|
||||
sampleSpacing = 0.35,
|
||||
marchStep = 0.15,
|
||||
tuning = DistanceFieldTuning(smoothingRange = 0.5f)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the smoothed field-gradient normal used for rendering only (up-vector
|
||||
* alignment in angRollVel). Cached here at tick rate since collecting primitives
|
||||
* and sampling the field is real work - actually applying the rotation happens
|
||||
* separately at frame rate, same pattern as HorseBehaviour's surfDist/BoardBehaviour's
|
||||
* surfaceNormal.
|
||||
*/
|
||||
fun updateRenderSurfaceNormal(state: ClimbState, vehicle: PokemonEntity) {
|
||||
val level = vehicle.level()
|
||||
val center = vehicle.position().add(0.0, (vehicle.bbHeight * 0.5f).toDouble(), 0.0)
|
||||
val horizontalRadius = 1.5
|
||||
val verticalRange = 1.5
|
||||
|
||||
val searchBox = AABB(
|
||||
center.x - horizontalRadius, center.y - verticalRange, center.z - horizontalRadius,
|
||||
center.x + horizontalRadius, center.y + verticalRange, center.z + horizontalRadius
|
||||
)
|
||||
|
||||
val primitives: List<FieldPrimitive> = collectPrimitivesFromBlocks(level, searchBox, PrimitiveShape.ELLIPSOID)
|
||||
if (primitives.isEmpty()) return
|
||||
|
||||
val tuning = DistanceFieldTuning(smoothingRange = 0.5f)
|
||||
val gradient = estimateGradient(primitives, center, tuning)
|
||||
state.renderSurfaceNormal.set(gradient)
|
||||
}
|
||||
|
||||
/** Debug viz: draws a short line of particles from the vehicle out along the current attachment normal. */
|
||||
fun spawnAttachmentNormalParticles(vehicle: PokemonEntity, normal: Vec3) {
|
||||
val level = vehicle.level()
|
||||
if (!level.isClientSide) return
|
||||
if (normal.lengthSqr() < 1e-6) return
|
||||
|
||||
val origin = vehicle.position().add(0.0, (vehicle.bbHeight * 0.5f).toDouble(), 0.0)
|
||||
val length = 1.5
|
||||
val steps = 15
|
||||
for (i in 0..steps) {
|
||||
val t = i.toDouble() / steps
|
||||
val point = origin.add(normal.scale(length * t))
|
||||
level.addParticle(ParticleTypes.END_ROD, point.x, point.y, point.z, 0.0, 0.0, 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
fun inAirCheck(
|
||||
state: ClimbState,
|
||||
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)
|
||||
}
|
||||
|
||||
fun handleSprinting(
|
||||
state: ClimbState,
|
||||
driver: Player
|
||||
) {
|
||||
if (state.sprinting.get()) {
|
||||
state.sprinting.set(driver.isSprinting && state.stamina.get() > 0.0f)
|
||||
} else {
|
||||
// Only allow sprinting to start if over x percentage of stamina left
|
||||
val stamSprintPerc = 25.0f
|
||||
state.sprinting.set(driver.isSprinting && state.stamina.get() > stamSprintPerc / 100.0f)
|
||||
}
|
||||
}
|
||||
|
||||
fun tickStamina(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
) {
|
||||
val stam = state.stamina.get()
|
||||
var newStam = stam
|
||||
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr)).toFloat() / 20.0f
|
||||
|
||||
if (state.sprinting.get()) {
|
||||
newStam = max(0.0f,stam - stamDrainRate)
|
||||
|
||||
} else {
|
||||
newStam = min(1.0f,stam + stamDrainRate * 4)
|
||||
}
|
||||
|
||||
state.stamina.set(newStam)
|
||||
}
|
||||
|
||||
override fun updatePassengerRotation(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: LivingEntity
|
||||
) { return }
|
||||
|
||||
override fun clampPassengerRotation(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: LivingEntity
|
||||
) { }
|
||||
|
||||
|
||||
override fun rotation(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: LivingEntity
|
||||
): Vec2 {
|
||||
return Vec2(vehicle.xRot, vehicle.yRot )
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculates the rotation amount given the difference between
|
||||
* the current player y rot and entity y rot. This gives the
|
||||
* affect of influencing a rides rotation through the mouse
|
||||
* without it being instant.
|
||||
*
|
||||
* THIS IS ADAPTED FOR 3D ORIENTATION
|
||||
*/
|
||||
fun calcRotAmount(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: LivingEntity
|
||||
): Float {
|
||||
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
|
||||
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
|
||||
val walkHandlingBoost = 5
|
||||
val maxYawDiff = vehicle.runtime.resolveFloat(settings.lookYawLimit)
|
||||
|
||||
// Normalize the current rotation diff
|
||||
val controller = (vehicle as? OrientationControllable)?.orientationController
|
||||
if (controller == null || controller.orientation == null) return 0.0f
|
||||
|
||||
val rotDiff = Mth.wrapDegrees(Mth.wrapDegrees(driver.yRot) - controller.yaw).coerceIn(-maxYawDiff,maxYawDiff)
|
||||
val rotDiffNorm = rotDiff / maxYawDiff
|
||||
|
||||
// Take the square root so that the ride levels out quicker when at lower differences between entity
|
||||
// y and driver y
|
||||
// This influences the speed of the turn based on how far in one direction you're looking
|
||||
val minRotMod = 0.4f // Min speed that the rotation modulation converges towards
|
||||
val rotDiffMod = ((sqrt(abs(rotDiffNorm)) * (1.0f - minRotMod)) + minRotMod) * rotDiffNorm.sign
|
||||
|
||||
// Turn rate should be quick when walking and slower when sprinting.
|
||||
// Smoothly move between these based on speed
|
||||
val walkSpeed = getWalkSpeed(vehicle)
|
||||
val w = max(walkSpeed, vehicle.deltaMovement.horizontalDistance())
|
||||
val invRelSpeed = (RidingBehaviour.scaleToRange(w, walkSpeed, topSpeed ) - 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
|
||||
val rotAmount = turnSpeed.coerceIn(-abs(rotDiff), abs(rotDiff))
|
||||
|
||||
return rotAmount
|
||||
}
|
||||
|
||||
|
||||
override fun velocity(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
): Vec3 {
|
||||
state.rideVelocity.set(calculateRideSpaceVel(settings, state, vehicle, driver))
|
||||
return state.rideVelocity.get()
|
||||
}
|
||||
|
||||
private fun calculateRideSpaceVel(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
): Vec3 {
|
||||
val globalHorse: HorseSettings = CobblemonRideSettings.horse
|
||||
val canSprint = vehicle.runtime.resolveBoolean(settings.canSprint ?: globalHorse.canSprint!!)
|
||||
val canJump = vehicle.runtime.resolveBoolean(settings.canJump ?: globalHorse.canJump!!)
|
||||
val jumpForce = vehicle.runtime.resolveDouble(settings.jumpExpr ?: globalHorse.jumpExpr!!) * 0.75
|
||||
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalHorse.speedExpr!!)
|
||||
val walkSpeed = getWalkSpeed(vehicle)
|
||||
val topSpeed = if(canSprint && state.sprinting.get()) rideTopSpeed else walkSpeed
|
||||
val accelTime = if(state.sprinting.get()) (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalHorse.accelerationExpr!!) * 20.0) else 0.5 * 20.0
|
||||
val accel = topSpeed / accelTime
|
||||
var activeInput = false
|
||||
|
||||
/******************************************************
|
||||
* Gather the previous velocity and check for horizontal
|
||||
* collisions
|
||||
*****************************************************/
|
||||
var newVelocity = state.rideVelocity.get() //.normalize().scale(vehicle.deltaMovement.horizontalDistance())
|
||||
|
||||
/******************************************************
|
||||
* Gather the previous velocity and check for horizontal
|
||||
* collisions
|
||||
*****************************************************/
|
||||
if (vehicle.horizontalCollision) {
|
||||
var horizontalVec = Vec3(newVelocity.x, 0.0, newVelocity.z)
|
||||
horizontalVec = horizontalVec.normalize().scale(min(horizontalVec.length(), 0.3))
|
||||
newVelocity = Vec3(horizontalVec.x, newVelocity.y, horizontalVec.z)
|
||||
}
|
||||
|
||||
/******************************************************
|
||||
* Speed up and slow down based on input
|
||||
*****************************************************/
|
||||
if (driver.zza != 0.0f) {
|
||||
|
||||
// If on a tight turn then do not speed up past half of top speed in order to turn quicker
|
||||
// Also determine how fast to be slowing down based on how far turned you are
|
||||
val lookYawLimit = vehicle.runtime.resolveFloat(settings.lookYawLimit ?: globalHorse.lookYawLimit!!)
|
||||
val percOfMaxTurnSpeed = abs(Mth.wrapDegrees(driver.yRot - vehicle.yRot) / lookYawLimit) * 100.0f
|
||||
val turnPercThresh = 0.0f
|
||||
val s = min(((percOfMaxTurnSpeed - turnPercThresh) / (100.0f - turnPercThresh)).pow(1),1.0f)
|
||||
val effectiveTopSpeed = if (percOfMaxTurnSpeed > turnPercThresh) topSpeed / max(2.0f*s,1.0f) else topSpeed
|
||||
val turningSlowDown = s * 0.1
|
||||
|
||||
//make sure it can't exceed top speed
|
||||
val forwardInput = when {
|
||||
driver.zza > 0 && newVelocity.z > effectiveTopSpeed -> 0.0
|
||||
driver.zza < 0 && newVelocity.z < (-effectiveTopSpeed / 3.0) -> 0.0
|
||||
else -> driver.zza.sign
|
||||
}
|
||||
|
||||
// Add extra friction if trying to slow down to turn faster
|
||||
if (newVelocity.z > effectiveTopSpeed) {
|
||||
newVelocity = newVelocity.subtract(0.0, 0.0, min(turningSlowDown * newVelocity.z.sign, newVelocity.z))
|
||||
}
|
||||
|
||||
newVelocity = Vec3(
|
||||
newVelocity.x,
|
||||
newVelocity.y,
|
||||
(newVelocity.z + (accel * forwardInput.toDouble())))
|
||||
|
||||
activeInput = true
|
||||
}
|
||||
|
||||
/******************************************************
|
||||
* Gravity logic
|
||||
*****************************************************/
|
||||
val isOnTopSurface = state.attachmentNormal.get() == Vec3(0.0, 1.0, 0.0)
|
||||
if (isOnTopSurface) {
|
||||
if (!vehicle.onGround() && state.jumpTicks.get() <= 0) {
|
||||
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.6
|
||||
val terminalVel = 2.0
|
||||
newVelocity = Vec3(newVelocity.x, max(newVelocity.y - gravity, -terminalVel), newVelocity.z)
|
||||
} else if(vehicle.onGround()) {
|
||||
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
|
||||
}
|
||||
} else {
|
||||
// Wall/ceiling - small constant pull to stay stuck, not real gravity.
|
||||
val stickForce = 0.08
|
||||
newVelocity = Vec3(newVelocity.x, newVelocity.y - stickForce, newVelocity.z)
|
||||
}
|
||||
|
||||
|
||||
/******************************************************
|
||||
* Ground Friction
|
||||
*****************************************************/
|
||||
if( (newVelocity.horizontalDistance() > 0 && vehicle.onGround() && !activeInput) || newVelocity.horizontalDistance() > topSpeed) {
|
||||
newVelocity = newVelocity.subtract(0.0, 0.0, min(0.03 * newVelocity.z.sign, newVelocity.z))
|
||||
}
|
||||
|
||||
/******************************************************
|
||||
* Jump Logic
|
||||
*****************************************************/
|
||||
if (state.jumpTicks.get() > 0 || (state.jumpTicks.get() >= 0 && driver.jumping && vehicle.onGround() && canJump && driver.deltaMovement.y <= 0.1)) {
|
||||
// Spread out jumpforce so that variable height jumps are possible
|
||||
val jumpInputTicks = 6
|
||||
if (driver.jumping && (state.jumpTicks.get() >= 0 && state.jumpTicks.get() < jumpInputTicks)) {
|
||||
val appliedJumpForce = ((jumpForce*1.5) / jumpInputTicks) //* (1 - state.jumpTicks.get() / jumpInputTicks)
|
||||
newVelocity = Vec3(newVelocity.x, newVelocity.y + appliedJumpForce, newVelocity.z)
|
||||
state.jumpTicks.set(state.jumpTicks.get() + 1)
|
||||
} else {
|
||||
// Set delay before next jump is possible after hitting ground
|
||||
val tickJumpDelay = 3
|
||||
state.jumpTicks.set(-tickJumpDelay)
|
||||
}
|
||||
} else if (vehicle.onGround() && state.jumpTicks.get() < 0) {
|
||||
// Tick off the delay once reaching the ground
|
||||
state.jumpTicks.set(state.jumpTicks.get() + 1)
|
||||
}
|
||||
|
||||
// Zero out lateral velocity possibly picked up from a controller transition
|
||||
newVelocity = Vec3(0.0, newVelocity.y, newVelocity.z)
|
||||
|
||||
|
||||
return newVelocity
|
||||
}
|
||||
|
||||
private fun getWalkSpeed( vehicle: PokemonEntity ): Double {
|
||||
val walkspeed = vehicle.runtime.resolveDouble(vehicle.behaviour.moving.walk.walkSpeed)
|
||||
val movementSpeed = vehicle.attributes.getValue(Attributes.MOVEMENT_SPEED)
|
||||
val speedModifier = 1.2 * 0.35
|
||||
return walkspeed * movementSpeed * speedModifier
|
||||
|
||||
}
|
||||
|
||||
override fun angRollVel(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
if (vehicle !is OrientationControllable) return Vec3.ZERO
|
||||
val controller = vehicle.orientationController
|
||||
|
||||
// Smoothly rotate the model's up vector toward the (separately computed,
|
||||
// smoothed) field-gradient normal. Purely cosmetic - does not affect attachment
|
||||
// or movement, which are driven by state.attachmentNormal instead.
|
||||
val upAlignmentRatePerTick = 0.2
|
||||
val frameBlend = 1.0 - (1.0 - upAlignmentRatePerTick).pow(deltaTime * 20.0)
|
||||
alignUpToSurfaceNormal(controller, state.renderSurfaceNormal.get(), frameBlend.toFloat())
|
||||
|
||||
return Vec3.ZERO
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates [controller]'s orientation so its up vector moves a fraction of the way
|
||||
* toward [targetUp] - partial per call, not instant, so alignment settles smoothly
|
||||
* rather than snapping every time the target changes.
|
||||
*/
|
||||
private fun alignUpToSurfaceNormal(controller: OrientationController, targetUp: Vec3, alignmentBlend: Float) {
|
||||
if (targetUp.lengthSqr() < 1e-6) return
|
||||
val targetUpVec = Vector3f(targetUp.x.toFloat(), targetUp.y.toFloat(), targetUp.z.toFloat()).normalize()
|
||||
|
||||
controller.updateOrientation { current ->
|
||||
val currentUp = current.transform(Vector3f(UP), Vector3f())
|
||||
if (currentUp.lengthSquared() < 1e-6f) return@updateOrientation current
|
||||
|
||||
// Full rotation that would exactly align currentUp to targetUp...
|
||||
val fullCorrection = Quaternionf().rotationTo(currentUp, targetUpVec)
|
||||
// ...but only take a partial step toward it, so alignment is smooth, not instant.
|
||||
val steppedCorrection = Quaternionf().slerp(fullCorrection, alignmentBlend)
|
||||
|
||||
Matrix3f().set(steppedCorrection).mul(current, Matrix3f())
|
||||
}
|
||||
}
|
||||
|
||||
override fun rotationOnMouseXY(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
mouseY: Double,
|
||||
mouseX: Double,
|
||||
mouseYSmoother: SmoothDouble,
|
||||
mouseXSmoother: SmoothDouble,
|
||||
sensitivity: Double,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
if (vehicle !is OrientationControllable) return Vec3.ZERO
|
||||
val vehicleController = vehicle.orientationController
|
||||
|
||||
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(xInput, 0.0, 0.0)
|
||||
}
|
||||
|
||||
|
||||
override fun canJump(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun setRideBar(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
): Float {
|
||||
return (state.stamina.get() / 1.0f)
|
||||
}
|
||||
|
||||
override fun jumpForce(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
jumpStrength: Int
|
||||
): Vec3 {
|
||||
return Vec3.ZERO
|
||||
}
|
||||
|
||||
override fun gravity(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
regularGravity: Double
|
||||
): Double {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
override fun rideFovMultiplier(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
): Float {
|
||||
return 1.0f
|
||||
}
|
||||
|
||||
override fun useAngVelSmoothing(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun inertia(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity
|
||||
): Double {
|
||||
return 1.0
|
||||
}
|
||||
|
||||
override fun shouldRoll(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun turnOffOnGround(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun dismountOnShift(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun shouldRotatePokemonHead(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun shouldRotateRiderHead(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getRideSounds(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity
|
||||
): RideSoundSettingsList {
|
||||
return settings.rideSounds
|
||||
}
|
||||
|
||||
override fun createDefaultState(settings: ClimbSettings) = ClimbState()
|
||||
}
|
||||
|
||||
class ClimbSettings : RidingBehaviourSettings {
|
||||
override val key = ClimbBehaviour.KEY
|
||||
override val stats = mutableMapOf<RidingStat, IntRange>()
|
||||
|
||||
var canJump = "true".asExpression()
|
||||
private set
|
||||
|
||||
var canSprint = "true".asExpression()
|
||||
private set
|
||||
|
||||
var lookYawLimit = "90.0f".asExpression()
|
||||
private set
|
||||
|
||||
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'LAND', 1.0, 0.4)".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', 0.5, 2.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 six block jump
|
||||
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'LAND', 1.0, 0.38)".asExpression()
|
||||
private set
|
||||
|
||||
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 180.0, 40.0)".asExpression()
|
||||
private set
|
||||
|
||||
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
|
||||
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeResourceLocation(key)
|
||||
buffer.writeRidingStats(stats)
|
||||
rideSounds.encode(buffer)
|
||||
buffer.writeExpression(speedExpr)
|
||||
buffer.writeExpression(accelerationExpr)
|
||||
buffer.writeExpression(staminaExpr)
|
||||
buffer.writeExpression(jumpExpr)
|
||||
buffer.writeExpression(handlingExpr)
|
||||
buffer.writeExpression(canJump)
|
||||
buffer.writeExpression(canSprint)
|
||||
}
|
||||
|
||||
override fun decode(buffer: RegistryFriendlyByteBuf) {
|
||||
stats.putAll(buffer.readRidingStats())
|
||||
rideSounds = RideSoundSettingsList.decode(buffer)
|
||||
speedExpr = buffer.readExpression()
|
||||
accelerationExpr = buffer.readExpression()
|
||||
staminaExpr = buffer.readExpression()
|
||||
jumpExpr = buffer.readExpression()
|
||||
handlingExpr = buffer.readExpression()
|
||||
canJump = buffer.readExpression()
|
||||
canSprint = buffer.readExpression()
|
||||
}
|
||||
}
|
||||
|
||||
class ClimbState : RidingBehaviourState() {
|
||||
var sprinting = ridingState(false, Side.CLIENT)
|
||||
var inAir = ridingState(false, Side.CLIENT)
|
||||
var jumpTicks = ridingState(0, Side.CLIENT)
|
||||
|
||||
// Discrete, physics-facing: which of the 6 faces we're attached to right now.
|
||||
// Drives newDownForce in velocity(). Set by updateAttachment().
|
||||
var attachmentNormal = ridingState(Vec3(0.0, 1.0, 0.0), Side.CLIENT)
|
||||
var attachmentPoint = ridingState(Vec3(0.0, 1.0, 0.0), Side.CLIENT)
|
||||
|
||||
// Smoothed, rendering-facing: the field-gradient normal used only to slowly rotate
|
||||
// the model's visual up vector in angRollVel(). Deliberately separate from
|
||||
// attachmentNormal - see comments in tick(). Set by updateRenderSurfaceNormal().
|
||||
var renderSurfaceNormal = ridingState(Vec3(0.0, 1.0, 0.0), Side.CLIENT)
|
||||
|
||||
override fun encode(buffer: FriendlyByteBuf) {
|
||||
super.encode(buffer)
|
||||
buffer.writeBoolean(sprinting.get())
|
||||
buffer.writeBoolean(inAir.get())
|
||||
buffer.writeInt(jumpTicks.get())
|
||||
}
|
||||
|
||||
override fun decode(buffer: FriendlyByteBuf) {
|
||||
super.decode(buffer)
|
||||
sprinting.set(buffer.readBoolean(), forced = true)
|
||||
inAir.set(buffer.readBoolean(), forced = true)
|
||||
jumpTicks.set(buffer.readInt(), forced = true)
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
super.reset()
|
||||
sprinting.set(false, forced = true)
|
||||
inAir.set(false, forced = true)
|
||||
jumpTicks.set(0, forced = true)
|
||||
attachmentNormal.set(Vec3(0.0, 1.0, 0.0), forced = true)
|
||||
attachmentPoint.set(Vec3(0.0, 1.0, 0.0), forced = true)
|
||||
renderSurfaceNormal.set(Vec3(0.0, 1.0, 0.0), forced = true)
|
||||
}
|
||||
|
||||
override fun copy() = ClimbState().also {
|
||||
it.rideVelocity.set(this.rideVelocity.get(), forced = true)
|
||||
it.stamina.set(this.stamina.get(), forced = true)
|
||||
it.sprinting.set(this.sprinting.get(), forced = true)
|
||||
it.inAir.set(this.inAir.get(), forced = true)
|
||||
it.jumpTicks.set(this.jumpTicks.get(), forced = true)
|
||||
it.attachmentNormal.set(this.attachmentNormal.get(), forced = true)
|
||||
it.attachmentPoint.set(this.attachmentNormal.get(), forced = true)
|
||||
it.renderSurfaceNormal.set(this.renderSurfaceNormal.get(), forced = true)
|
||||
}
|
||||
|
||||
override fun shouldSync(previous: RidingBehaviourState): Boolean {
|
||||
if (previous !is ClimbState) return false
|
||||
if (previous.sprinting.get() != sprinting.get()) return true
|
||||
if (previous.inAir.get() != inAir.get()) return true
|
||||
return super.shouldSync(previous)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateAttachment(state: ClimbState, vehicle: PokemonEntity) {
|
||||
val probeMargin = 0.15
|
||||
val currentFace = AttachmentFace.entries.firstOrNull { it.normal == state.attachmentNormal.get() }
|
||||
val face = determineAttachmentFace(vehicle, currentFace, probeMargin)
|
||||
if (face != null) {
|
||||
state.attachmentNormal.set(face.normal)
|
||||
}
|
||||
// face == null means nothing's currently in reach - leave attachmentNormal as
|
||||
// whatever it last was until the jump/detach state takes over.
|
||||
}
|
||||
|
||||
enum class AttachmentFace(val normal: Vec3) {
|
||||
UP(Vec3(0.0, 1.0, 0.0)),
|
||||
DOWN(Vec3(0.0, -1.0, 0.0)),
|
||||
NORTH(Vec3(0.0, 0.0, -1.0)),
|
||||
SOUTH(Vec3(0.0, 0.0, 1.0)),
|
||||
EAST(Vec3(1.0, 0.0, 0.0)),
|
||||
WEST(Vec3(-1.0, 0.0, 0.0))
|
||||
}
|
||||
|
||||
/** Thin slab test on one face of the vehicle's actual bounding box - true if anything solid is touching that side. */
|
||||
fun probeFaceContact(vehicle: PokemonEntity, face: AttachmentFace, probeMargin: Double): Boolean {
|
||||
val box = vehicle.boundingBox
|
||||
val testBox = when (face) {
|
||||
AttachmentFace.UP -> AABB(box.minX, box.maxY, box.minZ, box.maxX, box.maxY + probeMargin, box.maxZ)
|
||||
AttachmentFace.DOWN -> AABB(box.minX, box.minY - probeMargin, box.minZ, box.maxX, box.minY, box.maxZ)
|
||||
AttachmentFace.NORTH -> AABB(box.minX, box.minY, box.minZ - probeMargin, box.maxX, box.maxY, box.minZ)
|
||||
AttachmentFace.SOUTH -> AABB(box.minX, box.minY, box.maxZ, box.maxX, box.maxY, box.maxZ + probeMargin)
|
||||
AttachmentFace.EAST -> AABB(box.maxX, box.minY, box.minZ, box.maxX + probeMargin, box.maxY, box.maxZ)
|
||||
AttachmentFace.WEST -> AABB(box.minX - probeMargin, box.minY, box.minZ, box.minX, box.maxY, box.maxZ)
|
||||
}
|
||||
return !vehicle.level().noCollision(vehicle, testBox)
|
||||
}
|
||||
|
||||
/** Stays on the current face if it's still touching, otherwise scans for a new one. */
|
||||
fun determineAttachmentFace(vehicle: PokemonEntity, currentFace: AttachmentFace?, probeMargin: Double): AttachmentFace? {
|
||||
if (currentFace != null && probeFaceContact(vehicle, currentFace, probeMargin)) {
|
||||
return currentFace
|
||||
}
|
||||
return AttachmentFace.entries.firstOrNull { probeFaceContact(vehicle, it, probeMargin) }
|
||||
}
|
||||
@@ -182,6 +182,7 @@ class RideControlsOverlay : Gui(Minecraft.getInstance()) {
|
||||
showHorizontalMouse = false
|
||||
}
|
||||
VehicleBehaviour.KEY -> {}
|
||||
BoardBehaviour.KEY -> {}
|
||||
}
|
||||
|
||||
val centerX = minecraft.window.guiScaledWidth / 2
|
||||
|
||||
+10
-4
@@ -41,6 +41,8 @@ import kotlin.math.sign
|
||||
import kotlin.math.sqrt
|
||||
|
||||
object MountedCameraRenderer {
|
||||
private const val MIN_OFFSET_LENGTH = 1.0E-6
|
||||
|
||||
private var returnTimer: Float = 0f
|
||||
private var lastHandledRotationTime: Double = Double.MIN_VALUE
|
||||
private var frameTime: Double = 0.0
|
||||
@@ -79,7 +81,7 @@ object MountedCameraRenderer {
|
||||
val locator = delegate.locatorStates[locatorName] ?: return null
|
||||
|
||||
// Get additional offset from poser and add to the eyeHeight offset
|
||||
val currEyeHeight: Double = Mth.lerp(instance.partialTickTime.toDouble(), eyeHeight, eyeHeightOld)
|
||||
val currEyeHeight: Double = Mth.lerp(instance.partialTickTime.toDouble(), eyeHeightOld, eyeHeight)
|
||||
var offset = Vec3(0.0, currEyeHeight - (driver.bbHeight / 2), 0.0)
|
||||
var eyeOffset = Vec3(0.0, currEyeHeight - (driver.bbHeight / 2), 0.0)
|
||||
|
||||
@@ -126,10 +128,10 @@ object MountedCameraRenderer {
|
||||
} */
|
||||
|
||||
val offsetDistance = offset.length()
|
||||
val offsetDirection = offset.scale(1 / offsetDistance)
|
||||
val offsetDirection = safeDirection(offset, offsetDistance)
|
||||
|
||||
val eyeOffsetDistance = eyeOffset.length()
|
||||
val eyeOffsetDirection = eyeOffset.scale(1 / eyeOffsetDistance)
|
||||
val eyeOffsetDirection = safeDirection(eyeOffset, eyeOffsetDistance)
|
||||
|
||||
// Use getMaxZoom to calculate clipping
|
||||
val maxZoom: Double = getMaxZoom(offsetDistance, offsetDirection, pos, instance)
|
||||
@@ -329,7 +331,7 @@ object MountedCameraRenderer {
|
||||
val offset = if (thirdPersonReverse && cameraOffsets.containsKey(locatorName + "_reverse")) {
|
||||
cameraOffsets[locatorName + "_reverse"]!!
|
||||
} else if (thirdPersonReverse && cameraOffsets.containsKey("seat_default_reverse")) {
|
||||
cameraOffsets[locatorName + "seat_default_reverse"]!!
|
||||
cameraOffsets["seat_default_reverse"]!!
|
||||
} else if (cameraOffsets.containsKey(locatorName)) {
|
||||
cameraOffsets[locatorName]!!
|
||||
} else if (cameraOffsets.containsKey("seat_default")) {
|
||||
@@ -354,6 +356,10 @@ object MountedCameraRenderer {
|
||||
}
|
||||
|
||||
|
||||
/** Unit direction of [offset], or zero when it's too short to have one (avoids NaN from 0 / 0). */
|
||||
private fun safeDirection(offset: Vec3, length: Double): Vec3 =
|
||||
if (length > MIN_OFFSET_LENGTH) offset.scale(1 / length) else Vec3.ZERO
|
||||
|
||||
private fun getMaxZoom(maxZoom: Double, directionVector: Vec3, positionVector: Vec3, instance: Camera): Double {
|
||||
var maxZoom = maxZoom
|
||||
for (i in 0 .. 7) {
|
||||
|
||||
+5
@@ -15,6 +15,7 @@ import com.cobblemon.mod.common.api.riding.behaviour.types.air.HelicopterSetting
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.air.HoverSettings
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.air.JetSettings
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.air.RocketSettings
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.air.BoardSettings
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseSettings
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.land.MinekartSettings
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleSettings
|
||||
@@ -35,6 +36,7 @@ class RideSettingsSyncPacket(
|
||||
val hover: HoverSettings,
|
||||
val jet: JetSettings,
|
||||
val rocket: RocketSettings,
|
||||
val board: BoardSettings,
|
||||
val horse: HorseSettings,
|
||||
val minekart: MinekartSettings,
|
||||
val vehicle: VehicleSettings,
|
||||
@@ -52,6 +54,7 @@ class RideSettingsSyncPacket(
|
||||
hover.encode(buffer)
|
||||
jet.encode(buffer)
|
||||
rocket.encode(buffer)
|
||||
board.encode(buffer)
|
||||
horse.encode(buffer)
|
||||
minekart.encode(buffer)
|
||||
vehicle.encode(buffer)
|
||||
@@ -71,6 +74,7 @@ class RideSettingsSyncPacket(
|
||||
hover = HoverSettings(),
|
||||
jet = JetSettings(),
|
||||
rocket = RocketSettings(),
|
||||
board = BoardSettings(),
|
||||
horse = HorseSettings(),
|
||||
minekart = MinekartSettings(),
|
||||
vehicle = VehicleSettings(),
|
||||
@@ -85,6 +89,7 @@ class RideSettingsSyncPacket(
|
||||
hover.decode(buffer)
|
||||
jet.decode(buffer)
|
||||
rocket.decode(buffer)
|
||||
board.decode(buffer)
|
||||
horse.decode(buffer)
|
||||
minekart.decode(buffer)
|
||||
vehicle.decode(buffer)
|
||||
|
||||
+3
-6
@@ -17,12 +17,7 @@ import com.cobblemon.mod.common.api.riding.behaviour.types.composite.strategies.
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.strategies.FallStrategy
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.strategies.JumpStrategy
|
||||
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.land.*
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.*
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.google.gson.JsonDeserializationContext
|
||||
@@ -50,7 +45,9 @@ object RidingBehaviourSettingsAdapter : JsonDeserializer<RidingBehaviourSettings
|
||||
MinekartBehaviour.KEY to MinekartSettings::class.java,
|
||||
HoverBehaviour.KEY to HoverSettings::class.java,
|
||||
RocketBehaviour.KEY to RocketSettings::class.java,
|
||||
BoardBehaviour.KEY to BoardSettings::class.java,
|
||||
SubmarineBehaviour.KEY to SubmarineSettings::class.java,
|
||||
ClimbBehaviour.KEY to ClimbSettings::class.java,
|
||||
CompositeBehaviour.KEY to CompositeSettings::class.java,
|
||||
|
||||
/*
|
||||
|
||||
+642
-177
File diff suppressed because it is too large
Load Diff
+8
@@ -846,6 +846,14 @@
|
||||
"cubes": [
|
||||
{"origin": [-2, -1, 27], "size": [4, 1, 4], "inflate": -0.01, "uv": [104, 47]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_seat_1",
|
||||
"parent": "surfboard",
|
||||
"pivot": [0, 14.5, 3],
|
||||
"locators": {
|
||||
"seat_1": [19, 1.5, 0]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"infiniteStamina": "false",
|
||||
"canJump": "true",
|
||||
"canSprint": "true",
|
||||
"lookYawLimit": "90.0",
|
||||
"speedExpr": "q.get_ride_stats('SPEED', 'LAND', 1.2, 0.1)",
|
||||
"accelerationExpr": "q.get_ride_stats('ACCELERATION', 'LAND', 0.1, 12.0)",
|
||||
"staminaExpr": "q.get_ride_stats('STAMINA', 'LAND', 120.0, 4.0)",
|
||||
"jumpExpr": "q.get_ride_stats('JUMP', 'LAND', 1.6, 0.2)",
|
||||
"handlingExpr": "q.get_ride_stats('SKILL', 'LAND', 90.0, 30.0)"
|
||||
}
|
||||
@@ -53,7 +53,7 @@
|
||||
"riding": {
|
||||
"behaviours": {
|
||||
"LAND": {
|
||||
"key": "cobblemon:land/horse",
|
||||
"key": "cobblemon:land/climb",
|
||||
"rideSounds": [],
|
||||
"stats": {
|
||||
"ACCELERATION": "55-85",
|
||||
|
||||
@@ -48,6 +48,26 @@
|
||||
"height": 2.15,
|
||||
"fixed": false
|
||||
},
|
||||
"riding": {
|
||||
"behaviours": {
|
||||
"LAND": {
|
||||
"key": "cobblemon:air/board",
|
||||
"rideSounds": [],
|
||||
"stats": {
|
||||
"ACCELERATION": "50-100",
|
||||
"JUMP": "50-100",
|
||||
"SKILL": "50-100",
|
||||
"SPEED": "50-100",
|
||||
"STAMINA": "50-100"
|
||||
}
|
||||
}
|
||||
},
|
||||
"seats": [
|
||||
{
|
||||
"locator": "seat_1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"behaviour": {
|
||||
"resting": {
|
||||
"canSleep": true,
|
||||
|
||||
Reference in New Issue
Block a user