Compare commits

..
78 changed files with 6001 additions and 4533 deletions

No files matched your search

-11
View File
@@ -8,16 +8,8 @@
### Pokémon Added
### Model updates for the following Pokémon
- Froakie
- Frogadier
- Greninja
(They have eyelids now.)
### Animation updates for the following Pokémon
- Froakie
- Frogadier
- Greninja
(They can blink now.)
### Changes
- Reworked spawn presets and added structure tags for spawns
@@ -25,8 +17,6 @@
### Fixes
- Fixed blank Cooking Pot recipe book entries and missing tabs on Fabric.
- Fixed Minior's "Shields Down" ability not working as intended
- Fixed Dusclops using Squirtle's cry
- Fixed size error with ceiling attached Orb hitbox
### Developer
@@ -34,7 +24,6 @@
- Added `q.target_fainted` function to move action effects that checks if any opponents fainted from the move.
### Particles
- Updated Water shuriken to account for new `q.target_fainted` function
#### New Move Animations
@@ -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;
@@ -83,7 +83,7 @@ public abstract class CameraMixin implements CameraDuck {
}
// reset the rotation values if it is now not being ridden.
MountedCameraRenderer.INSTANCE.setSmoothRotation(null);
MountedCameraRenderer.INSTANCE.reset();
original.call(instance, x, y, z);
}
}
@@ -19,6 +19,7 @@ import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.phys.Vec2;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -71,17 +72,18 @@ public class HumanoidModelMixin {
behaviour.shouldRotateRiderHead(settings, state, pokemonEntity)
);
// If the player is on a rollable ride then use the ride local rotations to rotate the head
// This is to allow the player head to face the correct direction when "freelooking"
// TODO: find a way for this to work on built jars without borking head rotations.
// if (entity instanceof RidePassenger playerRotater &&
// vehicle instanceof OrientationControllable controller &&
// controller.getOrientationController().isActive()) {
// this.head.xRot = playerRotater.cobblemon$getRideXRot();
// headPitch = playerRotater.cobblemon$getRideXRot();
// this.head.yRot = playerRotater.cobblemon$getRideYRot();
// netHeadYaw = playerRotater.cobblemon$getRideYRot();
// }
// Vanilla's head angles are world yaw but the body sits on a seat that can be on its side, so measure the real look against the seat
if (shouldRotatePlayerHead) {
Vec2 riderHead = MountedPlayerRenderer.riderHeadAngles(pokemonEntity, entity, Mth.clamp(ageInTicks - entity.tickCount, 0f, 1f));
if (riderHead != null) {
netHeadYaw = riderHead.x;
headPitch = riderHead.y;
if (this.head != null) {
this.head.yRot = netHeadYaw * Mth.DEG_TO_RAD;
this.head.xRot = headPitch * Mth.DEG_TO_RAD;
}
}
}
if (!shouldRotatePlayerHead) {
netHeadYaw = 0f;
@@ -9,11 +9,14 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.client.render.camera.MountedCameraRenderer;
import com.cobblemon.mod.common.client.render.player.FirstPersonRecallBallRenderer;
import com.cobblemon.mod.common.item.PokedexItem;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.client.renderer.ItemInHandRenderer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.world.InteractionHand;
@@ -40,6 +43,14 @@ public class ItemInHandRendererMixin {
}
}
@Inject(method = "renderHandsWithItems", at = @At("HEAD"))
private void cobblemon$rollHandsWithRide(float partialTicks, PoseStack poseStack, MultiBufferSource.BufferSource buffer, LocalPlayer player, int light, CallbackInfo ci) {
float roll = MountedCameraRenderer.INSTANCE.getHandRoll();
if (roll != 0f) {
poseStack.mulPose(Axis.ZN.rotationDegrees(roll));
}
}
@Redirect(method = "renderHandsWithItems", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;mulPose(Lorg/joml/Quaternionf;)V"))
private void cobblemon$renderHandswithItems(PoseStack instance, Quaternionf quaternion) {
if (!(Minecraft.getInstance().player != null && Minecraft.getInstance().player.getVehicle() instanceof OrientationControllable controllable && controllable.getOrientationController().isActive())) {
@@ -32,7 +32,6 @@ import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.player.Input;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.ClipContext;
@@ -50,11 +49,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,30 +82,28 @@ 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"))
private void cobblemon$updateOrientationControllerRideTick(CallbackInfo ci) {
if (Minecraft.getInstance().player != (Object)this) return;
var driver = (LocalPlayer)(Object)this;
var vehicle = driver.getVehicle();
if (!(this.getVehicle() instanceof OrientationControllable controllableVehicle)) return;
var shouldUseCustomOrientation = cobblemon$shouldUseCustomOrientation((LocalPlayer)(Object)this);
var vehicleController = controllableVehicle.getOrientationController();
// If the player has just switched to riding a custom orientation ride then set their
// x and y rots local to the vehicle rots(if the controller wasn't active and will now be active)
// This ensures that on transition your camera stays in the same spot
if (!vehicleController.isActive() && shouldUseCustomOrientation) {
// Set local to the vehicle x and yrot so
var playerRotater = (RidePassenger)driver;
playerRotater.cobblemon$setRideXRot(Mth.wrapDegrees(driver.getXRot() - vehicle.getXRot()));
playerRotater.cobblemon$setRideYRot(Mth.wrapDegrees(driver.getYRot() - vehicle.getYRot()));
}
// Look angles get seeded by the camera on its first rolling frame
vehicleController.setActive(shouldUseCustomOrientation);
}
@@ -11,6 +11,10 @@ package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.Cobblemon;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.api.riding.behaviour.RideSteerInput;
import com.cobblemon.mod.common.api.riding.camera.RideLookInput;
import com.cobblemon.mod.common.api.riding.camera.RideLookSettings;
import com.cobblemon.mod.common.api.riding.camera.RideLookTake;
import com.cobblemon.mod.common.client.CobblemonClient;
import com.cobblemon.mod.common.client.keybind.keybinds.PartySendBinding;
import com.cobblemon.mod.common.client.keybind.keybinds.RidingFreelookBinding;
@@ -20,13 +24,12 @@ import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext;
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.blaze3d.Blaze3D;
import kotlin.Pair;
import kotlin.Unit;
import net.minecraft.client.Minecraft;
import net.minecraft.client.MouseHandler;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.util.Mth;
import net.minecraft.util.SmoothDouble;
import net.minecraft.world.phys.Vec3;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@@ -42,6 +45,11 @@ import static net.minecraft.util.Mth.lerp;
@Mixin(MouseHandler.class)
public abstract class MouseHandlerMixin {
// Entity.turn scales mouse movement to degrees by this
@Unique private static final float COBBLEMON$VANILLA_DEGREES_PER_MOUSE_UNIT = 0.15F;
// Share of the look offset closed per second as it drifts back to centre. Raise to recentre faster
@Unique private static final float COBBLEMON$LOOK_RECENTER_RATE = 5.0F;
@Shadow private double accumulatedScrollY;
@Shadow @Final private Minecraft minecraft;
@@ -160,74 +168,48 @@ public abstract class MouseHandlerMixin {
}
var vehicleController = controllableVehicle.getOrientationController();
var freeLooking = RidingFreelookBinding.INSTANCE.isDown();
Pair<Boolean, Boolean> mouseShouldModifyDriver = ((PokemonEntity) vehicle).ifRidingAvailableSupply(new Pair(false, false), (behaviour, settings, state) -> {
return behaviour.mouseModifiesDriverRotation(
settings,
state,
(PokemonEntity) vehicle
);
var pokemonVehicle = (PokemonEntity) vehicle;
var playerRotater = (RidePassenger)player;
float yawDelta = (float)cursorDeltaX * COBBLEMON$VANILLA_DEGREES_PER_MOUSE_UNIT;
float pitchDelta = (float)cursorDeltaY * COBBLEMON$VANILLA_DEGREES_PER_MOUSE_UNIT;
// Passengers only ever look around
if (player.getControlledVehicle() == null) {
cobblemon$addLook(player, yawDelta, pitchDelta);
return false;
}
// The ride takes what it wants of the mouse to steer with and the rest moves the look. Freelook keeps it all for the look
var lookInput = new RideLookInput(yawDelta, pitchDelta, playerRotater.cobblemon$getRideYRot(), playerRotater.cobblemon$getRideXRot());
RideLookTake take = RidingFreelookBinding.INSTANCE.isDown() ? RideLookTake.NONE : pokemonVehicle.ifRidingAvailableSupply(RideLookTake.all(lookInput), (behaviour, settings, state) -> {
return behaviour.consumeLook(settings, state, pokemonVehicle, lookInput);
});
float lookYaw = yawDelta - take.getYaw();
float lookPitch = pitchDelta - take.getPitch();
if (lookYaw != 0.0f || lookPitch != 0.0f) {
cobblemon$addLook(player, lookYaw, lookPitch);
}
// Drift the look back to centre on the axes the ride steers with
float recenter = min(COBBLEMON$LOOK_RECENTER_RATE * (float)movementTime, 1.0f);
if (take.getRecenterYaw()) {
playerRotater.cobblemon$setRideYRot(lerp(recenter, Mth.wrapDegrees(playerRotater.cobblemon$getRideYRot()), 0.0f));
}
if (take.getRecenterPitch()) {
playerRotater.cobblemon$setRideXRot(lerp(recenter, Mth.wrapDegrees(playerRotater.cobblemon$getRideXRot()), 0.0f));
}
// Hand the ride its share back in mouse units since that's what every ride's steering is tuned for
double rideDeltaX = yawDelta == 0.0f ? 0.0 : cursorDeltaX * (take.getYaw() / yawDelta);
double rideDeltaY = pitchDelta == 0.0f ? 0.0 : cursorDeltaY * (take.getPitch() / pitchDelta);
var steerInput = cobblemon$getSteerInput(vehicleController, rideDeltaX, rideDeltaY, movementTime);
pokemonVehicle.ifRidingAvailable((behaviour, settings, state) -> {
behaviour.steer(settings, state, pokemonVehicle, player, steerInput);
return Unit.INSTANCE;
});
//-------------------------------------------------------------------------
// If looking around then modify the players orientation instead and zero the mouse deltas so the mouse doesn't
// affect the vehicle orientation when looking around.
// If the client is a passenger it will modify these rotations and return
//-------------------------------------------------------------------------
if (freeLooking || player.getControlledVehicle() == null) {
// Modify driver rotations
cobblemon$setDriverRotations(player, cursorDeltaX, cursorDeltaY);
// If the player/passenger is not the driver then return and ignore all vehicle rotation logic
if (player.getControlledVehicle() == null) return false;
// Set to zero so that vehicle rotations don't also get affected by movement.
cursorDeltaX = 0.0;
cursorDeltaY = 0.0;
} else if (mouseShouldModifyDriver.getFirst()) { // If not freelooking then check modification per mouse input dimension
cobblemon$setDriverRotations(player, cursorDeltaX, 0.0);
cursorDeltaX = 0.0;
} else if (mouseShouldModifyDriver.getSecond()) {
cobblemon$setDriverRotations(player, 0.0, cursorDeltaY);
cursorDeltaY = 0.0;
}
//-------------------------------------------------------------------------
// Smoothly return driver rotations to zero when needed
//-------------------------------------------------------------------------
var playerRotater = (RidePassenger)player;
if( !freeLooking && !mouseShouldModifyDriver.getFirst()){
playerRotater.cobblemon$setRideYRot(lerp(min(5.0f * (float)movementTime, 1.0f), Mth.wrapDegrees(playerRotater.cobblemon$getRideYRot()), 0.0f));
}
if( !freeLooking && !mouseShouldModifyDriver.getSecond()) {
playerRotater.cobblemon$setRideXRot(lerp(min(5.0f * (float)movementTime, 1.0f), Mth.wrapDegrees(playerRotater.cobblemon$getRideXRot()), 0.0f));
}
// Send mouse input to be interpreted into rotation deltas by the ride controller
Vec3 angVecMouse = cobblemon$getRideMouseRotation(cursorDeltaX, cursorDeltaY, movementTime);
// Perform Rotation using mouse influenced rotation deltas.
vehicleController.rotate(
(float) angVecMouse.x,
(float) angVecMouse.y,
(float) angVecMouse.z
);
// Gather and apply the current rotation deltas
var angRot = cobblemon$getAngularVelocity(movementTime);
// Apply smoothing if requested by the controller.
if(cobblemon$shouldUseAngVelSmoothing()) {
var yaw = yawSmoother.getNewDeltaValue(angRot.x * 0.5f, d);
var pitch = pitchSmoother.getNewDeltaValue(angRot.y * 0.5f, d);
var roll = rollSmoother.getNewDeltaValue(angRot.z * 0.5f, d);
vehicleController.rotate((float) yaw, (float) pitch, (float) roll);
}
else {
vehicleController.rotate((float) (angRot.x * 10 * d), (float) (angRot.y * 10 * d), (float) (angRot.z * 10 * d));
}
return returnValue;
// The camera sets the player's rotation from the look every frame so vanilla turn would only get overwritten
return false;
}
@Inject(method = "handleAccumulatedMovement", at = @At("HEAD"))
@@ -248,28 +230,9 @@ public abstract class MouseHandlerMixin {
this.turnPlayer(cobblemon$timeDelta);
}
/** The ride's share of the mouse put through the riding sensitivity, invert and swap options. */
@Unique
private Vec3 cobblemon$getAngularVelocity(double deltaTime) {
var player = minecraft.player;
if (player == null) return Vec3.ZERO;
if (!(player instanceof OrientationControllable)) return Vec3.ZERO;
var playerVehicle = player.getVehicle();
if (playerVehicle == null) return Vec3.ZERO;
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return Vec3.ZERO;
return pokemonEntity.ifRidingAvailableSupply(Vec3.ZERO, (behaviour, settings, state) -> {
return behaviour.angRollVel(settings, state, pokemonEntity, player, deltaTime);
});
}
@Unique
private Vec3 cobblemon$getRideMouseRotation(double mouseX, double mouseY, double deltaTime) {
var player = minecraft.player;
if (player == null) return Vec3.ZERO;
var vehicle = player.getVehicle();
if (vehicle == null) return Vec3.ZERO;
if (!(vehicle instanceof PokemonEntity pokemonEntity)) return Vec3.ZERO;
private RideSteerInput cobblemon$getSteerInput(OrientationController controller, double mouseX, double mouseY, double deltaTime) {
var invertYaw = Cobblemon.config.getInvertYaw();
var invertPitch = Cobblemon.config.getInvertPitch();
@@ -282,21 +245,18 @@ public abstract class MouseHandlerMixin {
var pitch = (swapXAndY ? xValue : yValue) * (invertPitch? -1.0f : 1.0f);
var sensitivity = cobblemon$getRidingSensitivity();
return pokemonEntity.ifRidingAvailableSupply(Vec3.ZERO, (behaviour, settings, state) -> {
return behaviour.rotationOnMouseXY(
settings,
state,
pokemonEntity,
player,
pitch,
yaw,
yMouseSmoother,
xMouseSmoother,
sensitivity,
deltaTime
);
});
return new RideSteerInput(
controller,
yaw,
pitch,
xMouseSmoother,
yMouseSmoother,
cobblemon$getRidingSensitivity(),
deltaTime,
yawSmoother,
pitchSmoother,
rollSmoother
);
}
@Unique
@@ -305,31 +265,33 @@ public abstract class MouseHandlerMixin {
return Math.pow(sensitivity, 3);
}
/** Moves the ride look by degrees within the ride's look limits. Same job as vanilla turn but on the look instead of the player. */
@Unique
private void cobblemon$setDriverRotations(LocalPlayer player, double cursorDeltaX, double cursorDeltaY) {
// the vanilla turn() method redone so that it uses rideRotations.
float f = (float)cursorDeltaY * 0.15F; // Note: turn() uses xRot for pitch (up/down)
float g = (float)cursorDeltaX * 0.15F; // Note: turn() uses yRot for yaw (left/right)
private void cobblemon$addLook(LocalPlayer player, float yawDelta, float pitchDelta) {
var playerRotater = (RidePassenger)player;
playerRotater.cobblemon$setRideXRot(playerRotater.cobblemon$getRideXRot() + f);
playerRotater.cobblemon$setRideYRot(playerRotater.cobblemon$getRideYRot() + g);
playerRotater.cobblemon$setRideXRot(Mth.clamp(playerRotater.cobblemon$getRideXRot(), -89.0F, 89.0F));
playerRotater.cobblemon$setRideYRot(Mth.clamp(playerRotater.cobblemon$getRideYRot(), -105.0F, 105.0F));
var lookSettings = cobblemon$getLookSettings(player);
playerRotater.cobblemon$setRideXRot(cobblemon$softClamp(playerRotater.cobblemon$getRideXRot(), pitchDelta, lookSettings.getMaxPitch()));
playerRotater.cobblemon$setRideYRot(cobblemon$softClamp(playerRotater.cobblemon$getRideYRot(), yawDelta, lookSettings.getMaxYaw()));
if (player.getVehicle() != null) {
player.getVehicle().onPassengerTurned(player);
}
}
/**
* Moves an angle by delta while keeping it inside the limit. An angle already past the limit
* (the base turning under a low carry look can put it there) isn't snapped back, it just can't go further out.
*/
@Unique
private boolean cobblemon$shouldUseAngVelSmoothing() {
var player = minecraft.player;
if (player == null) return true;
private static float cobblemon$softClamp(float angle, float delta, float limit) {
float bound = Math.max(limit, Math.abs(angle));
return Mth.clamp(angle + delta, -bound, bound);
}
var playerVehicle = player.getVehicle();
if (playerVehicle == null) return true;
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return true;
return pokemonEntity.ifRidingAvailableSupply(true, (behaviour, settings, state) -> {
return behaviour.useAngVelSmoothing(settings, state, pokemonEntity);
@Unique
private RideLookSettings cobblemon$getLookSettings(LocalPlayer player) {
if (!(player.getVehicle() instanceof PokemonEntity pokemonEntity)) return RideLookSettings.DEFAULT;
return pokemonEntity.ifRidingAvailableSupply(RideLookSettings.DEFAULT, (behaviour, settings, state) -> {
return behaviour.lookSettings(settings, state, pokemonEntity);
});
}
@@ -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)
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.orientation
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.math.QuatSpring
import com.cobblemon.mod.common.util.math.geometry.toDegrees
import com.cobblemon.mod.common.util.math.geometry.toRadians
@@ -29,7 +30,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 +103,11 @@ 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)
val pokemon = entity as? PokemonEntity
val halflife = pokemon?.ifRidingAvailableSupply(fallback = null as Float?) { behaviour, settings, state ->
behaviour.renderHalflife(settings, state, pokemon)
} ?: RENDER_SMOOTH_HALFLIFE
simpleSpringDamperQuat(renderSpring, targetQuat, halflife, TICK_DT)
val newRenderOrientation = Matrix3f()
renderSpring.quat.get(newRenderOrientation)
@@ -0,0 +1,53 @@
/*
* 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
import com.cobblemon.mod.common.api.orientation.OrientationController
import net.minecraft.util.SmoothDouble
import net.minecraft.world.phys.Vec3
/**
* One frame of steering for a rolling ride on the client that's driving it.
* The mouse values are the share the ride took in consumeLook, already through the riding
* sensitivity, invert and swap options and still in mouse units.
*/
class RideSteerInput(
/** The ride's own orientation controller */
val controller: OrientationController,
val mouseYaw: Double,
val mousePitch: Double,
val mouseXSmoother: SmoothDouble,
val mouseYSmoother: SmoothDouble,
/** Vanilla's mouse sensitivity curve, cubed */
val sensitivity: Double,
/** Seconds since the last frame */
val deltaTime: Double,
private val yawSmoother: SmoothDouble,
private val pitchSmoother: SmoothDouble,
private val rollSmoother: SmoothDouble
) {
companion object {
// Share of each push the smoothers get. Lower makes smoothed turning softer
const val SMOOTHED_SHARE = 0.5
}
/** Turns the ride by local yaw, pitch and roll degrees right away. */
fun rotate(rotation: Vec3) {
controller.rotate(rotation.x.toFloat(), rotation.y.toFloat(), rotation.z.toFloat())
}
/** Turns the ride by local yaw, pitch and roll degrees eased through vanilla's mouse smoother so it spreads over a few frames. */
fun rotateSmoothed(rotation: Vec3) {
controller.rotate(
yawSmoother.getNewDeltaValue(rotation.x * SMOOTHED_SHARE, deltaTime).toFloat(),
pitchSmoother.getNewDeltaValue(rotation.y * SMOOTHED_SHARE, deltaTime).toFloat(),
rollSmoother.getNewDeltaValue(rotation.z * SMOOTHED_SHARE, deltaTime).toFloat()
)
}
}
@@ -12,15 +12,21 @@ import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.camera.RideCameraContext
import com.cobblemon.mod.common.api.riding.camera.RideLookInput
import com.cobblemon.mod.common.api.riding.camera.RideLookTake
import com.cobblemon.mod.common.api.riding.camera.RideLookSettings
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.util.RideFrame
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
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.Quaternionf
import org.joml.Vector3f
/**
* Represents the behaviour of a Pokemon when being ridden.
@@ -41,30 +47,42 @@ interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBeha
fun pose(settings: Settings, state: State, vehicle: PokemonEntity): PoseType
/** Ride speed for vanilla's setSpeed. Only reads state, anything that changes state goes in [driverTick]. */
fun speed(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float
/**
* Runs once a tick only on the client that's steering, after [tick] and the ride's rotation.
* Whatever it works out reaches the server and other players through the riding state sync.
*/
fun driverTick(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player) {}
fun clampPassengerRotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity) {}
fun updatePassengerRotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity) {}
fun rotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity): Vec2
/**
* Ride velocity for this tick in world space blocks per tick.
* [RideFrame.yawToWorld] turns a velocity worked out along the ride's yaw into world space.
*/
fun velocity(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player, input: Vec3): Vec3
fun angRollVel(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player, deltaTime: Double): Vec3
/**
* True to have the driver's client call [velocity] a second time each tick inside travel.
* Vanilla already asks for it once in getRiddenInput and travel always asked again, so every ride's
* numbers are tuned around running twice a tick. Return false once a ride is retuned for a single run.
*/
fun velocityRunsTwicePerTick(settings: Settings, state: State, vehicle: PokemonEntity): Boolean = true
fun rotationOnMouseXY(
settings: Settings,
state: State,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3
/** Moves the ride itself this tick instead of the shared velocity and inertia. True if it did */
fun travel(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Boolean = false
/**
* Turns the ride for one frame while it rolls. Runs every frame on the client that's driving with the mouse
* share the ride took in [consumeLook]. Rotate through [RideSteerInput.controller] or its helpers.
*/
fun steer(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player, input: RideSteerInput) {}
fun canJump(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Boolean
@@ -76,8 +94,6 @@ interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBeha
fun rideFovMultiplier(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float
fun useAngVelSmoothing(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun inertia(settings: Settings, state: State, vehicle: PokemonEntity): Double
fun shouldRoll(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
@@ -111,10 +127,28 @@ interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBeha
fun applyRenderRotation(settings: Settings, state: State, vehicle: PokemonEntity, partialTicks: Float) {}
/**
* Determines if the pair of mouse inputs Pair(mouseX, mouseY) get used to modify the driverRotations or the vehicle rotations themselves.
* Driver rotations are the rotations used by passengers or a driver that is freelooking. This does not override the freelook button.
* How much of this frame's mouse look the ride takes to steer with. What it takes goes through
* [steer] and the rest moves the rider's look. Only asked for the driver and never while freelooking.
*/
fun mouseModifiesDriverRotation(settings: Settings, state: State, vehicle: PokemonEntity): Pair<Boolean, Boolean> = Pair(false, false)
fun consumeLook(settings: Settings, state: State, vehicle: PokemonEntity, input: RideLookInput): RideLookTake = RideLookTake.all(input)
/**
* Rotation the rider's camera hangs off while the ride rolls. Called every frame on the client
* and the rider's look sits on top of it. The player's disableRoll setting still applies after this.
*/
fun cameraBase(settings: Settings, state: State, vehicle: PokemonEntity, context: RideCameraContext): Quaternionf = context.defaultBase()
/** Degrees to roll the first person hands and held items */
fun firstPersonHandRoll(settings: Settings, state: State, vehicle: PokemonEntity, context: RideCameraContext): Float = 0f
/** Blocks to shift the model and its locators by once [orientation] has turned it, so a ride can keep its model on what it's standing on */
fun renderOffset(settings: Settings, state: State, vehicle: PokemonEntity, orientation: Quaternionf): Vector3f = Vector3f()
/** Seconds the drawn orientation takes to close half the gap to the real one. Null keeps the controller's default */
fun renderHalflife(settings: Settings, state: State, vehicle: PokemonEntity): Float? = null
/** Look limits and carry for riders while the ride rolls. */
fun lookSettings(settings: Settings, state: State, vehicle: PokemonEntity): RideLookSettings = RideLookSettings.DEFAULT
/**
* Used in MoLang functions for querying states for animations or other magical MoLang effects.
@@ -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())
}
@@ -15,8 +15,13 @@ 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.orientation.OrientationController.Companion.FORWARDS
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.camera.RideCameraContext
import com.cobblemon.mod.common.api.riding.camera.RideLookInput
import com.cobblemon.mod.common.api.riding.camera.RideLookTake
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
@@ -24,7 +29,9 @@ 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.FloatSpring
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.math.simpleSpringDamper
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
@@ -34,6 +41,8 @@ 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.Matrix3f
import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.*
@@ -42,6 +51,28 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
val KEY = cobblemonResource("air/bird")
private const val FLY_POSE_SPEED = 0.1
// At or below this the bird hovers instead of flying
private const val HOVER_SPEED = 0.1
// Within this many degrees of straight up or down wings level means nothing so the camera holds its roll
private const val VERTICAL_DEADZONE_DEGREES = 15f
private val MIN_LEVEL_UP_LENGTH_SQ = sin(VERTICAL_DEADZONE_DEGREES.toRadians()).pow(2)
// Below this speed the bird is too slow to bank so its wings always level out
private const val NO_BANK_SPEED = 0.2
// Degrees before the max roll where rolling further starts easing off. Raise for a softer stop
private const val ROLL_CAP_BAND_DEGREES = 20f
// Degrees past the turn roll limit over which mouse pitch hands over from world up and down to the bird's own pitch
private const val PITCH_BLEND_BAND_DEGREES = 15f
// Pitch where mouse pitch starts handing back to the bird's own pitch. By straight up or down it's all the bird's
// own so a loop carries on over the top instead of world up pulling it back
private const val WORLD_PITCH_FADE_START = 60f
// Fastest the wings level out by themselves in degrees per second
private const val MAX_ROLL_SETTLE_RATE = 45f
// Within this many degrees of level the settle rate eases off so it lands on level instead of overshooting
private const val ROLL_SETTLE_ARRIVAL_DEGREES = 35f
// How fast the settle rate can change in degrees per second squared. Same as the old per frame cap at 60 fps
private const val ROLL_SETTLE_ACCEL = 67.5f
// Keeps the turn roll limit from dividing by zero
private const val MIN_TURN_ROLL_LIMIT = 1f
private const val GLIDE_ENTRY_SPEED = 0.19
private const val GLIDE_EXIT_SPEED = 0.13
}
@@ -84,25 +115,17 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
) {
if(vehicle.level().isClientSide) {
tickStamina(settings, state, vehicle, driver)
checkUpsideDown(state, vehicle, driver)
checkStaminaDive(state)
}
}
private fun checkUpsideDown(
state: BirdState,
vehicle: PokemonEntity,
driver: Player,
) {
// Also checks stamina
val controller = (vehicle as OrientationControllable).orientationController
if (cos(controller.roll.toRadians()) < 0.0) {
val upsideDownRate = 1.0 / (2.0 * 20.0) // 2 seconds to max out upsideDown force
state.currUpsideDownForce.set( min(1.0, state.currUpsideDownForce.get() + upsideDownRate * abs(cos(controller.roll.toRadians()))))
} else if (state.stamina.get() == 0.0f) { // checks stamina depletion and forces dive if depleted
val upsideDownRate = 1.0 / (2.0 * 20.0) // 1 seconds to max out
state.currUpsideDownForce.set( min(1.0, state.currUpsideDownForce.get() + upsideDownRate))
} else { // Its right side up or has stamina so reset it
state.currUpsideDownForce.set(0.0)
// Out of stamina the bird can't hold itself up so it builds up a dive
private fun checkStaminaDive(state: BirdState) {
if (state.stamina.get() == 0.0f) {
val diveRate = 1.0 / (2.0 * 20.0) // 2 seconds to max out
state.staminaDiveForce.set(min(1.0, state.staminaDiveForce.get() + diveRate))
} else {
state.staminaDiveForce.set(0.0)
}
}
@@ -161,8 +184,8 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
//Perform ride velocity update
calculateRideSpaceVel(settings, state, vehicle, driver)
// The downward force used to encourage players to stop flying upside down.
val extraDownwardForce = state.currUpsideDownForce.get().pow(2) * -0.3 // 6 blocks a second downward
// Pulls the bird down once it's out of stamina
val extraDownwardForce = state.staminaDiveForce.get().pow(2) * -0.3 // 6 blocks a second downward
// Convert the local velocity vector into a world vector
val localVelVec = Vector3f(
@@ -170,8 +193,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
(state.rideVelocity.get().y).toFloat(),
state.rideVelocity.get().z.toFloat() * -1.0f // Flip the z axis to make this left handed
)
// Unrotate preemptively as this vector gets rotatee later down the line in MC logic.
var worldVelVec = localVelVec.mul(controller.orientation).toVec3d().yRot(vehicle.yRot.toRadians())
var worldVelVec = localVelVec.mul(controller.orientation).toVec3d()
worldVelVec = worldVelVec.add(0.0, extraDownwardForce, 0.0) // Add the stamina depletion force to bring the ride down.
return worldVelVec
@@ -282,19 +304,23 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
state.rideVelocity.set(newVelocity)
}
override fun angRollVel(
private fun rotateOverTime(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
if (vehicle !is OrientationControllable) return Vec3.ZERO
) {
if (vehicle !is OrientationControllable) return
val controller = (vehicle as OrientationControllable).orientationController
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalBird.handlingExpr!!)
val yawDeltaDeg = deltaTime * handling * sin(Math.toRadians(controller.roll.toDouble()))
// Turn rate tops out at the turn roll limit. Banking further is for show and doesn't turn any harder
val turnRollLimit = frameTuning(settings, state, vehicle).turnRollLimit.coerceIn(MIN_TURN_ROLL_LIMIT, 90f)
val bankFromLevel = if (abs(controller.roll) > 90f) 180f - abs(controller.roll) else abs(controller.roll)
val turnShare = sin(min(bankFromLevel, turnRollLimit).toRadians()) / sin(turnRollLimit.toRadians()) * controller.roll.sign
val yawDeltaDeg = deltaTime * handling * turnShare
val trueYawDelt = yawDeltaDeg * abs(cos(Math.toRadians(controller.pitch.toDouble()))).coerceIn(-1.0, 1.0)
// Dampen yaw when upside down
@@ -309,8 +335,6 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
controller.applyGlobalPitch(pCorr)
controller.rotateRoll(rCorr)
//yaw, pitch, roll
return Vec3(0.0, 0.0, 0.0)
}
private fun correctOrientation(
@@ -320,45 +344,28 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
controller: OrientationController,
deltaTime: Double
) {
// Calculate correcting roll force.
val rollCorrectionTimer = vehicle.runtime.resolveDouble(settings.timeToRollCorrect ?: globalBird.timeToRollCorrect!!)
// If the ride is moving then have the correction rate be an equal proportion of:
// - how rolled are you
// - how long has it been since last mouse input (higher is stronger correction)
// Also the correction is proportional as a whole to how NOT pitched are you so you don't spin endlessly when looking up or down
val howRolledAmI = sqrt(abs((cos(controller.roll.toRadians()) - 1.0f) * 0.5f)) // sqrt so it trends towards one pretty quickly
val maxRollCorrectionRate =
if (state.rideVelocity.get().length() < 0.2) 45.0f
else 45.0f * cos(controller.pitch.toRadians()).pow(2) * (
howRolledAmI*1.0f +
(state.noInputTimeRoll.get() - rollCorrectionTimer).coerceIn(0.0, 1.0).pow(2).toFloat()*0.5f
)
val maxRollForce = maxRollCorrectionRate / 40.0f
val rollArrivalDeg = 35.0f
val desiredRoll = 0.0f
// Calculate the signed error (how far and in what direction)
val rollError = Mth.wrapDegrees(desiredRoll.angleDifference(controller.roll))
// Use the absolute error to calculate the magnitude of the influence
val influenceMagnitude = (min(rollArrivalDeg, abs(rollError)) / rollArrivalDeg)
val desiredRollForce = maxRollCorrectionRate * influenceMagnitude * -rollError.sign
var steeredRollForce = desiredRollForce - state.currRollCorrectionForce.get()
// Ensure the 'steering' correction force doesn't exceed our maxForce
if (abs(steeredRollForce) > maxRollForce) {
steeredRollForce = steeredRollForce.sign * maxRollForce
// Wings only level out by themselves when the rider has let go and they're nearly level already,
// or when the bird's too slow to bank at all. A bank the rider holds stays where they put it
val tuning = frameTuning(settings, state, vehicle)
val idle = state.noInputTimeRoll.get() >= tuning.rollSettleDelay
val settling = state.rideVelocity.get().length() < NO_BANK_SPEED || (idle && abs(controller.roll) < tuning.rollSettleAngle)
val rollError = Mth.wrapDegrees(0.0f.angleDifference(controller.roll))
val desiredRollRate = if (settling) {
MAX_ROLL_SETTLE_RATE * (min(ROLL_SETTLE_ARRIVAL_DEGREES, abs(rollError)) / ROLL_SETTLE_ARRIVAL_DEGREES) * -rollError.sign
} else {
0f
}
// Apply the 'steered' force (its really not a steering force I need a better name)
state.currRollCorrectionForce.set(
state.currRollCorrectionForce.get() + steeredRollForce
)
val maxRateChange = ROLL_SETTLE_ACCEL * deltaTime
val currentRollRate = state.currRollCorrectionForce.get()
state.currRollCorrectionForce.set(currentRollRate + (desiredRollRate - currentRollRate).coerceIn(-maxRateChange, maxRateChange))
//Calculate correcting pitch force.
val maxPitchCorrectionRate = if (state.currUpsideDownForce.get() != 0.0) 15.0f * state.currUpsideDownForce.get().toFloat() else 30.0f
val maxPitchCorrectionRate = if (state.staminaDiveForce.get() != 0.0) 15.0f * state.staminaDiveForce.get().toFloat() else 30.0f
val maxPitchForce = maxPitchCorrectionRate / 40.0f
val pitchArrivalDeg = 35.0f
val desiredPitch = if (state.currUpsideDownForce.get() != 0.0) 90.0f else 0.0f
val desiredPitch = if (state.staminaDiveForce.get() != 0.0) 90.0f else 0.0f
if((state.rideVelocity.get().length() < 0.1) || state.currUpsideDownForce.get() != 0.0) {
if((state.rideVelocity.get().length() < 0.1) || state.staminaDiveForce.get() != 0.0) {
val pitchErrror = Mth.wrapDegrees(desiredPitch.angleDifference(controller.pitch))
val arrivalInfluence = (min(pitchArrivalDeg,abs(pitchErrror)) / pitchArrivalDeg)
val desiredPitchForce = maxPitchCorrectionRate * arrivalInfluence * -pitchErrror.sign
@@ -381,7 +388,18 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
}
}
override fun rotationOnMouseXY(
override fun steer(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity,
driver: Player,
input: RideSteerInput
) {
input.rotate(mouseRotation(settings, state, vehicle, driver, input.mousePitch, input.mouseYaw, input.mouseYSmoother, input.mouseXSmoother, input.sensitivity, input.deltaTime))
rotateOverTime(settings, state, vehicle, driver, input.deltaTime)
}
private fun mouseRotation(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity,
@@ -394,15 +412,10 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
// Begin the roll correction time counter if not enough mouse input
// is detected
if (mouseX < 1 && abs(mouseY) < 1) {
// Time since the rider last moved the mouse, wings only settle once it passes the settle delay
if (abs(mouseX) < 1 && abs(mouseY) < 1) {
state.noInputTimeRoll.set(state.noInputTimeRoll.get() + deltaTime)
} else if (cos(controller.roll.toRadians()) < 0.1) {
state.noInputTimeRoll.set(state.noInputTimeRoll.get() + deltaTime * 0.5)
}
else {
} else {
state.noInputTimeRoll.set(0.0)
}
@@ -443,46 +456,42 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBird.speedExpr!!) / 20.0
val mouseInputMult = vehicle.runtime.resolveDouble(settings.mouseInputMult ?: globalBird.mouseInputMult!!)
// Applied to mouseX to prevent turning upside down when trying to roll hard.
val rollDegreeDebuff = if(mouseX.sign.toFloat() == controller.roll.sign) 1 - 0.9f*sqrt(abs(sin(controller.roll.toRadians())))*sqrt(abs(cos(controller.pitch))) else 1.0f
val tuning = frameTuning(settings, state, vehicle)
//Smooth out mouse input.
val smoothingSpeed = 2.0
val mouseXc = (mouseX).coerceIn(-45.0, 45.0) * mouseInputMult * rollDegreeDebuff
// When upside down apply a pitching force to nudge the player towards a dive. This reduces the 'weirdness'
// of being upside down
val rolledOverPitchCorrection = -8*sqrt(abs(min(cos(controller.roll.toRadians()),0.0f))) * (cos(controller.pitch.toRadians())).pow(2)
val mouseYc = ((mouseY).coerceIn(-45.0, 45.0) * mouseInputMult) + rolledOverPitchCorrection
val mouseXc = (mouseX).coerceIn(-45.0, 45.0) * mouseInputMult
val mouseYc = (mouseY).coerceIn(-45.0, 45.0) * mouseInputMult
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1, deltaTime * smoothingSpeed)
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1, deltaTime * smoothingSpeed)
val currSpeed = state.rideVelocity.get().length()
val currHorzSpeed = state.rideVelocity.get().horizontalDistance()
val hoverSpeed = 0.1
val hoverSpeed = HOVER_SPEED
val pitchDamping = abs(cos(controller.pitch.toRadians())).pow(2) // Helper value used when wanting to discard the influence of another value at steep pitches
// Yaw locally when at or below hoverSpeed but not when pitched up or rolled over
val globalYaw = xInput * (1 - sqrt(RidingBehaviour.scaleToRange(currHorzSpeed, hoverSpeed, topSpeed))).coerceIn(0.0, 1.0)
controller.applyGlobalYaw(globalYaw.toFloat())
// Pitch locally up or down depending upon a number of factors:
// - Reduce pitch substantially when rolled and in a steep yaw. This prevents the player from ignoring a slow handling stat
// - Do not pitch at all when at or below hover speed
// - pitch faster at higher speeds
val p = 1.0 - vehicle.runtime.resolveDouble(settings.horizontalPitchExpr ?: globalBird.horizontalPitchExpr!!)
var localPitch = yInput * (1 - abs(sin(controller.roll.toRadians())).pow(2) * p * hypot(controller.forwardVector.x.toDouble(), controller.forwardVector.z.toDouble()).toFloat())
localPitch *= if (currSpeed > hoverSpeed || yInput > 0.0) RidingBehaviour.scaleToRange(currSpeed, 0.0, topSpeed).coerceIn(0.2, 1.0)
// No pitching at hover speed and faster pitching the faster the bird goes
val pitchInput = yInput * if (currSpeed > hoverSpeed || yInput > 0.0) RidingBehaviour.scaleToRange(currSpeed, 0.0, topSpeed).coerceIn(0.2, 1.0)
else 0.0
// Roll less when slow and not at all when at hoverSpeed or lower.
// Apply a roll righting force when trying to pitch hard while rolled sideways. This nudges the player towards
// pitching globally
val pitchInfluencedRollCorrection = 0.0
val howRolledAmI = (( cos(controller.roll.toRadians()) - 1).coerceIn(-1.0f, 0.0f) * -2) //Coerce into how much roll you want to let in in the deadzone
// Inside a normal bank mouse pitch is world up and down so pulling up climbs instead of tightening the turn.
// Past the turn roll limit, or pointing near straight up or down, it hands over to the bird's own pitch
val pastTurnRoll = smoothstep01((abs(controller.roll) - tuning.turnRollLimit) / PITCH_BLEND_BAND_DEGREES)
val nearVertical = smoothstep01((abs(controller.pitch) - WORLD_PITCH_FADE_START) / (90f - WORLD_PITCH_FADE_START))
val localShare = 1f - (1f - pastTurnRoll) * (1f - nearVertical)
controller.applyGlobalPitch((pitchInput * (1f - localShare)).toFloat())
// Pulling with the bird's own pitch while rolled turns hard so it's held back by handling there
val p = 1.0 - vehicle.runtime.resolveDouble(settings.horizontalPitchExpr ?: globalBird.horizontalPitchExpr!!)
val localPitch = pitchInput * localShare * (1 - abs(sin(controller.roll.toRadians())).pow(2) * p * hypot(controller.forwardVector.x.toDouble(), controller.forwardVector.z.toDouble()).toFloat())
// Roll less when slow and not at all when at hoverSpeed or lower
val isInputtingTowardsRoll = if ( xInput.sign == controller.roll.sign.toDouble()) 1.0 else 0.0
val inputInRolledDir = if (isInputtingTowardsRoll == 1.0) 1.0 else -cos(controller.roll.toRadians()).coerceIn(-1.0f, 0.0f).toDouble()
val rollDampen = (1 - howRolledAmI.coerceIn(0.0f, 0.7f) * pitchDamping * inputInRolledDir)
val rollForce = if (currHorzSpeed > hoverSpeed) xInput * sqrt(abs(RidingBehaviour.scaleToRange(currHorzSpeed, hoverSpeed, topSpeed)).coerceIn(0.0,1.0)) * rollDampen + pitchInfluencedRollCorrection
else 0.0
val rollInput = xInput * sqrt(abs(RidingBehaviour.scaleToRange(currHorzSpeed, hoverSpeed, topSpeed)).coerceIn(0.0,1.0))
val rollForce = if (currHorzSpeed > hoverSpeed) capRoll(rollInput, controller.roll, tuning.maxRoll) else 0.0
// When already rolled significantly part of the roll force should turn to local yaw force
val localYawForce = rollForce * abs(sin(controller.roll.toRadians())).pow(2) * pitchDamping * isInputtingTowardsRoll
@@ -546,14 +555,6 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
return 1.0f + normalizedGlideSpeed.pow(2).toFloat() * 0.2f
}
override fun useAngVelSmoothing(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun inertia(settings: BirdSettings, state: BirdState, vehicle: PokemonEntity): Double {
return 0.5
}
@@ -607,6 +608,139 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
override fun createDefaultState(settings: BirdSettings) = BirdState()
/**
* Keeps the camera level until the bird rolls past the engage angle, then eases it into the bird's roll.
* It levels out again once the bird comes back under the release angle. Yaw and pitch always follow the bird.
*/
override fun cameraBase(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity,
context: RideCameraContext
): Quaternionf {
val ride = context.defaultBase()
val tuning = frameTuning(settings, state, vehicle)
val roll = context.data as? BirdCameraRoll ?: BirdCameraRoll().also { context.data = it }
val forward = FORWARDS.rotate(ride, Vector3f())
val up = UP.rotate(ride, Vector3f())
// Up with wings level. Pointing near straight up or down it flips sides so it's only used away from vertical
val levelUp = Vector3f(0f, 1f, 0f).sub(Vector3f(forward).mul(forward.y))
val hasLevel = levelUp.lengthSquared() > MIN_LEVEL_UP_LENGTH_SQ
if (hasLevel) {
levelUp.normalize()
val rideRoll = signedAngle(levelUp, up, forward)
if (roll.engaged && abs(rideRoll) < tuning.rollRelease) {
roll.engaged = false
} else if (!roll.engaged && abs(rideRoll) > tuning.rollEngage) {
roll.engaged = true
}
}
// Camera up lives in world space so the level up flipping over the top of a loop can't drag the camera round
val cameraUp = roll.cameraUp?.let { flatten(it, forward) } ?: Vector3f(up)
val target = when {
roll.engaged -> up
hasLevel -> levelUp
else -> Vector3f(cameraUp)
}
if (roll.cameraUp == null) {
cameraUp.set(target)
} else if (context.frameSeconds > 0f) {
// Spring the leftover angle so it builds up speed and settles instead of lunging at the start
roll.spring.value = signedAngle(target, cameraUp, forward)
simpleSpringDamper(roll.spring, 0f, tuning.rollHalflife, context.frameSeconds)
cameraUp.set(target).rotateAxis(roll.spring.value * Mth.DEG_TO_RAD, forward.x, forward.y, forward.z)
}
roll.cameraUp = cameraUp
return levelFrame(forward, cameraUp)
}
override fun firstPersonHandRoll(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity,
context: RideCameraContext
): Float {
return context.rideRollAroundView() * frameTuning(settings, state, vehicle).handRollShare
}
// Hovering the rider can look up and down freely and around up to the hover limit. Only pushing past it turns the bird
override fun consumeLook(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity,
input: RideLookInput
): RideLookTake {
// Only forward speed counts so rising or sinking on the spot is still hovering
if (state.rideVelocity.get().horizontalDistance() > HOVER_SPEED) return RideLookTake.all(input)
val limit = frameTuning(settings, state, vehicle).hoverLookYawLimit
// Never snap a look that's already past the limit, it just can't be pushed further out
val bound = max(limit, abs(input.yaw))
val target = input.yaw + input.yawDelta
val pushed = target - target.coerceIn(-bound, bound)
return RideLookTake(pushed, 0f, recenterYaw = false, recenterPitch = false)
}
/** Knobs the bird reads every frame, resolved at most once a tick so MoLang doesn't run per frame. */
private fun frameTuning(settings: BirdSettings, state: BirdState, vehicle: PokemonEntity): BirdTuning {
val cached = state.frameTuning.get()
if (cached != null && cached.tick == vehicle.tickCount) return cached
val runtime = vehicle.runtime
return BirdTuning(
tick = vehicle.tickCount,
rollEngage = runtime.resolveFloat(settings.cameraRollEngage ?: globalBird.cameraRollEngage!!),
rollRelease = runtime.resolveFloat(settings.cameraRollRelease ?: globalBird.cameraRollRelease!!),
rollHalflife = runtime.resolveFloat(settings.cameraRollHalflife ?: globalBird.cameraRollHalflife!!),
hoverLookYawLimit = runtime.resolveFloat(settings.hoverLookYawLimit ?: globalBird.hoverLookYawLimit!!),
turnRollLimit = runtime.resolveFloat(settings.turnRollLimit ?: globalBird.turnRollLimit!!),
maxRoll = runtime.resolveFloat(settings.maxRoll ?: globalBird.maxRoll!!),
rollSettleAngle = runtime.resolveFloat(settings.rollSettleAngle ?: globalBird.rollSettleAngle!!),
rollSettleDelay = runtime.resolveFloat(settings.timeToRollCorrect ?: globalBird.timeToRollCorrect!!),
handRollShare = runtime.resolveFloat(settings.handRollShare ?: globalBird.handRollShare!!)
).also { state.frameTuning.set(it) }
}
/** Smoothstep of [t] clamped to 0 to 1. */
private fun smoothstep01(t: Float): Float {
val x = t.coerceIn(0f, 1f)
return x * x * (3f - 2f * x)
}
/**
* Eases roll that would take the bird further from level to a stop at [maxRoll] degrees.
* Rolling back toward level is never held back.
*/
private fun capRoll(rollDelta: Double, roll: Float, maxRoll: Float): Double {
if (rollDelta.sign != roll.sign.toDouble()) return rollDelta
val room = (maxRoll - abs(roll)).coerceAtLeast(0f)
val ease = (room / ROLL_CAP_BAND_DEGREES).coerceIn(0f, 1f)
val eased = rollDelta * ease * ease * (3f - 2f * ease)
// Never step past the cap in one frame
return eased.coerceIn(-room.toDouble(), room.toDouble())
}
/** Signed degrees from [from] to [to] turning around [axis]. Both have to be perpendicular to it. */
private fun signedAngle(from: Vector3f, to: Vector3f, axis: Vector3f): Float {
return atan2(Vector3f(from).cross(to).dot(axis), from.dot(to)) * Mth.RAD_TO_DEG
}
/** [vector] with the part along [axis] removed, or null if nothing is left. */
private fun flatten(vector: Vector3f, axis: Vector3f): Vector3f? {
val flat = Vector3f(vector).sub(Vector3f(axis).mul(vector.dot(axis)))
return if (flat.lengthSquared() > 0f) flat.normalize() else null
}
/** Rotation whose forward is [forward] and up is [up]. They have to be unit length and perpendicular. */
private fun levelFrame(forward: Vector3f, up: Vector3f): Quaternionf {
val right = Vector3f(forward).cross(up)
val back = Vector3f(forward).negate()
return Quaternionf().setFromNormalized(Matrix3f(right, up, back))
}
override fun asMoLangValue(
settings: BirdSettings,
state: BirdState,
@@ -659,6 +793,39 @@ class BirdSettings : RidingBehaviourSettings {
// This is coerced between 1 and 0 or things would get weird.
var stamDrainPerRateDive: Expression? = null
private set
// Degrees of roll before the camera starts rolling with the bird
var cameraRollEngage: Expression? = null
private set
// Degrees the bird has to roll back under before the camera levels out. Keep it below engage so it doesn't flicker
var cameraRollRelease: Expression? = null
private set
// Seconds the camera roll spring takes to settle. Raise for a lazier camera
var cameraRollHalflife: Expression? = null
private set
// Degrees either side the rider can look while hovering before pushing further turns the bird
var hoverLookYawLimit: Expression? = null
private set
// Roll in degrees where turning tops out and mouse pitch stops being world up and down
var turnRollLimit: Expression? = null
private set
// Furthest the bird can roll from level in degrees. Keep cameraRollEngage just above it so the camera
// stays level through normal flying and only rolls in loops and flips. 180 lets it roll all the way over
var maxRoll: Expression? = null
private set
// Within this many degrees of level the wings settle back to level once the rider lets go of the mouse
var rollSettleAngle: Expression? = null
private set
// Share of the bird's roll the first person hands lean with, 0 to 1
var handRollShare: Expression? = null
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
@@ -677,6 +844,14 @@ class BirdSettings : RidingBehaviourSettings {
buffer.writeNullableExpression(stamDrainPercRateGlide)
buffer.writeNullableExpression(stamDrainPerRateDive)
buffer.writeNullableExpression(timeToRollCorrect)
buffer.writeNullableExpression(cameraRollEngage)
buffer.writeNullableExpression(cameraRollRelease)
buffer.writeNullableExpression(cameraRollHalflife)
buffer.writeNullableExpression(hoverLookYawLimit)
buffer.writeNullableExpression(turnRollLimit)
buffer.writeNullableExpression(maxRoll)
buffer.writeNullableExpression(rollSettleAngle)
buffer.writeNullableExpression(handRollShare)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
@@ -695,6 +870,14 @@ class BirdSettings : RidingBehaviourSettings {
stamDrainPercRateGlide = buffer.readNullableExpression()
stamDrainPerRateDive = buffer.readNullableExpression()
timeToRollCorrect = buffer.readNullableExpression()
cameraRollEngage = buffer.readNullableExpression()
cameraRollRelease = buffer.readNullableExpression()
cameraRollHalflife = buffer.readNullableExpression()
hoverLookYawLimit = buffer.readNullableExpression()
turnRollLimit = buffer.readNullableExpression()
maxRoll = buffer.readNullableExpression()
rollSettleAngle = buffer.readNullableExpression()
handRollShare = buffer.readNullableExpression()
}
}
@@ -705,7 +888,9 @@ class BirdState : RidingBehaviourState() {
var currPitchCorrectionForce = ridingState(0.0, Side.CLIENT)
var noInputTimeRoll = ridingState(0.0, Side.CLIENT)
var noInputTimePitch = ridingState(0.0, Side.CLIENT)
var currUpsideDownForce = ridingState(0.0, Side.CLIENT)
var staminaDiveForce = ridingState(0.0, Side.CLIENT)
// Client only cache of the knobs read every frame, never synced
var frameTuning = ridingState<BirdTuning?>(null, Side.CLIENT)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
@@ -725,6 +910,7 @@ class BirdState : RidingBehaviourState() {
currPitchCorrectionForce.set(0.0, forced = true)
noInputTimeRoll.set(0.0, forced = true)
noInputTimePitch.set(0.0, forced = true)
frameTuning.set(null, forced = true)
}
override fun toString(): String {
@@ -761,3 +947,26 @@ fun Float.angleDifference(other: Float): Float {
while (diff > 180F) diff -= 360F
return diff
}
/** Bird knobs resolved for one tick. */
data class BirdTuning(
val tick: Int,
val rollEngage: Float,
val rollRelease: Float,
val rollHalflife: Float,
val hoverLookYawLimit: Float,
val turnRollLimit: Float,
val maxRoll: Float,
val rollSettleAngle: Float,
val rollSettleDelay: Float,
val handRollShare: Float
)
/** Bird's camera roll smoothing, kept in the camera context between frames. */
class BirdCameraRoll {
var engaged = false
// World space up the camera had last frame, null until the first frame
var cameraUp: Vector3f? = null
// Degrees left between the camera and where it's heading, plus how fast that's closing
val spring = FloatSpring()
}
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
@@ -19,11 +18,11 @@ 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.RideFrame
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.RegistryFriendlyByteBuf
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
@@ -67,7 +66,18 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
return Vec2(driver.xRot, driver.yRot)
}
// This ride works along its yaw so its velocity gets turned into world space here
override fun velocity(
settings: GliderSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
}
private fun localVelocity(
settings: GliderSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
@@ -80,34 +90,6 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
return Vec3(xVector, yVector, zVector)
}
override fun angRollVel(
settings: GliderSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: GliderSettings,
state: RidingBehaviourState,
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.
return Vec3(0.0, mouseY, mouseX)
}
override fun canJump(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity, driver: Player): Boolean {
return false
}
@@ -149,10 +131,6 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
return 1.0f
}
override fun useAngVelSmoothing(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun inertia(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Double {
return 0.5
}
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RideSteerInput
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
@@ -20,6 +21,7 @@ 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.RideFrame
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
@@ -91,12 +93,23 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
// This ride works along its yaw so its velocity gets turned into world space here
override fun velocity(
settings: HelicopterSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
}
private fun localVelocity(
settings: HelicopterSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val runtime = vehicle.runtime
@@ -120,7 +133,7 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
return Vec3(xVel, yVel, zVel)
}
override fun angRollVel(
private fun rotationOverTime(
settings: HelicopterSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
@@ -175,7 +188,18 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
return Vec3(0.0, 0.0, 0.0)
}
override fun rotationOnMouseXY(
override fun steer(
settings: HelicopterSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
input: RideSteerInput
) {
input.rotate(mouseRotation(settings, state, vehicle, driver, input.mousePitch, input.mouseYaw, input.mouseYSmoother, input.mouseXSmoother, input.sensitivity, input.deltaTime))
input.rotateSmoothed(rotationOverTime(settings, state, vehicle, driver, input.deltaTime))
}
private fun mouseRotation(
settings: HelicopterSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
@@ -241,10 +265,6 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
return 1.0f
}
override fun useAngVelSmoothing(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return true
}
override fun inertia(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Double {
return 0.1
}
@@ -18,6 +18,7 @@ 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.RideFrame
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
@@ -26,7 +27,6 @@ import net.minecraft.client.Minecraft
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.player.Player
import net.minecraft.world.level.ClipContext
@@ -190,12 +190,23 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
return rotAmount
}
// This ride works along its yaw so its velocity gets turned into world space here
override fun velocity(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
}
private fun localVelocity(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val retVel = calculateRideSpaceVel(settings, state, vehicle, driver)
state.rideVelocity.set(retVel)
@@ -283,31 +294,6 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
return newVelocity
}
override fun angRollVel(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun canJump(
settings: HoverSettings,
state: HoverState,
@@ -355,14 +341,6 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
return if (state.isBoosting.get()) 1.1f else 1.0f
}
override fun useAngVelSmoothing(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun inertia(
settings: HoverSettings,
state: HoverState,
@@ -15,6 +15,7 @@ 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.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RideSteerInput
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
@@ -28,7 +29,6 @@ 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.math.geometry.toRadians
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveDouble
@@ -176,7 +176,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
(state.rideVelocity.get().y).toFloat(),
state.rideVelocity.get().z.toFloat() * -1.0f // Flip the z axis to make this left handed? orr.. right handed? idk, flip it though
)
var worldVelVec = localVelVec.mul(controller.orientation).toVec3d().yRot(vehicle.yRot.toRadians()) // Unrotate preemptively as this vector gets rotate later down the line in MC logic.
var worldVelVec = localVelVec.mul(controller.orientation).toVec3d()
worldVelVec = Vec3(
worldVelVec.x,
worldVelVec.y * (1 - (state.noStamTickCnt.get() / penaltyDeccel).coerceIn(0.0f, 1.0f)), // Dampen the upwards movement depending on stamina depletion time
@@ -266,7 +266,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
}
override fun angRollVel(
private fun rotationOverTime(
settings: JetSettings,
state: JetState,
vehicle: PokemonEntity,
@@ -292,7 +292,18 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
return Vec3(yawForce, 0.0, 0.0)
}
override fun rotationOnMouseXY(
override fun steer(
settings: JetSettings,
state: JetState,
vehicle: PokemonEntity,
driver: Player,
input: RideSteerInput
) {
input.rotate(mouseRotation(settings, state, vehicle, driver, input.mousePitch, input.mouseYaw, input.mouseYSmoother, input.mouseXSmoother, input.sensitivity, input.deltaTime))
input.rotateSmoothed(rotationOverTime(settings, state, vehicle, driver, input.deltaTime))
}
private fun mouseRotation(
settings: JetSettings,
state: JetState,
vehicle: PokemonEntity,
@@ -390,10 +401,6 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
}
}
override fun useAngVelSmoothing(settings: JetSettings, state: JetState, vehicle: PokemonEntity): Boolean {
return true
}
override fun inertia(settings: JetSettings, state: JetState, vehicle: PokemonEntity): Double {
return 1.0
}
@@ -19,6 +19,7 @@ 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.RideFrame
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
@@ -27,7 +28,6 @@ import net.minecraft.client.Minecraft
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.player.Player
import net.minecraft.world.phys.Vec2
@@ -75,15 +75,19 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, 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) {
handleBoosting(settings, state, vehicle, driver)
tickStamina(settings, state, vehicle, driver)
}
return state.rideVelocity.get().length().toFloat()
}
override fun driverTick(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
) {
handleBoosting(settings, state, vehicle, driver)
tickStamina(settings, state, vehicle, driver)
}
fun handleBoosting(
settings: RocketSettings,
state: RocketState,
@@ -208,12 +212,23 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
}
// This ride works along its yaw so its velocity gets turned into world space here
override fun velocity(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
}
private fun localVelocity(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val retVel = calculateRideSpaceVel(settings, state, vehicle, driver)
state.rideVelocity.set(retVel)
@@ -318,37 +333,6 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
return newVelocity
}
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,
@@ -403,14 +387,6 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
}
}
override fun useAngVelSmoothing(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun inertia(
settings: RocketSettings,
state: RocketState,
@@ -11,6 +11,10 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.composite
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.strategies.CompositeRidingStrategies
import com.cobblemon.mod.common.api.riding.camera.RideCameraContext
import com.cobblemon.mod.common.api.riding.camera.RideLookInput
import com.cobblemon.mod.common.api.riding.camera.RideLookTake
import com.cobblemon.mod.common.api.riding.camera.RideLookSettings
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
@@ -22,11 +26,12 @@ import com.cobblemon.mod.common.util.writeRidingStats
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.Quaternionf
import org.joml.Vector3f
class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
companion object {
@@ -170,16 +175,6 @@ class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
}
}
override fun useAngVelSmoothing(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.useAngVelSmoothing(behaviourSettings, behaviourState, vehicle)
}
}
override fun rideFovMultiplier(
settings: CompositeSettings,
state: CompositeState,
@@ -236,46 +231,6 @@ class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
}
}
override fun rotationOnMouseXY(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.rotationOnMouseXY(
behaviourSettings,
behaviourState,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
}
}
override fun angRollVel(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.angRollVel(behaviourSettings, behaviourState, vehicle, driver, deltaTime)
}
}
override fun velocity(
settings: CompositeSettings,
state: CompositeState,
@@ -288,6 +243,39 @@ class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
}
}
override fun steer(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player,
input: RideSteerInput
) {
chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.steer(behaviourSettings, behaviourState, vehicle, driver, input)
}
}
override fun travel(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.travel(behaviourSettings, behaviourState, vehicle, driver)
}
}
override fun velocityRunsTwicePerTick(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.velocityRunsTwicePerTick(behaviourSettings, behaviourState, vehicle)
}
}
override fun rotation(
settings: CompositeSettings,
state: CompositeState,
@@ -310,6 +298,17 @@ class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
}
}
override fun driverTick(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player
) {
chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.driverTick(behaviourSettings, behaviourState, vehicle, driver)
}
}
override fun pose(
settings: CompositeSettings,
state: CompositeState,
@@ -361,6 +360,70 @@ class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
behaviour.damageOnCollision(behaviourSettings, behaviourState, vehicle, impactVec)
}
}
override fun cameraBase(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
context: RideCameraContext
): Quaternionf {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.cameraBase(behaviourSettings, behaviourState, vehicle, context)
}
}
override fun firstPersonHandRoll(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
context: RideCameraContext
): Float {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.firstPersonHandRoll(behaviourSettings, behaviourState, vehicle, context)
}
}
override fun renderOffset(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
orientation: Quaternionf
): Vector3f {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.renderOffset(behaviourSettings, behaviourState, vehicle, orientation)
}
}
override fun renderHalflife(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): Float? {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.renderHalflife(behaviourSettings, behaviourState, vehicle)
}
}
override fun lookSettings(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): RideLookSettings {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.lookSettings(behaviourSettings, behaviourState, vehicle)
}
}
override fun consumeLook(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
input: RideLookInput
): RideLookTake {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.consumeLook(behaviourSettings, behaviourState, vehicle, input)
}
}
}
open class CompositeSettings : RidingBehaviourSettings {
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviours
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeState
import com.cobblemon.mod.common.api.riding.util.RideFrame
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.world.entity.player.Player
@@ -63,7 +64,8 @@ object RunStrategy : CompositeRidingStrategy<CompositeSettings> {
if (state.activeBehaviour.get() == alternativeSettings.key) return false
if (state.lastTransition.get() + 20 >= entity.level().gameTime) return false
val alternativeBehaviour = RidingBehaviours.get(alternativeSettings.key)
return driver.isSprinting && input.z > 0.5 && alternativeBehaviour.isActive(alternativeSettings, state.alternateBehaviourState, entity)
// Ride velocity is world space so check how much of it is forward
return driver.isSprinting && RideFrame.worldToYaw(input, entity.yRot).z > 0.5 && alternativeBehaviour.isActive(alternativeSettings, state.alternateBehaviourState, entity)
}
}
@@ -22,6 +22,7 @@ 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.RideFrame
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
@@ -44,7 +45,6 @@ import net.minecraft.core.Direction
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
@@ -267,12 +267,23 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
}
// This ride works along its yaw so its velocity gets turned into world space here
override fun velocity(
settings: HorseSettings,
state: HorseState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
}
private fun localVelocity(
settings: HorseSettings,
state: HorseState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
state.rideVelocity.set(calculateRideSpaceVel(settings, state, vehicle, driver))
return state.rideVelocity.get()
@@ -398,37 +409,6 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
}
override fun angRollVel(
settings: HorseSettings,
state: HorseState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: HorseSettings,
state: HorseState,
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: HorseSettings,
state: HorseState,
@@ -475,14 +455,6 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
return if (state.sprinting.get()) 1.15f else 1.0f
}
override fun useAngVelSmoothing(
settings: HorseSettings,
state: HorseState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun inertia(
settings: HorseSettings,
state: HorseState,
@@ -12,7 +12,6 @@ import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
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.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
@@ -25,6 +24,7 @@ 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.RideFrame
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
@@ -45,7 +45,6 @@ import kotlin.math.sign
import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
@@ -98,7 +97,15 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
vehicle: PokemonEntity,
driver: Player
): Float {
return driver.deltaMovement.length().toFloat()
}
override fun driverTick(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
) {
val boostMax = vehicle.runtime.resolveFloat(settings.boostLimit ?: globalMinekart.boostLimit!!)
//TODO: should this be by how much turn has accumulated or simply time turning... or both?
val boostPerSecond = 20.0f
@@ -127,7 +134,6 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
}
inAirCheck(state, vehicle)
return driver.deltaMovement.length().toFloat()
}
fun inAirCheck(
@@ -197,12 +203,23 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
}
// This ride works along its yaw so its velocity gets turned into world space here
override fun velocity(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
}
private fun localVelocity(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
state.rideVelocity.set(calculateRideSpaceVel(settings, state, vehicle, driver))
return state.rideVelocity.get()
@@ -308,34 +325,6 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
return newVelocity
}
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.
return Vec3(0.0, mouseY, mouseX)
}
override fun canJump(
settings: MinekartSettings,
state: MinekartState,
@@ -387,14 +376,6 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
return fov
}
override fun useAngVelSmoothing(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun inertia(settings: MinekartSettings, state: MinekartState, vehicle: PokemonEntity): Double {
return 1.0
}
@@ -12,7 +12,6 @@ import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
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.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
@@ -25,6 +24,7 @@ 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.RideFrame
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
@@ -44,7 +44,6 @@ import kotlin.math.sign
import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
@@ -98,7 +97,15 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
vehicle: PokemonEntity,
driver: Player
): Float {
return driver.deltaMovement.length().toFloat()
}
override fun driverTick(
settings: VehicleSettings,
state: VehicleState,
vehicle: PokemonEntity,
driver: Player
) {
if (driver.isShiftKeyDown) {
state.drifting.set(true)
if(driver.zza != 0.0f) {
@@ -112,7 +119,6 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
}
inAirCheck(state, vehicle)
return driver.deltaMovement.length().toFloat()
}
fun inAirCheck(
@@ -172,12 +178,23 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
return Vec2(driver.xRot, vehicle.yRot + newMomentum.toFloat())
}
// This ride works along its yaw so its velocity gets turned into world space here
override fun velocity(
settings: VehicleSettings,
state: VehicleState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
}
private fun localVelocity(
settings: VehicleSettings,
state: VehicleState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
state.rideVelocity.set(calculateRideSpaceVel(settings, state, vehicle, driver))
return state.rideVelocity.get()
@@ -278,34 +295,6 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
return newVelocity
}
override fun angRollVel(
settings: VehicleSettings,
state: VehicleState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: VehicleSettings,
state: VehicleState,
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.
return Vec3(0.0, mouseY, mouseX)
}
override fun canJump(
settings: VehicleSettings,
state: VehicleState,
@@ -352,14 +341,6 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
return 1.0f
}
override fun useAngVelSmoothing(
settings: VehicleSettings,
state: VehicleState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun inertia(settings: VehicleSettings, state: VehicleState, vehicle: PokemonEntity): Double {
return 1.0
}
@@ -11,7 +11,6 @@ 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.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
@@ -23,6 +22,7 @@ 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.RideFrame
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
@@ -40,7 +40,6 @@ import kotlin.math.min
import net.minecraft.core.BlockPos
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.Mth
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
@@ -133,12 +132,23 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
return Vec2(vehicle.xRot, vehicle.yRot + state.deltaRotation.get().toFloat())
}
// This ride works along its yaw so its velocity gets turned into world space here
override fun velocity(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
}
private fun localVelocity(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
applyStrafeRotation(vehicle, driver, settings, state)
@@ -259,34 +269,6 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
vehicle.setYHeadRot(driver.yRot)
}
override fun angRollVel(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: BoatSettings,
state: BoatState,
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.
return Vec3(0.0, mouseY, mouseX)
}
override fun canJump(
settings: BoatSettings,
state: BoatState,
@@ -334,10 +316,6 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
return if (state.isVehicleSprinting.get()) sprintFov else 1.0f
}
override fun useAngVelSmoothing(settings: BoatSettings, state: BoatState, vehicle: PokemonEntity): Boolean {
return false
}
override fun inertia(settings: BoatSettings, state: BoatState, vehicle: PokemonEntity): Double {
return 0.5
}
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.liquid
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.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
@@ -22,6 +21,7 @@ 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.RideFrame
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
@@ -29,7 +29,6 @@ import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.writeRidingStats
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
@@ -96,12 +95,23 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
// This ride works along its yaw so its velocity gets turned into world space here
override fun velocity(
settings: BurstSettings,
state: BurstState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
}
private fun localVelocity(
settings: BurstSettings,
state: BurstState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val f = driver.xxa * 0.05f
var g = driver.zza * 0.6f
@@ -112,34 +122,6 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
return Vec3(f.toDouble(), 0.0, g.toDouble())
}
override fun angRollVel(
settings: BurstSettings,
state: BurstState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: BurstSettings,
state: BurstState,
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.
return Vec3(0.0, mouseX, mouseY)
}
override fun canJump(
settings: BurstSettings,
state: BurstState,
@@ -186,10 +168,6 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
return 1.0f
}
override fun useAngVelSmoothing(settings: BurstSettings, state: BurstState, vehicle: PokemonEntity): Boolean {
return false
}
override fun inertia(settings: BurstSettings, state: BurstState, vehicle: PokemonEntity): Double {
return 0.5
}
@@ -16,6 +16,7 @@ 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.RideSteerInput
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
@@ -54,6 +55,8 @@ import org.joml.Vector3f
class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
companion object {
// The correction forces were tuned as degrees per tenth of a second
private const val CORRECTION_FORCE_TO_DEGREES_PER_SECOND = 10.0
val KEY = cobblemonResource("liquid/dolphin")
}
@@ -201,7 +204,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
}
state.lastVelocity.set(prevVel)
return state.lastVelocity.get().yRot(vehicle.yRot.toRadians())
return state.lastVelocity.get()
} else {
if(state.hasBreached.get()) {
state.clearMouseSmoothers.set(true)
@@ -238,18 +241,17 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
val worldVelVec = localVelVec.mul(orientation).toVec3d()
state.lastVelocity.set(worldVelVec) // Set last velocity to be used during breaching
// Unrotate preemptively as this vector gets rotated later down the line in MC logic.
return worldVelVec.yRot(vehicle.yRot.toRadians())
return worldVelVec
}
override fun angRollVel(
private fun rotateOverTime(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
if (vehicle !is OrientationControllable) return Vec3.ZERO
) {
if (vehicle !is OrientationControllable) return
val controller = (vehicle as OrientationControllable).orientationController
val currSpeed = vehicle.deltaMovement.length()
@@ -267,8 +269,8 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
// Correct orientation when at low speeds
correctOrientation(settings, state, vehicle, controller, driver, deltaTime)
//yaw, pitch, roll
return Vec3(0.0, state.currPitchCorrectionForce.get(), state.currRollCorrectionForce.get())
val correction = deltaTime * CORRECTION_FORCE_TO_DEGREES_PER_SECOND
controller.rotate(0f, (state.currPitchCorrectionForce.get() * correction).toFloat(), (state.currRollCorrectionForce.get() * correction).toFloat())
}
private fun correctOrientation(
@@ -354,7 +356,18 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
}
}
override fun rotationOnMouseXY(
override fun steer(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
driver: Player,
input: RideSteerInput
) {
input.rotate(mouseRotation(settings, state, vehicle, driver, input.mousePitch, input.mouseYaw, input.mouseYSmoother, input.mouseXSmoother, input.sensitivity, input.deltaTime))
rotateOverTime(settings, state, vehicle, driver, input.deltaTime)
}
private fun mouseRotation(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
@@ -487,10 +500,6 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
return if (state.boosting.get()) 1.2f else 1.0f
}
override fun useAngVelSmoothing(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Boolean {
return false
}
override fun inertia(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Double {
return if (!vehicle.isInWater && !vehicle.isUnderWater) 1.0 else 0.1
}
@@ -17,14 +17,16 @@ 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.*
import com.cobblemon.mod.common.api.riding.camera.RideLookInput
import com.cobblemon.mod.common.api.riding.camera.RideLookTake
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.RideFrame
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveBoolean
@@ -254,29 +256,17 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
*********************************************/
// If the driver and vehicles eyes are out of the water but the vehicle is still in it then move only horizontally or downwards as you have surfaced
if (state.onSurface.get()) {
val worldVelVec = Vec3(currVel.x, min(0.0, currVel.y), currVel.z)
return worldVelVec
return RideFrame.yawToWorld(Vec3(currVel.x, min(0.0, currVel.y), currVel.z), vehicle.yRot)
} else {
val vertVec = Vec3(0.0, currVel.y, 0.0)
val horzVec = Vec3(currVel.x, 0.0, 0.0)
val horzVec = RideFrame.yawToWorld(Vec3(currVel.x, 0.0, 0.0), vehicle.yRot)
val localZVelVec = Vector3f(0.0f, 0.0f, currVel.z.toFloat() * -1.0f) // Flip the z axis to make this left handed
// Unrotate preemptively as this vector gets rotated later down the line in MC logic.
val worldVelVec = localZVelVec.mul(orientation).toVec3d().yRot(vehicle.yRot.toRadians())
val worldVelVec = localZVelVec.mul(orientation).toVec3d()
// Add back the horizontal and vertical components.
return worldVelVec.add(vertVec).add(horzVec)
}
}
override fun angRollVel(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun applyRenderRotation(
settings: SubmarineSettings,
state: SubmarineState,
@@ -323,7 +313,17 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
}
override fun rotationOnMouseXY(
override fun steer(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: Player,
input: RideSteerInput
) {
input.rotate(mouseRotation(settings, state, vehicle, driver, input.mousePitch, input.mouseYaw, input.mouseYSmoother, input.mouseXSmoother, input.sensitivity, input.deltaTime))
}
private fun mouseRotation(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
@@ -432,10 +432,6 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
return 1.0f
}
override fun useAngVelSmoothing(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Boolean {
return false
}
override fun inertia(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Double {
return if (!vehicle.isUnderWater) 1.0 else 0.1
}
@@ -452,8 +448,10 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
return false
}
override fun mouseModifiesDriverRotation(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Pair<Boolean, Boolean> {
return Pair(false, state.onSurface.get())
// Surfaced the mouse pitch only moves the look so you can look around without nosing the sub under
override fun consumeLook(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity, input: RideLookInput): RideLookTake {
if (!state.onSurface.get()) return RideLookTake.all(input)
return RideLookTake(input.yawDelta, 0f, recenterYaw = true, recenterPitch = false)
}
override fun shouldRotatePokemonHead(
@@ -0,0 +1,95 @@
/*
* 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.camera
import com.cobblemon.mod.common.api.orientation.OrientationController.Companion.FORWARDS
import com.cobblemon.mod.common.api.orientation.OrientationController.Companion.UP
import net.minecraft.util.Mth
import org.joml.Matrix3f
import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.atan2
/**
* What a ride gets to build its camera base from each frame.
* One lives per ride on the client so [data] can keep a ride's own camera state between frames.
*/
class RideCameraContext {
companion object {
// Ride up this close to the view direction has no readable roll
private const val MIN_FLAT_LENGTH_SQ = 1.0E-6f
/** Rotation whose forward is [forward] and up is [up]. Both unit length and perpendicular */
@JvmStatic
fun frameOf(forward: Vector3f, up: Vector3f): Quaternionf {
val right = Vector3f(forward).cross(up)
return Quaternionf().setFromNormalized(Matrix3f(right, up, Vector3f(forward).negate()))
}
}
/** Ride orientation exactly as the driver sets it */
val rawOrientation = Quaternionf()
/** Spring smoothed ride orientation the model and seats are drawn with */
val renderOrientation = Quaternionf()
/** True when the local player is the one steering */
var isDriving = false
private set
/** Base the camera used last frame, null on the first frame of a ride */
var previousBase: Quaternionf? = null
private set
/** Seconds since the last frame, 0 while paused */
var frameSeconds = 0f
private set
var partialTicks = 0f
private set
/** Free slot for a ride to keep its own camera state in. Cleared when the ride changes */
var data: Any? = null
/** Final camera view this frame, base with the look on top */
val view = Quaternionf()
/** Drivers hang straight off the ride. Passengers get the smoothed one since raw packets make it jerky */
fun defaultBase(): Quaternionf = Quaternionf(if (isDriving) rawOrientation else renderOrientation)
internal fun update(raw: Quaternionf, render: Quaternionf, isDriving: Boolean, frameSeconds: Float, partialTicks: Float) {
rawOrientation.set(raw)
renderOrientation.set(render)
this.isDriving = isDriving
this.frameSeconds = frameSeconds
this.partialTicks = partialTicks
}
/** Degrees the ride's model is rolled from the camera around the view direction */
fun rideRollAroundView(): Float {
val forward = FORWARDS.rotate(view, Vector3f())
val cameraUp = UP.rotate(view, Vector3f())
val rideUp = UP.rotate(renderOrientation, Vector3f())
rideUp.sub(Vector3f(forward).mul(rideUp.dot(forward)))
if (rideUp.lengthSquared() < MIN_FLAT_LENGTH_SQ) return 0f
return atan2(Vector3f(cameraUp).cross(rideUp).dot(forward), cameraUp.dot(rideUp)) * Mth.RAD_TO_DEG
}
internal fun updateView(view: Quaternionf) {
this.view.set(view)
}
internal fun finishFrame(base: Quaternionf) {
previousBase = (previousBase ?: Quaternionf()).set(base)
}
internal fun reset() {
view.identity()
rawOrientation.identity()
renderOrientation.identity()
isDriving = false
previousBase = null
frameSeconds = 0f
partialTicks = 0f
data = null
}
}
@@ -0,0 +1,24 @@
/*
* 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.camera
/**
* One frame of mouse look in degrees, before the ride takes its share.
* Angles are relative to the camera base.
*/
data class RideLookInput(
/** Degrees the mouse moved the look sideways this frame */
val yawDelta: Float,
/** Degrees the mouse moved the look up or down this frame */
val pitchDelta: Float,
/** Look yaw before this frame's movement */
val yaw: Float,
/** Look pitch before this frame's movement */
val pitch: Float
)
@@ -0,0 +1,32 @@
/*
* 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.camera
/**
* How far a rider can look around the camera base and how much of the base's turning the look follows.
*/
data class RideLookSettings(
/** Degrees the look can pitch either side of the camera base */
val maxPitch: Float = DEFAULT_MAX_PITCH,
/** Degrees the look can yaw either side of the camera base */
val maxYaw: Float = DEFAULT_MAX_YAW,
/** 0 to 1 share of the base's turning the look follows. 1 turns with the base and 0 holds its direction in the world */
val carry: Float = FULL_CARRY
) {
companion object {
// Just short of straight up or down so yaw never goes degenerate
const val DEFAULT_MAX_PITCH = 89f
// A bit past side on so you can look over your shoulder. Raise to let riders look further back
const val DEFAULT_MAX_YAW = 105f
const val FULL_CARRY = 1f
@JvmField
val DEFAULT = RideLookSettings()
}
}
@@ -0,0 +1,31 @@
/*
* 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.camera
/**
* The part of a frame's mouse look a ride takes to steer with, in degrees.
* Whatever isn't taken moves the rider's look instead.
*/
data class RideLookTake(
val yaw: Float,
val pitch: Float,
/** Ease the look back to centre on this axis */
val recenterYaw: Boolean,
val recenterPitch: Boolean
) {
companion object {
/** Ride takes nothing and the look stays where it's put */
@JvmField
val NONE = RideLookTake(0f, 0f, recenterYaw = false, recenterPitch = false)
/** Ride steers with all of it and the look drifts back to centre. How rolling rides have always worked */
@JvmStatic
fun all(input: RideLookInput) = RideLookTake(input.yawDelta, input.pitchDelta, recenterYaw = true, recenterPitch = true)
}
}
@@ -9,8 +9,12 @@
package com.cobblemon.mod.common.api.riding.util
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.Entity
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.shapes.BooleanOp
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
import kotlin.math.floor
/**
@@ -60,3 +64,22 @@ fun collectPrimitivesFromBlocks(
return primitives
}
/**
* Real collision boxes in [searchBox] as [PrimitiveShape.BOX] primitives, so slabs, stairs and fences
* keep their actual shape. Merged first so a flat wall comes out as one box with no seams to dip the field
*/
fun collectCollisionPrimitives(entity: Entity, searchBox: AABB): List<FieldPrimitive> {
var merged: VoxelShape = Shapes.empty()
for (shape in entity.level().getBlockCollisions(entity, searchBox)) {
merged = Shapes.joinUnoptimized(merged, shape, BooleanOp.OR)
}
return merged.optimize().toAabbs().map {
val center = it.center
FieldPrimitive(
shape = PrimitiveShape.BOX,
centerX = center.x.toFloat(), centerY = center.y.toFloat(), centerZ = center.z.toFloat(),
halfWidth = (it.xsize * 0.5).toFloat(), halfHeight = (it.ysize * 0.5).toFloat(), halfDepth = (it.zsize * 0.5).toFloat()
)
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.util
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.util.Mth
import net.minecraft.world.phys.Vec3
/**
* Turns ride velocities between world space and a ride's yaw local space,
* where x is strafe, y is up and z is forward along the ride's yaw.
*/
object RideFrame {
/** Yaw local to world. Same maths PokemonEntity.travel always used so rides come out identical. */
@JvmStatic
fun yawToWorld(local: Vec3, yawDegrees: Float): Vec3 {
val sin = Mth.sin(yawDegrees.toRadians()).toDouble()
val cos = Mth.cos(yawDegrees.toRadians()).toDouble()
return Vec3(local.x * cos - local.z * sin, local.y, local.z * cos + local.x * sin)
}
/** World to yaw local. Exact inverse of [yawToWorld]. */
@JvmStatic
fun worldToYaw(world: Vec3, yawDegrees: Float): Vec3 {
val sin = Mth.sin(yawDegrees.toRadians()).toDouble()
val cos = Mth.cos(yawDegrees.toRadians()).toDouble()
return Vec3(world.x * cos + world.z * sin, world.y, world.z * cos - world.x * sin)
}
}
@@ -198,17 +198,7 @@ interface Spawner {
val mid = chunk.getBlockState(startPos)
val above = chunk.getBlockState(abovePos)
// Above must be non-solid
if (!above.isPathfindable(PathComputationType.AIR)) {
return false
}
// Position must be non-air
if (mid.isAir) {
return false
}
return true
return above.isPathfindable(PathComputationType.AIR) && mid.isAir
}
fun constrainArea(area: SpawningZoneInput): SpawningZoneInput? {
@@ -30,7 +30,7 @@ class OrbBlock(properties: Properties) : SixFaceRotationalBlock(properties) {
val FLOOR_NORTH = Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.375, 0.6875)
val WALL_NORTH = Shapes.box(0.3125, 0.3125, 0.625, 0.6875, 0.6875, 1.0)
val CEILING_NORTH = Shapes.box(0.3125, 0.625, 0.3125, 0.6875, 1.0, 0.6875)
val CEILING_NORTH = Shapes.box(0.3125, 0.625, 0.375, 0.6875, 1.0, 0.6875)
}
init {
@@ -1,50 +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
import org.joml.Quaternionf
import org.spongepowered.asm.mixin.Unique
interface RidingCameraInterface {
// Used to help in camera rotation rendering when transitioning
// from rolled to unrolled.
@Unique
fun `getCobblemon$returnTimer`(): Float
@Unique
fun `setCobblemon$returnTimer`(time: Float)
@Unique
fun `getCobblemon$lastHandledRotationTime`(): Double
@Unique
fun `setCobblemon$lastHandledRotationTime`(time: Double)
@Unique
fun `getCobblemon$frameTime`(): Double
@Unique
fun `setCobblemon$frameTime`(time: Double)
@Unique
fun `getCobblemon$partialTickTime`(): Float
@Unique
fun `setCobblemon$partialTickTime`(time: Float)
@Unique
fun `getCobblemon$rollAngleStart`(): Float
@Unique
fun `setCobblemon$rollAngleStart`(angle: Float)
fun `getCobblemon$smoothRotation`(): Quaternionf?
fun `setCobblemon$smoothRotation`(smoothRotation: Quaternionf)
}
@@ -182,6 +182,7 @@ class RideControlsOverlay : Gui(Minecraft.getInstance()) {
showHorizontalMouse = false
}
VehicleBehaviour.KEY -> {}
BoardBehaviour.KEY -> {}
}
val centerX = minecraft.window.guiScaledWidth / 2
@@ -10,10 +10,8 @@ package com.cobblemon.mod.common.client.net.orientation
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.duck.RidePassenger
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateOrientationPacket
import net.minecraft.client.Minecraft
import net.minecraft.util.Mth
object ClientboundUpdateOrientationHandler : ClientNetworkPacketHandler<ClientboundUpdateOrientationPacket> {
override fun handle(packet: ClientboundUpdateOrientationPacket, client: Minecraft) {
@@ -26,21 +24,7 @@ object ClientboundUpdateOrientationHandler : ClientNetworkPacketHandler<Clientbo
// of truth on vehicle orientation updates.
if (vehicle.controllingPassenger != player) {
packet.active?.let { shouldUseCustomOrientation ->
val playerIsPassenger = vehicle.passengers.contains(player)
// If the player has just switched to riding a custom orientation ride then set their
// ride x and y rots local to the vehicle rots(if the controller wasn't active and will now be active)
// This ensures that on transition your camera stays in the same spot
if (!vehicleController.isActive() && shouldUseCustomOrientation && playerIsPassenger) {
// Set local to the vehicle x and yrot
val playerRotater = player as RidePassenger?
if (playerRotater != null) {
playerRotater.`cobblemon$setRideXRot`(Mth.wrapDegrees(player.getXRot() - vehicle.getXRot()))
playerRotater.`cobblemon$setRideYRot`(Mth.wrapDegrees(player.getYRot() - vehicle.getYRot()))
}
}
// Passenger look angles get seeded by the camera on its first rolling frame
vehicle.orientationController.active = shouldUseCustomOrientation
}
vehicle.orientationController.updateOrientation { _ -> packet.orientation }
@@ -14,6 +14,7 @@ import com.cobblemon.mod.common.api.orientation.OrientationController
import com.cobblemon.mod.common.api.orientation.OrientationController.Companion.FORWARDS
import com.cobblemon.mod.common.api.orientation.OrientationController.Companion.LEFT
import com.cobblemon.mod.common.api.orientation.OrientationController.Companion.UP
import com.cobblemon.mod.common.api.riding.camera.RideLookSettings
import com.cobblemon.mod.common.client.MountedPokemonAnimationRenderController
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
@@ -30,23 +31,45 @@ import net.minecraft.client.Minecraft
import net.minecraft.util.Mth
import net.minecraft.world.entity.Entity
import net.minecraft.world.level.ClipContext
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.HitResult
import net.minecraft.world.phys.Vec3
import org.joml.Matrix3f
import org.joml.Quaternionf
import org.joml.Vector3d
import org.joml.Vector3f
import kotlin.math.asin
import kotlin.math.atan2
import kotlin.math.ceil
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sign
import kotlin.math.sqrt
object MountedCameraRenderer {
private var returnTimer: Float = 0f
private const val MIN_OFFSET_LENGTH = 1.0E-6
// Below this the forward vector is pointing almost straight up or down so it has no usable yaw
private const val MIN_HORIZONTAL_LENGTH_SQ = 1.0E-6f
// Entity.turn clamps pitch to this
private const val VANILLA_MAX_PITCH = 90f
// Camera.getMaxZoom checks this far either side on each axis, so it reads a wall as hit if it starts closer than this
private const val VANILLA_CAMERA_PROBE = 0.1
// Extra gap past vanilla's probe the camera keeps from walls. Raise if the view still starts inside blocks
private const val CAMERA_WALL_CLEARANCE = 0.02
// Longest step of the camera sweep. Lower follows the straight line more closely round corners but costs more
private const val CAMERA_SWEEP_STEP = 0.25
private var lastHandledRotationTime: Double = Double.MIN_VALUE
private var frameTime: Double = 0.0
private var rollAngleStart: Float = 0f
var smoothRotation: Quaternionf? = null
private val state = RideCameraState()
/** Drops all per ride camera state. Called whenever the camera isn't on a ride. */
fun reset() = state.reset()
/** Degrees to roll the first person hands this frame */
fun getHandRoll(): Float = state.handRoll
fun getCameraPosition(
instance: Camera,
@@ -79,7 +102,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)
@@ -125,24 +148,44 @@ object MountedCameraRenderer {
pivot.add(entityPos.toVector3f())
} */
val offsetDistance = offset.length()
val offsetDirection = offset.scale(1 / 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)
val eyeMaxZoom: Double = getMaxZoom(eyeOffsetDistance, eyeOffsetDirection, eyePos, instance)
val playerRotater = driver as RidePassenger
playerRotater.`cobblemon$setRideEyePos`(eyeOffsetDirection.scale(eyeMaxZoom).add(eyePos))
return offsetDirection.scale(maxZoom).add(pos)
// The seat can swing into a wall round corners so clipping from it isn't safe. Come out from the hitbox instead
return sweepFromBody(vehicle, entityPos, offset.add(pos))
}
/**
* Slides a small box from the middle of the ride's hitbox out to [target] and returns where it ends up.
* The hitbox never sits in blocks so this never starts inside a wall, and the box keeps vanilla's
* zoom clipping from starting inside one either
*/
private fun sweepFromBody(vehicle: Entity, lerpedPos: Vec3, target: Vec3): Vec3 {
val level = vehicle.level()
val halfSize = VANILLA_CAMERA_PROBE + CAMERA_WALL_CLEARANCE
var start = lerpedPos.add(0.0, vehicle.bbHeight * 0.5, 0.0)
// Lerping between ticks can cut across a corner the ride went round, so fall back to where it really is
if (level.getBlockCollisions(null, AABB(start, start).inflate(halfSize)).any()) start = vehicle.boundingBox.center
var box = AABB(start, start).inflate(halfSize)
val path = target.subtract(start)
// Small steps so it slides along the straight line instead of going one axis at a time
val steps = max(1, ceil(path.length() / CAMERA_SWEEP_STEP).toInt())
val step = path.scale(1.0 / steps)
repeat(steps) {
box = box.move(Entity.collideBoundingBox(null, step, box, level, emptyList()))
}
return box.center
}
// Return value is false if the vanilla camera rotations should be applied.
fun setRotation(instance: Camera): Boolean {
val cameraDuck = instance as CameraDuck
state.handRoll = 0f
// Calculate the current frametime
val d = Blaze3D.getTime()
@@ -152,6 +195,7 @@ object MountedCameraRenderer {
// Don't assume the camera to be attached to an entity. Ponder Scenes from create et.al. for example aren't.
val vehicle: Entity? = cameraDuck.`cobblemon$getEntity`()?.vehicle ?: return false
if (vehicle !is OrientationControllable) return false
state.bindTo(vehicle.id)
// If the current vehicle has no orientation then return and perform
// vanilla camera rotations
@@ -161,11 +205,12 @@ object MountedCameraRenderer {
// If the controller has been deactivated but the orientation isn't null yet
// then perform transition from rolled ride/camera to vanilla
if (!vehicleController.isActive() && vehicleController.orientation != null) {
state.lookSeeded = false
return applyTransitionRotation(vehicleController, instance)
} else { // Otherwise perform cobblemon specific camera rotation and set transition helper variables
applyCameraRotation(instance)
returnTimer = 0f
rollAngleStart = instance.rotation().getEulerAnglesYXZ(Vector3f()).z()
state.returnTimer = 0f
state.rollAngleStart = instance.rotation().getEulerAnglesYXZ(Vector3f()).z()
return true
}
}
@@ -182,25 +227,31 @@ object MountedCameraRenderer {
val vehicleController: OrientationController = controllableVehicle.getOrientationController()
val isDriving = driver.controlledVehicle != null
val ride = vehicle as? PokemonEntity
// Grab the ride orientation
// If the player is driving then use the current orientation since the driver sets it.
// If not then use the smoothed render orientation for passengers otherwise the vehicle rotation will be jerky
var newRotation = if (isDriving) {
vehicleController.orientation!!.normal(Matrix3f()).getNormalizedRotation(Quaternionf())
} else {
vehicleController.getRenderOrientation(instance.partialTickTime)
}
val context = state.context
context.update(
vehicleController.orientation!!.normal(Matrix3f()).getNormalizedRotation(Quaternionf()),
vehicleController.getRenderOrientation(instance.partialTickTime),
isDriving,
if (Minecraft.getInstance().isPaused) 0f else frameTime.toFloat(),
instance.partialTickTime
)
// The ride picks what the camera hangs off. disableRoll still gets the last word below since it's the player's setting
var newRotation = ride?.let { pokemon ->
pokemon.ifRidingAvailableSupply<Quaternionf?>(null) { behaviour, settings, rideState ->
behaviour.cameraBase(settings, rideState, pokemon, context)
}
} ?: context.defaultBase()
// Unroll the current rotation if no roll is requested and not currently freelooking
if (Cobblemon.config.disableRoll) {
// Init the smooth rotation if it has not been set yet.
if (smoothRotation == null) {
smoothRotation = Quaternionf().set(instance.rotation())
if (state.smoothRotation == null) {
state.smoothRotation = Quaternionf().set(instance.rotation())
}
val rideAngs = newRotation.getEulerAnglesYXZ(Vector3f())
val cameraAngs = smoothRotation!!.getEulerAnglesYXZ(Vector3f())
val cameraAngs = state.smoothRotation!!.getEulerAnglesYXZ(Vector3f())
val degDiff = Mth.degreesDifference(
Math.toDegrees(cameraAngs.y().toDouble()).toFloat(),
@@ -237,37 +288,114 @@ object MountedCameraRenderer {
// Set rotations
val smoothedRot = Quaternionf().rotateYXZ(newYaw.toFloat(), newPitch.toFloat(), newRoll)
smoothRotation?.set(smoothedRot)
state.smoothRotation?.set(smoothedRot)
newRotation.set(smoothedRot)
}
var rotationOffset = 0
if (Minecraft.getInstance().options.cameraType.isMirrored) {
newRotation.rotateY(Math.toRadians(180.0).toFloat())
rotationOffset = 180
// newRotation is the camera base from here on
val playerRotater = driver as RidePassenger
// First frame on a roll ride so carry over wherever the player was already looking
if (!state.lookSeeded) {
seedLook(driver, playerRotater, newRotation)
state.lookSeeded = true
} else {
val lookSettings = ride?.let { pokemon ->
pokemon.ifRidingAvailableSupply(RideLookSettings.DEFAULT) { behaviour, settings, rideState ->
behaviour.lookSettings(settings, rideState, pokemon)
}
} ?: RideLookSettings.DEFAULT
val previousBase = context.previousBase
// Full carry is exactly what the look angles already do so skip the work
if (lookSettings.carry < RideLookSettings.FULL_CARRY && previousBase != null) {
val oldYaw = playerRotater.`cobblemon$getRideYRot`()
val oldPitch = playerRotater.`cobblemon$getRideXRot`()
carryLook(playerRotater, previousBase, newRotation, lookSettings.carry)
// The base turning drags the look along at its limits but never pushes it further out than it was
playerRotater.`cobblemon$setRideYRot`(clampOutward(playerRotater.`cobblemon$getRideYRot`(), oldYaw, lookSettings.maxYaw))
playerRotater.`cobblemon$setRideXRot`(clampOutward(playerRotater.`cobblemon$getRideXRot`(), oldPitch, lookSettings.maxPitch))
}
}
context.finishFrame(newRotation)
// Look sits on top of the base. Built straight as a quaternion so nothing round trips through euler angles
val view = Quaternionf(newRotation).mul(lookRotation(playerRotater)).normalize()
// Get the drivers local look angle in relation to the ride and apply it.
if (vehicleController.active) {
val vehicleMatrix = Matrix3f().set(newRotation)
val driverMatrix = Matrix3f() // Base the driver
val playerRotater = driver as RidePassenger
newRotation = vehicleMatrix.mul(
driverMatrix.rotateYXZ(
-Mth.DEG_TO_RAD * (playerRotater.`cobblemon$getRideYRot`()),
-Mth.DEG_TO_RAD * playerRotater.`cobblemon$getRideXRot`(),
0.0f
)
).normal(Matrix3f()).getNormalizedRotation(Quaternionf())
// Mirroring only flips the camera so the player still aims where they look
val cameraRotation = if (Minecraft.getInstance().options.cameraType.isMirrored) Quaternionf(view).rotateY(Mth.PI) else view
setCameraRotations(cameraRotation, instance, cameraDuck)
lookAlong(driver, view)
// Third person shows the player model which already leans with the ride
context.updateView(view)
if (Minecraft.getInstance().options.cameraType.isFirstPerson) {
state.handRoll = ride?.let { pokemon ->
pokemon.ifRidingAvailableSupply(0f) { behaviour, settings, rideState ->
behaviour.firstPersonHandRoll(settings, rideState, pokemon, context)
}
} ?: 0f
}
val eulerAngs = newRotation.getEulerAnglesYXZ(Vector3f())
setCameraRotations(newRotation, instance, cameraDuck)
}
// Set the drivers rotations to match
driver.xRot = -eulerAngs.x() * Mth.RAD_TO_DEG + rotationOffset
driver.yRot = 180.0f - (eulerAngs.y() * Mth.RAD_TO_DEG)
/** Sets ride look angles so the view keeps pointing where [player] already looks once [base] takes over. */
private fun seedLook(player: Entity, rotater: RidePassenger, base: Quaternionf) {
val local = Quaternionf(base).invert().transform(player.calculateViewVector(player.xRot, player.yRot).toVector3f())
setLookFromLocal(rotater, local)
}
/**
* Turns the look by only [carry] of how far the base turned since last frame.
* Whatever the look doesn't follow ends up as a change in the look angles instead.
*/
private fun carryLook(rotater: RidePassenger, previousBase: Quaternionf, base: Quaternionf, carry: Float) {
val worldLook = Quaternionf(previousBase).mul(lookRotation(rotater))
val baseDelta = Quaternionf(base).mul(Quaternionf(previousBase).invert())
Quaternionf().slerp(baseDelta, carry).mul(worldLook, worldLook)
val local = Quaternionf(base).invert().transform(FORWARDS.rotate(worldLook, Vector3f()))
setLookFromLocal(rotater, local)
}
/** Keeps [angle] within [limit] either side, or within [previous] if that was already further out */
private fun clampOutward(angle: Float, previous: Float, limit: Float): Float {
val bound = max(limit, abs(previous))
return angle.coerceIn(-bound, bound)
}
/** Look as a rotation relative to the base. */
private fun lookRotation(rotater: RidePassenger): Quaternionf = Quaternionf().rotationYXZ(
-Mth.DEG_TO_RAD * rotater.`cobblemon$getRideYRot`(),
-Mth.DEG_TO_RAD * rotater.`cobblemon$getRideXRot`(),
0.0f
)
/** Look angles that point along [local], a direction in the base's frame. Inverse of [lookRotation]. */
private fun setLookFromLocal(rotater: RidePassenger, local: Vector3f) {
rotater.`cobblemon$setRideXRot`(-asin(local.y.coerceIn(-1f, 1f)) * Mth.RAD_TO_DEG)
rotater.`cobblemon$setRideYRot`(atan2(local.x, -local.z) * Mth.RAD_TO_DEG)
}
/**
* Points [player] along [view] so picking, bows and other clients follow the camera.
* Yaw stays continuous so partial tick lerps never sweep through a 360 wrap.
*/
private fun lookAlong(player: Entity, view: Quaternionf) {
val forward = FORWARDS.rotate(view, Vector3f())
val pitch = pitchOf(forward)
val yaw = player.yRot + Mth.wrapDegrees(yawOf(forward, player.yRot) - player.yRot)
// Shift the old values by the same amount like vanilla turn does so the lerped view vector doesn't trail a tick behind
player.xRotO = Mth.clamp(player.xRotO + pitch - player.xRot, -VANILLA_MAX_PITCH, VANILLA_MAX_PITCH)
player.yRotO += yaw - player.yRot
player.xRot = pitch
player.yRot = yaw
}
/** Vanilla style pitch in degrees for a forward vector. */
private fun pitchOf(forward: Vector3f): Float = -asin(forward.y.coerceIn(-1f, 1f)) * Mth.RAD_TO_DEG
/** Vanilla style yaw in degrees for a forward vector, or [fallback] when looking straight up or down. */
private fun yawOf(forward: Vector3f, fallback: Float): Float {
if (forward.x * forward.x + forward.z * forward.z < MIN_HORIZONTAL_LENGTH_SQ) return fallback
return atan2(-forward.x, forward.z) * Mth.RAD_TO_DEG
}
private fun applyTransitionRotation(
@@ -277,14 +405,14 @@ object MountedCameraRenderer {
val cameraDuck = instance as CameraDuck
val entity = cameraDuck.`cobblemon$getEntity`()!!
// If the transition has just started then
if (returnTimer == 0f) {
if (state.returnTimer == 0f) {
resetDriverRotations(instance, entity)
}
if (returnTimer < 1f) {
if (state.returnTimer < 1f) {
// Rotation is taken from entity since we no longer handle mouse ourselves
// Stops a period of time when you can't input anything.
val interpolatedRoll = Mth.lerp(returnTimer, rollAngleStart, 0.0f)
val interpolatedRoll = Mth.lerp(state.returnTimer, state.rollAngleStart, 0.0f)
val pitch = Math.toRadians(-entity.xRot.toDouble()).toFloat()
val yaw = Math.toRadians((180 - entity.yRot).toDouble()).toFloat()
@@ -293,16 +421,16 @@ object MountedCameraRenderer {
setCameraRotations(interRot, instance, cameraDuck)
if (rollAngleStart == 0f) {
rollAngleStart = 1f
if (state.rollAngleStart == 0f) {
state.rollAngleStart = 1f
vehicleController.reset()
resetDriverRotations(instance, entity)
return false
}
returnTimer += instance.partialTickTime * (1.0f / 20.0f)
state.returnTimer += instance.partialTickTime * (1.0f / 20.0f)
return true
} else {
returnTimer = 1f
state.returnTimer = 1f
vehicleController.reset()
resetDriverRotations(instance, entity)
return false
@@ -329,7 +457,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 +482,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) {
@@ -384,14 +516,14 @@ object MountedCameraRenderer {
}
private fun setCameraRotations(rotation: Quaternionf, instance: Camera, duck: CameraDuck) {
val eulerAngs = rotation.getEulerAnglesYXZ(Vector3f())
val cameraRotation = instance.rotation()
cameraRotation.rotationYXZ(eulerAngs.y, eulerAngs.x, eulerAngs.z)
duck.`cobblemon$setXRot`(eulerAngs.x() * Mth.RAD_TO_DEG)
duck.`cobblemon$setYRot`(eulerAngs.y() * Mth.RAD_TO_DEG)
FORWARDS.rotate(rotation, duck.`cobblemon$getForwards`())
UP.rotate(rotation, duck.`cobblemon$getUp`())
val cameraRotation = instance.rotation().set(rotation).normalize()
FORWARDS.rotate(cameraRotation, duck.`cobblemon$getForwards`())
UP.rotate(cameraRotation, duck.`cobblemon$getUp`())
LEFT.rotate(cameraRotation, duck.`cobblemon$getLeft`())
// Same yaw and pitch convention vanilla setRotation stores since vanilla reads these back
val forward = duck.`cobblemon$getForwards`()
duck.`cobblemon$setXRot`(pitchOf(forward))
duck.`cobblemon$setYRot`(yawOf(forward, duck.`cobblemon$getYRot`()))
}
}
@@ -0,0 +1,55 @@
/*
* 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.camera
import com.cobblemon.mod.common.api.riding.camera.RideCameraContext
import org.joml.Quaternionf
/**
* Camera state for the ride the local player is on.
* Tied to one vehicle so nothing leaks from one ride into the next.
*/
class RideCameraState {
companion object {
private const val NO_VEHICLE = -1
}
var vehicleId: Int = NO_VEHICLE
private set
// 0 to 1 progress of the roll going back to level once a roll ride turns off
var returnTimer: Float = 0f
// Camera roll at the start of that transition
var rollAngleStart: Float = 0f
// Smoothed rotation the disableRoll camera eases with
var smoothRotation: Quaternionf? = null
// False until the look has been rebuilt from the player's real look for this stretch of rolling
var lookSeeded: Boolean = false
// Degrees the first person hands are rolled this frame
var handRoll: Float = 0f
// Handed to the ride's camera hooks every frame
val context = RideCameraContext()
/** Starts fresh if [vehicleId] isn't the vehicle this state was built for. */
fun bindTo(vehicleId: Int) {
if (this.vehicleId == vehicleId) return
reset()
this.vehicleId = vehicleId
}
fun reset() {
vehicleId = NO_VEHICLE
returnTimer = 0f
rollAngleStart = 0f
smoothRotation = null
lookSeeded = false
handRoll = 0f
context.reset()
}
}
@@ -770,8 +770,13 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
val controller = entity.orientationController
val transformationMatrix = Matrix4f()
val center = Vector3f(0f, entity.bbHeight/2, 0f)
val orientation = controller.getRenderOrientation(state.getPartialTicks())
// Same shift the renderer gives the model so seats stay on it
transformationMatrix.translate(entity.ifRidingAvailableSupply(fallback = Vector3f()) { behaviour, settings, rideState ->
behaviour.renderOffset(settings, rideState, entity, orientation)
})
transformationMatrix.translate(center)
transformationMatrix.rotate(controller.getRenderOrientation(state.getPartialTicks()))
transformationMatrix.rotate(orientation)
transformationMatrix.translate(center.negate())
matrixStack.mulPose(transformationMatrix)
} else {
@@ -34,7 +34,7 @@ class DusclopsModel (root: ModelPart) : PokemonPosableModel(root) {
lateinit var battleidle: Pose
override fun registerPoses() {
animations["cry"] = "q.bedrock_stateful('dusclops', 'cry')".asExpressionLike()
animations["cry"] = "q.bedrock_stateful('squirtle', 'cry')".asExpressionLike()
val blink = quirk { bedrockStateful("dusclops", "blink") }
@@ -19,9 +19,16 @@ import net.minecraft.client.model.AnimationUtils
import net.minecraft.client.model.HumanoidModel
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.client.player.AbstractClientPlayer
import net.minecraft.util.Mth
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.HumanoidArm
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.atan2
import kotlin.math.hypot
/**
* @author landonjw
@@ -30,6 +37,9 @@ object MountedPlayerRenderer {
@JvmField
var shouldApplyRootAnimation = false
// Furthest a passenger's head turns from its body either way. From LivingEntityRenderer.render
private const val VANILLA_PASSENGER_HEAD_YAW_LIMIT = 85f
@JvmField
val relevantPartsByName = mutableMapOf(
"body" to ModelPart(emptyList(), emptyMap()),
@@ -140,6 +150,28 @@ object MountedPlayerRenderer {
model.hat.copyFrom(model.head)
}
/**
* Rider head yaw and pitch in degrees like vanilla hands them to setupAnim, but measured against the seat
* the body is drawn on instead of world yaw. Null if the rider has no seat locator
*/
@JvmStatic
fun riderHeadAngles(pokemon: PokemonEntity, rider: LivingEntity, partialTicks: Float): Vec2? {
val delegate = pokemon.delegate as? PokemonClientDelegate ?: return null
val locator = delegate.getSeatLocator(rider)?.let { delegate.locatorStates[it] } ?: return null
// MountedLivingEntityRenderer cancels the body yaw so the seat's rotation is the whole body frame
val seat = locator.matrix.getNormalizedRotation(Quaternionf())
val forward = seat.transform(Vector3f(0f, 0f, -1f))
val up = seat.transform(Vector3f(0f, 1f, 0f))
// Way a positive head yaw turns
val side = Vector3f(forward).cross(up)
val view = rider.getViewVector(partialTicks).toVector3f()
val ahead = view.dot(forward)
val across = view.dot(side)
val yaw = atan2(across, ahead) * Mth.RAD_TO_DEG
val pitch = atan2(-view.dot(up), hypot(across, ahead)) * Mth.RAD_TO_DEG
return Vec2(yaw.coerceIn(-VANILLA_PASSENGER_HEAD_YAW_LIMIT, VANILLA_PASSENGER_HEAD_YAW_LIMIT), pitch)
}
const val RIDING_SITTING_OFFSET = -10.0
fun animateRoot(stack: PoseStack) {
val root = relevantPartsByName["body"] ?: return
@@ -199,11 +199,16 @@ class PokemonRenderer(
val matrix = poseMatrix.last().pose()
val yaw = Mth.rotLerp(partialTicks, entity.yBodyRotO, entity.yBodyRot)
val center = Vector3f(0f, entity.bbHeight/2, 0f)
val orientation = controller.getRenderOrientation(partialTicks)
val transformationMatrix = Matrix4f()
// Lets the ride slide the model onto what it's standing on
transformationMatrix.translate(entity.ifRidingAvailableSupply(fallback = Vector3f()) { behaviour, settings, state ->
behaviour.renderOffset(settings, state, entity, orientation)
})
//Move origin to center of Pokemon
transformationMatrix.translate(center)
transformationMatrix.rotate(controller.getRenderOrientation(partialTicks))
transformationMatrix.rotate(orientation)
//Move origin to base of the entity
transformationMatrix.translate(center.negate(Vector3f()))
@@ -107,7 +107,6 @@ import com.cobblemon.mod.common.pokemon.feature.SlowpokeTailRegrowthSpeciesFeatu
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.google.common.collect.UnmodifiableIterator
import com.mojang.serialization.Codec
@@ -1866,28 +1865,9 @@ open class PokemonEntity(
}
}
// Ridden movement is all in travel which never gets here while someone's steering
override fun handleRelativeFrictionAndCalculateMovement(deltaMovement: Vec3, friction: Float): Vec3 {
if (!this.hasRider() || this.controllingPassenger == null) {
super.handleRelativeFrictionAndCalculateMovement(deltaMovement, friction)
} else {
val velocity = ifRidingAvailableSupply(fallback = Vec3.ZERO) { behaviour, settings, state ->
behaviour.velocity(settings, state, this, this.controllingPassenger as Player, deltaMovement)
}
// Handle ridden pokemon differently to allow vector lerp instead of simple addition.
val v = getInputVector(velocity, 1.0f, this.yRot)
// Changing this will give the ride more or less inertia/handling/drift
val inertia = ifRidingAvailableSupply(fallback = 0.5) { behaviour, settings, state ->
behaviour.inertia(settings, state,this)
}
this.deltaMovement = this.deltaMovement.lerp(v, inertia)
var pos = this.deltaMovement.scale(this.speed.toDouble())
if (super.onGround() && this.deltaMovement.y == 0.0) {
pos = pos.subtract(0.0, 0.0001, 0.0)
}
this.move(MoverType.SELF, pos)
}
super.handleRelativeFrictionAndCalculateMovement(deltaMovement, friction)
return this.deltaMovement
}
@@ -1920,44 +1900,46 @@ open class PokemonEntity(
if (!this.hasRider() || this.controllingPassenger == null) {
super.travel(movementInput)
} else {
val inp = ifRidingAvailableSupply(fallback = Vec3.ZERO) { behaviour, settings, state ->
behaviour.velocity(settings, state, this, this.controllingPassenger as Player, deltaMovement)
// Some rides move themselves, like climbers going over every surface
val travelled = ifRidingAvailableSupply(fallback = false) { behaviour, settings, state ->
behaviour.travel(settings, state, this, this.controllingPassenger as Player)
}
if (!travelled) {
// movementInput is the world space velocity getRiddenInput already got from the ride this tick
val v = ifRidingAvailableSupply(fallback = Vec3.ZERO) { behaviour, settings, state ->
if (behaviour.velocityRunsTwicePerTick(settings, state, this)) {
behaviour.velocity(settings, state, this, this.controllingPassenger as Player, deltaMovement)
} else {
movementInput
}
}
// Rotate velocity vector to face the current y rotation
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,
inp.z * g.toDouble() + inp.x * f.toDouble()
)
val diff = v.subtract(this.deltaMovement)
val diff = v.subtract(this.deltaMovement)
val inertia = ifRidingAvailableSupply(fallback = 0.5) { behaviour, settings, state ->
behaviour.inertia(settings, state,this)
}
val inertia = ifRidingAvailableSupply(fallback = 0.5) { behaviour, settings, state ->
behaviour.inertia(settings, state,this)
}
this.deltaMovement = this.deltaMovement.add( diff.scale(inertia) )
val triedMovement = this.deltaMovement
this.deltaMovement = this.deltaMovement.add( diff.scale(inertia) )
val triedMovement = this.deltaMovement
this.move(MoverType.SELF, this.deltaMovement)
this.move(MoverType.SELF, this.deltaMovement)
if (this.horizontalCollision && this.isControlledByLocalInstance) {
ifRidingAvailable { behaviour, settings, state ->
// Tried minus performed = vector pointing at where we *couldn't* go
val delta = triedMovement.subtract(this.deltaMovement)
if (behaviour.damageOnCollision(settings, state, this, delta)) {
DamageOnCollisionPacket(delta).sendToServer()
if (this.horizontalCollision && this.isControlledByLocalInstance) {
ifRidingAvailable { behaviour, settings, state ->
// Tried minus performed = vector pointing at where we *couldn't* go
val delta = triedMovement.subtract(this.deltaMovement)
if (behaviour.damageOnCollision(settings, state, this, delta)) {
DamageOnCollisionPacket(delta).sendToServer()
for (passenger in this.passengers) {
passenger.deltaMovement = this.deltaMovement
}
DismountPokemonPacket().sendToServer()
for (passenger in this.passengers) {
passenger.deltaMovement = this.deltaMovement
// Reset ride velocity
state.rideVelocity.set(state.rideVelocity.get().multiply(0.0, 1.0, 0.0))
}
DismountPokemonPacket().sendToServer()
// Reset ride velocity
state.rideVelocity.set(state.rideVelocity.get().multiply(0.0, 1.0, 0.0))
}
}
}
@@ -2390,6 +2372,11 @@ open class PokemonEntity(
this.jumpInputStrength = 0
}
// Same spot speed used to get called from so nothing shifts order
if (this.isControlledByLocalInstance) {
behaviour.driverTick(settings, state, this, driver)
}
}
}
@@ -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)
@@ -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,
/*
@@ -64,7 +64,7 @@
"rotation": "math.random_integer(0,3)*90"
},
"minecraft:particle_motion_parametric": {
"relative_position": ["-(v.target_deltax+((v.particle_random_1-0.5)*1.33))*(v.particle_age / v.particle_lifetime)", "(v.target_deltay+((v.particle_random_2-0.5)*1.33))*(v.particle_age / v.particle_lifetime)", "-v.target_deltaz*(v.particle_age / v.particle_lifetime)"]
"relative_position": ["-(v.target_deltax+((v.particle_random_1-0.5)*2))*(v.particle_age / v.particle_lifetime)", "(v.target_deltay+((v.particle_random_2-0.5)*2))*(v.particle_age / v.particle_lifetime)", "-v.target_deltaz*(v.particle_age / v.particle_lifetime)"]
},
"minecraft:particle_appearance_billboard": {
"size": ["0.4*v.rsize", "0.4*v.rsize"],
@@ -227,14 +227,6 @@
{"origin": [3.1, 20.8, -0.84], "size": [2, 1, 1], "inflate": 0.1, "uv": [60, 42]}
]
},
{
"name": "eyelid_closed_left",
"parent": "head_left",
"pivot": [3.6, 19.7, -0.91],
"cubes": [
{"origin": [2.1, 17.2, -0.66], "size": [3, 5, 1], "inflate": 0.25, "uv": [98, 2]}
]
},
{
"name": "face_ditto_left",
"parent": "head_left",
@@ -340,14 +332,6 @@
{"origin": [-5.1, 20.8, -0.84], "size": [2, 1, 1], "inflate": 0.1, "uv": [51, 42], "mirror": true}
]
},
{
"name": "eyelid_closed_right",
"parent": "head_right",
"pivot": [-3.6, 19.7, -0.91],
"cubes": [
{"origin": [-5.1, 17.2, -0.66], "size": [3, 5, 1], "inflate": 0.25, "uv": [89, 2], "mirror": true}
]
},
{
"name": "face_ditto_right",
"parent": "head_right",
@@ -134,246 +134,107 @@
"jaw2": [0, 27, -7.35]
}
},
{
"name": "head_left",
"parent": "head_angle",
"pivot": [3.53, 31, 0.25],
"rotation": [5, 0, 0],
"cubes": [
{"origin": [2.53, 29.5, -4.75], "size": [2, 5, 5], "uv": [51, 21], "mirror": true},
{"origin": [3.63, 31.5, 0.25], "size": [0, 3, 5], "uv": [40, 55], "mirror": true}
]
},
{
"name": "eye_left",
"parent": "head_left",
"pivot": [4.53, 31.5, -2.25],
"cubes": [
{"origin": [3.54, 29, -4.25], "size": [1, 5, 4], "uv": [88, 2]}
]
},
{
"name": "eye_neutral_left",
"parent": "eye_left",
"pivot": [4.54, 33, -2.25],
"cubes": [
{"origin": [3.54, 32, -4.25], "size": [1, 2, 4], "inflate": 0.021, "uv": [88, 12]}
]
},
{
"name": "eye_angry_left",
"parent": "eye_left",
"pivot": [4.44, 32.5, -2.25],
"cubes": [
{"origin": [3.44, 31, -4.25], "size": [1, 3, 4], "inflate": 0.021, "uv": [88, 26]},
{"origin": [3.44, 31.5, -3.25], "size": [1, 1, 1], "inflate": 0.03, "uv": [95, 27]}
]
},
{
"name": "eye_sad_left",
"parent": "eye_left",
"pivot": [4.43, 33, -2.25],
"cubes": [
{"origin": [3.44, 32, -4.25], "size": [1, 2, 4], "inflate": 0.021, "uv": [88, 19]},
{"origin": [3.44, 32.5, -3.25], "size": [1, 1, 1], "inflate": 0.03, "uv": [95, 20]}
]
},
{
"name": "pupil_left",
"parent": "eye_left",
"pivot": [4.55, 31, -3.05],
"cubes": [
{"origin": [3.55, 29.5, -4.05], "size": [1, 3, 2], "uv": [95, 0]}
]
},
{
"name": "eye_shine_left",
"parent": "pupil_left",
"pivot": [4.55, 31.5, -3.05],
"cubes": [
{"origin": [3.56, 30.5, -3.55], "size": [1, 2, 1], "uv": [106, 2]}
]
},
{
"name": "open_eye_left",
"parent": "head_left",
"pivot": [4.44, 31.5, -2.25],
"cubes": [
{"origin": [3.44, 29, -4.25], "size": [1, 5, 4], "uv": [99, 2]}
]
},
{
"name": "open_pupil_left",
"parent": "open_eye_left",
"pivot": [4.45, 31.5, -2.25],
"cubes": [
{"origin": [3.45, 31, -3.75], "size": [1, 1, 3], "uv": [99, 14]}
]
},
{
"name": "open_neutral_left",
"parent": "open_eye_left",
"pivot": [4.34, 33, -2.25],
"cubes": [
{"origin": [3.34, 32, -4.25], "size": [1, 2, 4], "inflate": 0.021, "uv": [88, 12]}
]
},
{
"name": "open_happy_left",
"parent": "open_eye_left",
"pivot": [4.35, 32, -2.25],
"cubes": [
{"origin": [3.35, 31, -3.75], "size": [1, 2, 3], "uv": [99, 20]}
]
},
{
"name": "eyelid_left",
"parent": "head_left",
"pivot": [4.44, 31.5, -2.25],
"cubes": [
{"origin": [3.44, 29, -4.25], "size": [1, 5, 4], "inflate": 0.022, "uv": [99, 26]}
]
},
{
"name": "locator_eye_left",
"parent": "head_left",
"pivot": [4.54, 31, -2.46],
"rotation": [0, -90, 0],
"locators": {
"eye2": [4.54, 31, -2.46]
}
},
{
"name": "face_ditto_left",
"parent": "head_left",
"pivot": [4.435, 32, -2.25]
},
{
"name": "eye_ditto_left",
"parent": "face_ditto_left",
"pivot": [4.435, 32, -2.25],
"cubes": [
{"origin": [4.435, 31.5, -2.75], "size": [0, 1, 1], "uv": [113, 1]}
]
},
{
"name": "eye_closed_ditto_left",
"parent": "face_ditto_left",
"pivot": [4.335, 32, -2.25],
"cubes": [
{"origin": [4.335, 31.5, -3.25], "size": [0, 1, 2], "uv": [112, 2]},
{"origin": [4.345, 32, -2.75], "size": [0, 1, 1], "uv": [113, 5]}
]
},
{
"name": "head_right",
"parent": "head_angle",
"pivot": [-3.53, 31, 0.25],
"pivot": [-3.525, 31, 0.25],
"rotation": [5, 0, 0],
"cubes": [
{"origin": [-4.53, 29.5, -4.75], "size": [2, 5, 5], "uv": [51, 11]},
{"origin": [-3.63, 31.5, 0.25], "size": [0, 3, 5], "uv": [40, 55]}
]
},
{
"name": "eye_right",
"parent": "head_right",
"pivot": [-4.53, 31.5, -2.25],
"cubes": [
{"origin": [-4.54, 29, -4.25], "size": [1, 5, 4], "uv": [66, 2], "mirror": true}
]
},
{
"name": "eye_neutral_right",
"parent": "eye_right",
"pivot": [-4.54, 33, -2.25],
"cubes": [
{"origin": [-4.54, 32, -4.25], "size": [1, 2, 4], "inflate": 0.021, "uv": [66, 12], "mirror": true}
]
},
{
"name": "eye_angry_right",
"parent": "eye_right",
"pivot": [-4.44, 32.5, -2.25],
"cubes": [
{"origin": [-4.44, 31, -4.25], "size": [1, 3, 4], "inflate": 0.021, "uv": [66, 26], "mirror": true},
{"origin": [-4.44, 31.5, -3.25], "size": [1, 1, 1], "inflate": 0.03, "uv": [73, 27], "mirror": true}
]
},
{
"name": "eye_sad_right",
"parent": "eye_right",
"pivot": [-4.43, 33, -2.25],
"cubes": [
{"origin": [-4.44, 32, -4.25], "size": [1, 2, 4], "inflate": 0.021, "uv": [66, 19], "mirror": true},
{"origin": [-4.44, 32.5, -3.25], "size": [1, 1, 1], "inflate": 0.03, "uv": [73, 20], "mirror": true}
]
},
{
"name": "pupil_right",
"parent": "eye_right",
"pivot": [-4.55, 31, -3.05],
"cubes": [
{"origin": [-4.55, 29.5, -4.05], "size": [1, 3, 2], "uv": [73, 0], "mirror": true}
]
},
{
"name": "eye_shine_right",
"parent": "pupil_right",
"pivot": [-4.55, 31.5, -3.05],
"cubes": [
{"origin": [-4.56, 30.5, -3.55], "size": [1, 2, 1], "uv": [84, 2], "mirror": true}
]
},
{
"name": "open_eye_right",
"parent": "head_right",
"pivot": [-4.44, 31.5, -2.25],
"cubes": [
{"origin": [-4.44, 29, -4.25], "size": [1, 5, 4], "uv": [77, 2], "mirror": true}
]
},
{
"name": "open_pupil_right",
"parent": "open_eye_right",
"pivot": [-4.45, 31.5, -2.25],
"cubes": [
{"origin": [-4.45, 31, -3.75], "size": [1, 1, 3], "uv": [77, 14], "mirror": true}
]
},
{
"name": "open_neutral_right",
"parent": "open_eye_right",
"pivot": [-4.34, 33, -2.25],
"cubes": [
{"origin": [-4.34, 32, -4.25], "size": [1, 2, 4], "inflate": 0.021, "uv": [66, 12], "mirror": true}
]
},
{
"name": "open_happy_right",
"parent": "open_eye_right",
"pivot": [-4.35, 32, -2.25],
"cubes": [
{"origin": [-4.35, 31, -3.75], "size": [1, 2, 3], "uv": [77, 20], "mirror": true}
]
},
{
"name": "eyelid_right",
"parent": "head_right",
"pivot": [-4.44, 31.5, -2.25],
"cubes": [
{"origin": [-4.44, 29, -4.25], "size": [1, 5, 4], "inflate": 0.022, "uv": [77, 26], "mirror": true}
{"origin": [-4.525, 29.5, -4.75], "size": [2, 5, 5], "uv": [51, 11]},
{"origin": [-3.625, 31.5, 0.25], "size": [0, 3, 5], "uv": [52, 55]}
]
},
{
"name": "locator_eye_right",
"parent": "head_right",
"pivot": [-4.54, 31, -2.46],
"pivot": [-4.5, 31, -2.5],
"rotation": [0, 90, 0],
"locators": {
"eye1": [-4.54, 31, -2.46]
"eye1": [-4.5, 31, -2.5]
}
},
{
"name": "eye_right",
"parent": "head_right",
"pivot": [-4.525, 31.35, -2.25],
"cubes": [
{"origin": [-4.525, 28.85, -4.25], "size": [1, 5, 4], "inflate": 0.01, "uv": [66, 2], "mirror": true}
]
},
{
"name": "eye_neutral_right",
"parent": "eye_right",
"pivot": [-4.525, 32.85095, -2.27179],
"cubes": [
{"origin": [-4.505, 31.85, -4.25], "size": [1, 2, 4], "inflate": 0.05, "uv": [66, 12], "mirror": true}
]
},
{
"name": "eye_angry_right",
"parent": "eye_right",
"pivot": [-4.435, 32.1, -2.5],
"cubes": [
{"origin": [-4.405, 30.85, -4.25], "size": [1, 3, 4], "inflate": 0.05, "uv": [66, 26], "mirror": true},
{"origin": [-4.44, 31.35, -3.27], "size": [1, 1, 1], "inflate": 0.02, "uv": [73, 27], "mirror": true}
]
},
{
"name": "eye_sad_right",
"parent": "eye_right",
"pivot": [-4.435, 32.85, -2.5],
"cubes": [
{"origin": [-4.405, 31.85, -4.25], "size": [1, 2, 4], "inflate": 0.05, "uv": [66, 19], "mirror": true},
{"origin": [-4.44, 32.35, -3.27], "size": [1, 1, 1], "inflate": 0.02, "uv": [73, 20], "mirror": true}
]
},
{
"name": "pupil_right",
"parent": "eye_right",
"pivot": [-4.525, 30.85095, -3.02179],
"cubes": [
{"origin": [-4.525, 29.35, -4.04], "size": [1, 3, 2], "inflate": 0.02, "uv": [73, 0], "mirror": true}
]
},
{
"name": "pupil_right2",
"parent": "pupil_right",
"pivot": [-4.55, 31.35095, -3.02179],
"cubes": [
{"origin": [-4.55, 30.35, -3.55], "size": [1, 2, 1], "uv": [84, 2], "mirror": true}
]
},
{
"name": "eyelid_right",
"parent": "head_right",
"pivot": [-3.925, 31.35095, -2.27179],
"cubes": [
{"origin": [-4.425, 28.85, -4.25], "size": [1, 5, 4], "inflate": 0.01, "uv": [77, 2], "mirror": true}
]
},
{
"name": "pupil_eyelid_right",
"parent": "eyelid_right",
"pivot": [-4.425, 30.80095, -2.27179],
"cubes": [
{"origin": [-4.435, 30.3, -3.75], "size": [1, 1, 3], "inflate": 0.01, "uv": [77, 14], "mirror": true}
]
},
{
"name": "eyelid_right2",
"parent": "eyelid_right",
"pivot": [-3.925, 32.85095, -2.27179],
"cubes": [
{"origin": [-4.405, 31.85, -4.25], "size": [1, 2, 4], "inflate": 0.05, "uv": [66, 12], "mirror": true}
]
},
{
"name": "pupil_happy_right",
"parent": "eyelid_right",
"pivot": [-4.325, 30.80095, -2.27179],
"cubes": [
{"origin": [-4.325, 30.30095, -3.77179], "size": [1, 2, 3], "inflate": 0.05, "uv": [77, 20], "mirror": true}
]
},
{
"name": "face_ditto_right",
"parent": "head_right",
@@ -384,7 +245,7 @@
"parent": "face_ditto_right",
"pivot": [-4.435, 32, -2.25],
"cubes": [
{"origin": [-4.435, 31.5, -2.75], "size": [0, 1, 1], "uv": [118, 1], "mirror": true}
{"origin": [-4.435, 31.5, -2.75], "size": [0, 1, 1], "uv": [113, 1]}
]
},
{
@@ -392,8 +253,131 @@
"parent": "face_ditto_right",
"pivot": [-4.335, 32, -2.25],
"cubes": [
{"origin": [-4.335, 31.5, -3.25], "size": [0, 1, 2], "uv": [117, 2], "mirror": true},
{"origin": [-4.345, 32, -2.75], "size": [0, 1, 1], "uv": [118, 5], "mirror": true}
{"origin": [-4.335, 31.5, -3.25], "size": [0, 1, 2], "uv": [112, 2]},
{"origin": [-4.345, 32, -2.75], "size": [0, 1, 1], "uv": [113, 5]}
]
},
{
"name": "head_left",
"parent": "head_angle",
"pivot": [3.525, 31, 0.25],
"rotation": [5, 0, 0],
"cubes": [
{"origin": [2.525, 29.5, -4.75], "size": [2, 5, 5], "uv": [51, 21], "mirror": true},
{"origin": [3.625, 31.5, 0.25], "size": [0, 3, 5], "uv": [40, 55], "mirror": true}
]
},
{
"name": "locator_eye_left",
"parent": "head_left",
"pivot": [4.5, 31, -2.5],
"rotation": [0, -90, 0],
"locators": {
"eye2": [4.5, 31, -2.5]
}
},
{
"name": "eye_left",
"parent": "head_left",
"pivot": [4.525, 31.35, -2.25],
"cubes": [
{"origin": [3.525, 28.85, -4.25], "size": [1, 5, 4], "inflate": 0.01, "uv": [88, 2]}
]
},
{
"name": "eye_neutral_left",
"parent": "eye_left",
"pivot": [4.525, 32.85095, -2.27179],
"cubes": [
{"origin": [3.505, 31.85, -4.25], "size": [1, 2, 4], "inflate": 0.05, "uv": [88, 12]}
]
},
{
"name": "eye_angry_left",
"parent": "eye_left",
"pivot": [4.435, 32.1, -2.5],
"cubes": [
{"origin": [3.405, 30.85, -4.25], "size": [1, 3, 4], "inflate": 0.05, "uv": [88, 26]},
{"origin": [3.44, 31.35, -3.27], "size": [1, 1, 1], "inflate": 0.02, "uv": [95, 27]}
]
},
{
"name": "eye_sad_left",
"parent": "eye_left",
"pivot": [4.435, 32.85, -2.5],
"cubes": [
{"origin": [3.405, 31.85, -4.25], "size": [1, 2, 4], "inflate": 0.05, "uv": [88, 19]},
{"origin": [3.44, 32.35, -3.27], "size": [1, 1, 1], "inflate": 0.02, "uv": [95, 20]}
]
},
{
"name": "pupil_left",
"parent": "eye_left",
"pivot": [4.525, 30.85095, -3.02179],
"cubes": [
{"origin": [3.525, 29.35, -4.04], "size": [1, 3, 2], "inflate": 0.02, "uv": [95, 0]}
]
},
{
"name": "pupil_left2",
"parent": "pupil_left",
"pivot": [4.55, 31.35095, -3.02179],
"cubes": [
{"origin": [3.55, 30.35, -3.55], "size": [1, 2, 1], "uv": [106, 2]}
]
},
{
"name": "eyelid_left",
"parent": "head_left",
"pivot": [3.925, 31.35095, -2.27179],
"cubes": [
{"origin": [3.425, 28.85, -4.25], "size": [1, 5, 4], "inflate": 0.01, "uv": [99, 2]}
]
},
{
"name": "pupil_eyelid_left",
"parent": "eyelid_left",
"pivot": [4.425, 30.80095, -2.27179],
"cubes": [
{"origin": [3.435, 30.3, -3.75], "size": [1, 1, 3], "inflate": 0.01, "uv": [99, 14]}
]
},
{
"name": "eyelid_left2",
"parent": "eyelid_left",
"pivot": [3.925, 32.85095, -2.27179],
"cubes": [
{"origin": [3.405, 31.85, -4.25], "size": [1, 2, 4], "inflate": 0.05, "uv": [88, 12]}
]
},
{
"name": "pupil_happy_left",
"parent": "eyelid_left",
"pivot": [4.325, 30.80095, -2.27179],
"cubes": [
{"origin": [3.325, 30.30095, -3.77179], "size": [1, 2, 3], "inflate": 0.05, "uv": [99, 20]}
]
},
{
"name": "face_ditto_left",
"parent": "head_left",
"pivot": [4.435, 32, -2.25]
},
{
"name": "eye_ditto_left",
"parent": "face_ditto_left",
"pivot": [4.435, 32, -2.25],
"cubes": [
{"origin": [4.435, 31.5, -2.75], "size": [0, 1, 1], "uv": [118, 1], "mirror": true}
]
},
{
"name": "eye_closed_ditto_left",
"parent": "face_ditto_left",
"pivot": [4.335, 32, -2.25],
"cubes": [
{"origin": [4.335, 31.5, -3.25], "size": [0, 1, 2], "uv": [117, 2], "mirror": true},
{"origin": [4.345, 32, -2.75], "size": [0, 1, 1], "uv": [118, 5], "mirror": true}
]
},
{
@@ -830,6 +830,11 @@
{"origin": [-3.5, 38.3, 33.75], "size": [7, 5, 9], "uv": [106, 33]}
]
},
{
"name": "tongue6",
"parent": "tongue5",
"pivot": [0, 40.8, 40.5]
},
{
"name": "muzzle",
"parent": "head_angle",
@@ -908,15 +913,6 @@
{"origin": [-0.1, 44.3, -8.11], "size": [4, 2, 1], "uv": [87, 72]}
]
},
{
"name": "eyelid_base_left",
"parent": "eye_left_base",
"pivot": [1.9, 43.9, -8.11],
"cubes": [
{"origin": [-0.1, 45.3, -8.11], "size": [4, 1, 1], "uv": [87, 76]},
{"origin": [-0.1, 41.9, -8.11], "size": [4, 4, 1], "uv": [87, 79]}
]
},
{
"name": "eye_left",
"parent": "eye_left_base",
@@ -954,7 +950,7 @@
"parent": "eye_left",
"pivot": [1.9, 44.2, -8.07],
"cubes": [
{"origin": [0.4, 43.7, -8.07], "size": [3, 1, 1], "inflate": 0.02, "uv": [87, 85]}
{"origin": [0.4, 43.7, -8.07], "size": [3, 1, 1], "inflate": 0.02, "uv": [87, 76]}
]
},
{
@@ -962,7 +958,7 @@
"parent": "eye_left",
"pivot": [1.9, 44.2, -8.07],
"cubes": [
{"origin": [0.4, 42.95, -8.07], "size": [3, 2, 1], "inflate": 0.02, "uv": [87, 88]}
{"origin": [0.4, 42.95, -8.07], "size": [3, 2, 1], "inflate": 0.02, "uv": [87, 79]}
]
},
{
@@ -999,15 +995,6 @@
{"origin": [-3.9, 44.3, -8.11], "size": [4, 2, 1], "uv": [76, 72], "mirror": true}
]
},
{
"name": "eyelid_base_right",
"parent": "eye_right_base",
"pivot": [-1.9, 43.9, -8.11],
"cubes": [
{"origin": [-3.9, 45.3, -8.11], "size": [4, 1, 1], "uv": [76, 76], "mirror": true},
{"origin": [-3.9, 41.9, -8.11], "size": [4, 4, 1], "uv": [76, 79], "mirror": true}
]
},
{
"name": "eye_right",
"parent": "eye_right_base",
@@ -1045,7 +1032,7 @@
"parent": "eye_right",
"pivot": [-1.9, 44.2, -8.07],
"cubes": [
{"origin": [-3.4, 43.7, -8.07], "size": [3, 1, 1], "inflate": 0.02, "uv": [76, 85], "mirror": true}
{"origin": [-3.4, 43.7, -8.07], "size": [3, 1, 1], "inflate": 0.02, "uv": [76, 76], "mirror": true}
]
},
{
@@ -1053,7 +1040,7 @@
"parent": "eye_right",
"pivot": [-1.9, 44.2, -8.07],
"cubes": [
{"origin": [-3.4, 42.95, -8.07], "size": [3, 2, 1], "inflate": 0.02, "uv": [76, 88], "mirror": true}
{"origin": [-3.4, 42.95, -8.07], "size": [3, 2, 1], "inflate": 0.02, "uv": [76, 79], "mirror": true}
]
},
{
@@ -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]
}
}
]
}
@@ -8,6 +8,7 @@
"poses": {
"standing": {
"poseTypes": ["STAND", "NONE", "PORTRAIT", "PROFILE"],
"isBattle": false,
"animations": [
"q.look('neck', 1, 1, 70, -10, 20, -20)",
"q.look('head_ai')",
@@ -34,9 +35,6 @@
"part": "wing_closed_left",
"isVisible": true
}
],
"quirks": [
"q.bedrock_quirk('fearow', 'blink')"
]
},
"walking": {
@@ -67,9 +65,6 @@
"part": "wing_closed_left",
"isVisible": true
}
],
"quirks": [
"q.bedrock_quirk('fearow', 'blink')"
]
},
"hover": {
@@ -101,9 +96,6 @@
"part": "wing_closed_left",
"isVisible": false
}
],
"quirks": [
"q.bedrock_quirk('fearow', 'blink')"
]
},
"fly": {
@@ -136,9 +128,6 @@
"part": "wing_closed_left",
"isVisible": false
}
],
"quirks": [
"q.bedrock_quirk('fearow', 'blink')"
]
},
"sleep": {
@@ -27,7 +27,7 @@
"q.look('head_ai')",
"q.bedrock('froakie', 'ground_idle')"
],
"quirks": ["q.bedrock_quirk('froakie', 'blink')"]
"quirks": []
},
"walking": {
"poseTypes": ["WALK"],
@@ -36,7 +36,7 @@
"q.look('head_ai')",
"q.bedrock('froakie', 'ground_walk')"
],
"quirks": ["q.bedrock_quirk('froakie', 'blink')"]
"quirks": []
},
"swimming": {
"poseTypes": ["WALK","SWIM"],
@@ -46,7 +46,7 @@
"q.look('head_ai')",
"q.bedrock('froakie', 'water_swim')"
],
"quirks": ["q.bedrock_quirk('froakie', 'blink')"]
"quirks": []
},
"float": {
"poseTypes": ["STAND","FLOAT"],
@@ -55,7 +55,7 @@
"q.look('head_ai')",
"q.bedrock('froakie', 'water_idle')"
],
"quirks": ["q.bedrock_quirk('froakie', 'blink')"]
"quirks": []
},
"sleep": {
"poseTypes": ["SLEEP"],
@@ -27,7 +27,7 @@
"q.look('head_ai', 1, 1, 5, -45, 45, -45)",
"q.bedrock('frogadier', 'ground_idle')"
],
"quirks": ["q.bedrock_quirk('frogadier', 'blink')"]
"quirks": []
},
"walking": {
"poseTypes": ["WALK"],
@@ -36,7 +36,7 @@
"q.look('head_ai', 1, 1, 0, -45, 45, -45)",
"q.bedrock('frogadier', 'ground_walk')"
],
"quirks": ["q.bedrock_quirk('frogadier', 'blink')"]
"quirks": []
},
"swimming": {
"poseTypes": ["WALK","SWIM"],
@@ -46,7 +46,7 @@
"q.look('head_ai')",
"q.bedrock('frogadier', 'water_swim')"
],
"quirks": ["q.bedrock_quirk('frogadier', 'blink')"]
"quirks": []
},
"float": {
"poseTypes": ["STAND","FLOAT"],
@@ -55,7 +55,7 @@
"q.look('head_ai')",
"q.bedrock('frogadier', 'water_idle')"
],
"quirks": ["q.bedrock_quirk('frogadier', 'blink')"]
"quirks": []
},
"sleep": {
"poseTypes": ["SLEEP"],
@@ -66,7 +66,7 @@
"q.bedrock('greninja', math.random(0, 1) < 0.4 ? 'ground_idle_alt' : 'ground_idle')"
],
"quirks": [
"q.bedrock_quirk('greninja', 'blink')","q.bedrock_quirk('greninja', q.array('quirk_look_left', 'quirk_look_right'), 8, 30, 1)"
"q.bedrock_quirk('greninja', q.array('quirk_look_left', 'quirk_look_right'), 8, 30, 1)"
]
},
"walking": {
@@ -78,7 +78,7 @@
"q.look('tongue1', -1, 1, 70, -45, 0, 0)",
"q.bedrock('greninja', 'ground_walk')"
],
"quirks": ["q.bedrock_quirk('greninja', 'blink')"]
"quirks": []
},
"running": {
"poseTypes": ["WALK"],
@@ -89,7 +89,7 @@
"q.look('tongue1', -1, 1, 70, -45, 0, 0)",
"q.bedrock('greninja', 'ground_run')"
],
"quirks": ["q.bedrock_quirk('greninja', 'blink')"]
"quirks": []
},
"surface-swimming": {
"poseTypes": ["WALK","SWIM"],
@@ -101,7 +101,7 @@
"q.look('tongue1', -1, 1, 70, -45, 0, 0)",
"q.bedrock('greninja', 'water_swim')"
],
"quirks": ["q.bedrock_quirk('greninja', 'blink')"],
"quirks": [],
"transformedParts": [
{
"part": "body",
@@ -118,7 +118,7 @@
"q.look('tongue1', -1, 1, 70, -45, 0, 0)",
"q.bedrock('greninja', 'water_idle')"
],
"quirks": ["q.bedrock_quirk('greninja', 'blink')"],
"quirks": [],
"transformedParts": [
{
"part": "body",
@@ -136,7 +136,7 @@
"q.look('tongue1', -1, 1, 70, -45, 0, 0)",
"q.bedrock('greninja', 'water_swim')"
],
"quirks": ["q.bedrock_quirk('greninja', 'blink')"],
"quirks": [],
"transformedParts": [
{
"part": "body",
@@ -153,7 +153,7 @@
"q.look('tongue1', -1, 1, 70, -45, 0, 0)",
"q.bedrock('greninja', 'water_idle')"
],
"quirks": ["q.bedrock_quirk('greninja', 'blink')"],
"quirks": [],
"transformedParts": [
{
"part": "body",
@@ -383,7 +383,7 @@
"item.cobblemon.eject_pack.tooltip_1": "Switches out the holder if any stats are lowered",
"item.cobblemon.eject_pack.tooltip_2": "Consumed after use",
"item.cobblemon.electric_seed": "Electric Seed",
"item.cobblemon.electric_seed.tooltip": "Boosts the holder's Defense in Electric Terrain",
"item.cobblemon.electric_seed.tooltip": "Boosts the holder's Defense in Electric terrain",
"item.cobblemon.everstone": "Everstone",
"item.cobblemon.everstone.tooltip": "Prevents the holder from evolving",
"item.cobblemon.eviolite": "Eviolite",
@@ -402,7 +402,7 @@
"item.cobblemon.focus_sash.tooltip_1": "Prevents the holder from fainting in a single hit",
"item.cobblemon.focus_sash.tooltip_2": "Consumed after use",
"item.cobblemon.grassy_seed": "Grassy Seed",
"item.cobblemon.grassy_seed.tooltip": "Boosts the holder's Defense in Grassy Terrain",
"item.cobblemon.grassy_seed.tooltip": "Boosts the holder's Defense in Grassy terrain",
"item.cobblemon.grip_claw": "Grip Claw",
"item.cobblemon.hard_stone": "Hard Stone",
"item.cobblemon.hard_stone.tooltip": "Boosts the power of the holder's Rock-type moves",
@@ -507,7 +507,7 @@
"item.cobblemon.mirror_herb.tooltip_1": "Allows the holder to mirror an opponent's stat increases to boost its own stats",
"item.cobblemon.mirror_herb.tooltip_2": "Consumed after use",
"item.cobblemon.misty_seed": "Misty Seed",
"item.cobblemon.misty_seed.tooltip": "Boosts the holder's Sp. Def in Misty Terrain",
"item.cobblemon.misty_seed.tooltip": "Boosts the holder's Sp. Def in Misty terrain",
"item.cobblemon.power_herb": "Power Herb",
"item.cobblemon.power_herb.tooltip_1": "Allows the holder to immediately use a move that normally requires a change of turn to charge",
"item.cobblemon.power_herb.tooltip_2": "Consumed after use",
@@ -535,7 +535,7 @@
"item.cobblemon.power_weight": "Power Weight",
"item.cobblemon.power_weight.tooltip": "Grants 8 HP EVs when gaining experience in battle",
"item.cobblemon.psychic_seed": "Psychic Seed",
"item.cobblemon.psychic_seed.tooltip": "Boosts the holder's Sp. Def in Psychic Terrain",
"item.cobblemon.psychic_seed.tooltip": "Boosts the holder's Sp. Def in Psychic terrain",
"item.cobblemon.flame_orb": "Flame Orb",
"item.cobblemon.flame_orb.tooltip": "Burns the holder",
"item.cobblemon.smoke_ball": "Smoke Ball",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 417 B

After

Width:  |  Height:  |  Size: 788 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 13 KiB

@@ -42,8 +42,7 @@
},
{
"type": "pause",
"condition": "q.target_fainted == false",
"pause": 1.25
"pause": 0.5
}
]
}
@@ -141,8 +141,7 @@
},
{
"type": "pause",
"condition": "q.target_fainted == false",
"pause": 1.25
"pause": 0.5
}
]
}
@@ -1,7 +1,7 @@
{
"infiniteAltitude": "false",
"infiniteStamina": "false",
"timeToRollCorrect": "0.5",
"timeToRollCorrect": "0.25",
"handlingExpr": "q.get_ride_stats('SKILL', 'AIR', 45.0, 10.0)",
"horizontalPitchExpr": "q.get_ride_stats('SKILL', 'AIR', 0.2, 0.1)",
"mouseInputMult": "q.get_ride_stats('SKILL', 'AIR', 1.2, 0.8)",
@@ -11,5 +11,13 @@
"stamDrainHorzMod": "1.5",
"stamDrainPercRateGlide": "0.25",
"stamDrainPerRateDive": "0.25",
"glideSpeedExpr": "q.get_ride_stats('JUMP', 'AIR', 2.0, 1.0)"
"glideSpeedExpr": "q.get_ride_stats('JUMP', 'AIR', 2.0, 1.0)",
"cameraRollEngage": "46.0",
"cameraRollRelease": "30.0",
"cameraRollHalflife": "0.25",
"hoverLookYawLimit": "90.0",
"turnRollLimit": "45.0",
"maxRoll": "45.0",
"rollSettleAngle": "5.0",
"handRollShare": "0.5"
}
@@ -0,0 +1,25 @@
{
"infiniteStamina": "false",
"canJump": "true",
"canSprint": "true",
"lookYawLimit": "105.0",
"speedExpr": "q.get_ride_stats('SPEED', 'LAND', 0.45, 0.2)",
"accelerationExpr": "q.get_ride_stats('ACCELERATION', 'LAND', 0.05, 6.0)",
"staminaExpr": "q.get_ride_stats('STAMINA', 'LAND', 10.0, 4.0)",
"jumpExpr": "q.get_ride_stats('JUMP', 'LAND', 5.0, 2.0)",
"handlingExpr": "q.get_ride_stats('SKILL', 'LAND', 180.0, 100.0)",
"handRollShare": "0.5",
"cameraTiltEngage": "50.0",
"cameraTiltRelease": "40.0",
"cameraHalflife": "0.3",
"maxTilt": "50.0",
"sideGrip": "0.05",
"topSpeedTurnShare": "0.6",
"topSpeedFovBoost": "0.1",
"lookRecenterDelay": "1.0",
"hoverHeight": "1.0",
"jumpChargeSeconds": "0.75",
"sprintSpeedFactor": "2.0",
"sprintTurnShare": "0.5",
"sprintFovBump": "0.1"
}
@@ -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,