Compare commits

..
67 changed files with 267 additions and 2742 deletions
-2
View File
@@ -13,7 +13,6 @@
### Changes
- Renamed `chargeGainedPerTick` config to `secondsToChargeHealingMachine`.
- Made Blocks of Gold count as Big Nuggets when held by a Pokémon (for Fling functionality)
### Fixes
- Fixed Particles sometimes facing the wrong direction (looking at you, Swords Dance)
@@ -55,7 +54,6 @@
- The `itemCondition` for a held item evolution requirement
- The `fossils` for a fossil entry
- Added MoLang flows for `poke_ball_capture_calculated`, `evolution_tested`, `evolution_accepted`, `evolution_completed`
- Added `interpolate` boolean property to animated textures to allow gradual colour changes between frames.
## [1.6.1 (January 26th, 2025)](#1-6-1)
@@ -9,9 +9,6 @@
package com.cobblemon.mod.common;
import net.minecraft.util.Mth;
import net.minecraft.util.SmoothDouble;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix3f;
import org.joml.Vector3f;
@@ -120,24 +117,8 @@ public interface Rollable {
return (float) Mth.wrapDegrees(-Math.toDegrees(getUpVector().angleSigned(UP, getForwardVector())));
}
/**
* Get the angular velocity in degrees per second as a Vec3
*/
Vec3 angRollVel( double deltaTime );
public Vec3 rotationOnMouseXY(
double yMouse,
double xMouse,
SmoothDouble yMouseSmoother,
SmoothDouble xMouseSmoother,
double sensitivity,
double deltaTime
);
boolean shouldRoll();
boolean useAngVelSmoothing();
void clearRotation();
}
@@ -1,38 +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.mixin;
import net.minecraft.client.player.LocalPlayer;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(LocalPlayer.class)
public abstract class LocalPlayerMixin {
@Shadow
private float jumpRidingScale;
@Inject(method = "getJumpRidingScale", at = @At("HEAD"), cancellable = true)
public void modifyJumpRidingScale(CallbackInfoReturnable<Float> cir) {
LocalPlayer player = (LocalPlayer) (Object) this;
if (player.isPassenger() && player.getVehicle() instanceof PokemonEntity) {
PokemonEntity ride = (PokemonEntity) player.getVehicle();
//Use custom jump bar logic if the current ride does not jump using it.
if( !ride.getRiding().canJump(ride, player))
{
cir.setReturnValue(ride.setRideBar());
}
}
}
}
@@ -38,7 +38,6 @@ import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.SmoothDouble;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
@@ -48,7 +47,6 @@ import net.minecraft.world.food.FoodProperties;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix3f;
import org.objectweb.asm.Opcodes;
@@ -57,7 +55,6 @@ import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@@ -274,7 +271,6 @@ public abstract class PlayerMixin extends LivingEntity implements Rollable, Scan
@Override
public Rollable updateOrientation(Function<Matrix3f, Matrix3f> update) {
//Disabled to allow non mouse inputs to modify roll
if (!shouldRoll()) return this;
if (this.orientation == null) {
@@ -300,60 +296,6 @@ public abstract class PlayerMixin extends LivingEntity implements Rollable, Scan
return pokemonEntity.getRiding().shouldRoll(pokemonEntity);
}
@Override
public Vec3 rotationOnMouseXY(
double yMouse,
double xMouse,
SmoothDouble yMouseSmoother,
SmoothDouble xMouseSmoother,
double sensitivity,
double deltaTime
) {
LocalPlayer player = Minecraft.getInstance().player;
if (!this.equals(player)) return Vec3.ZERO;
var playerVehicle = player.getVehicle();
if (playerVehicle == null) return Vec3.ZERO;
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return Vec3.ZERO;
return pokemonEntity.getRiding().rotationOnMouseXY(
pokemonEntity,
player,
yMouse,
xMouse,
yMouseSmoother,
xMouseSmoother,
sensitivity,
deltaTime
);
}
@Override
public boolean useAngVelSmoothing() {
LocalPlayer player = Minecraft.getInstance().player;
if (!this.equals(player)) return true;
var playerVehicle = player.getVehicle();
if (playerVehicle == null) return true;
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return true;
return pokemonEntity.getRiding().useAngVelSmoothing(pokemonEntity);
}
@Override
public Vec3 angRollVel( double deltaTime ) {
var ret = new Vec3(0.0, 0.0, 0.0);
LocalPlayer player = Minecraft.getInstance().player;
if (!this.equals(player)) return ret;
var playerVehicle = player.getVehicle();
if (playerVehicle == null) return ret;
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return ret;
return pokemonEntity.getRiding().angRollVel( pokemonEntity, player, deltaTime );
}
@Override
public void clearRotation() {
this.orientation = null;
@@ -9,10 +9,8 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.client.CobblemonClient;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.player.LocalPlayer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@@ -32,15 +30,5 @@ public class AbstractClientPlayerMixin {
if (usageContext.getScanningGuiOpen()) {
cir.setReturnValue(usageContext.getFovMultiplier());
}
//TODO: find a better solution that just tagging this logic on here. Likely just change it to a more general
//name?
LocalPlayer player = (LocalPlayer) (Object) this;
if (player != null && player.isPassenger() && player.getVehicle() instanceof PokemonEntity) {
PokemonEntity ride = (PokemonEntity) player.getVehicle();
//Return custom fov mult
cir.setReturnValue(ride.rideFovMult());
}
}
}
@@ -9,9 +9,18 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.Rollable;
import com.cobblemon.mod.common.api.riding.Rideable;
import com.cobblemon.mod.common.api.riding.Seat;
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate;
import com.cobblemon.mod.common.client.render.MatrixWrapper;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.Vec3;
import org.joml.Matrix3f;
import org.joml.Quaternionf;
import org.joml.Vector3f;
@@ -36,9 +45,12 @@ public abstract class CameraMixin {
@Shadow private float yRot;
@Shadow public abstract float getXRot();
@Shadow public abstract float getYRot();
@Shadow private Vec3 position;
@Shadow private float eyeHeight;
@Shadow private float eyeHeightOld;
@Unique private float returnTimer = 0;
@Unique private float rollAngleStart = 0;
@Unique Minecraft minecraft = Minecraft.getInstance();
@@ -80,6 +92,48 @@ public abstract class CameraMixin {
ci.cancel();
}
//If you want to move this to a delagate you need an AW for position
@WrapOperation(method = "setup", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setPosition(DDD)V"))
public void positionCamera(Camera instance, double x, double y, double z, Operation<Void> original){
Entity entity = instance.getEntity();
Entity vehicle = entity.getVehicle();
if(vehicle instanceof Rideable){
//RidingCameraDelagate.INSTANCE.positionCamera(instance, x, y, z, original);
if(!(vehicle instanceof PokemonEntity)){
original.call(instance, x, y, z);
return;
}
PokemonEntity pokemon = (PokemonEntity) vehicle;
int seatIndex = pokemon.getPassengers().indexOf(entity);
Seat seat = pokemon.getSeats().get(seatIndex);
PokemonClientDelegate delegate = (PokemonClientDelegate) pokemon.getDelegate();
MatrixWrapper locator = delegate.getLocatorStates().get(seat.getLocator());
if(locator == null){
original.call(instance, x, y, z);
return;
}
Vec3 locatorOffset = new Vec3(locator.getMatrix().getTranslation(new Vector3f()));
Vec3 entityPos = new Vec3(
Mth.lerp(instance.getPartialTickTime(), pokemon.xOld, pokemon.getX()),
Mth.lerp(instance.getPartialTickTime(), pokemon.yOld, pokemon.getY()),
Mth.lerp(instance.getPartialTickTime(), pokemon.zOld, pokemon.getZ())
);
Rollable rollable = (Rollable) entity;
float currEyeHeight = Mth.lerp(instance.getPartialTickTime(), eyeHeightOld, eyeHeight);
Matrix3f orientation = rollable.shouldRoll() && rollable.getOrientation() != null ? rollable.getOrientation() : new Matrix3f();
Vec3 rotatedEyeHeight = new Vec3(orientation.transform(new Vector3f(0f, currEyeHeight, 0f)));
position = locatorOffset.add(entityPos).add(rotatedEyeHeight);
} else {
original.call(instance, x, y, z);
}
}
@Unique
private void applyRotation(){
if (!(this.entity instanceof Rollable rollable) || rollable.getOrientation() == null) return;
@@ -14,13 +14,11 @@ import com.cobblemon.mod.common.client.keybind.keybinds.PartySendBinding;
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 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;
@@ -35,11 +33,8 @@ public class MouseHandlerMixin {
@Shadow private double accumulatedScrollY;
@Shadow @Final private Minecraft minecraft;
@Unique SmoothDouble xMouseSmoother = new SmoothDouble();
@Unique SmoothDouble yMouseSmoother = new SmoothDouble();
@Unique SmoothDouble pitchSmoother = new SmoothDouble();
@Unique SmoothDouble rollSmoother = new SmoothDouble();
@Unique SmoothDouble yawSmoother = new SmoothDouble();
@Shadow @Final private SmoothDouble smoothTurnY;
@@ -49,8 +44,6 @@ public class MouseHandlerMixin {
@Shadow private double accumulatedDY;
@Shadow private double accumulatedScrollX;
@Inject(
method = "onScroll",
at = @At(
@@ -102,13 +95,7 @@ public class MouseHandlerMixin {
target = "Lnet/minecraft/client/player/LocalPlayer;turn(DD)V"
)
)
public boolean cobblemon$modifyRotation(
LocalPlayer player,
double cursorDeltaX,
double cursorDeltaY,
@Local(argsOnly = true) double d,
@Local(argsOnly = true) double movementTime
) {
public boolean cobblemon$modifyRotation(LocalPlayer player, double cursorDeltaX, double cursorDeltaY, @Local(argsOnly = true) double d) {
PokedexUsageContext usageContext = CobblemonClient.INSTANCE.getPokedexUsageContext();
if (usageContext.getScanningGuiOpen()) {
this.smoothTurnY.reset();
@@ -122,66 +109,16 @@ public class MouseHandlerMixin {
}
if (!(player instanceof Rollable rollable)) return true;
if (!rollable.shouldRoll()) {
xMouseSmoother.reset();
yMouseSmoother.reset();
pitchSmoother.reset();
rollSmoother.reset();
yawSmoother.reset();
return true;
}
var defaultSensitivity = this.minecraft.options.sensitivity().get() * 0.6000000238418579 + 0.20000000298023224;
var ridingSensitivity = Math.pow(defaultSensitivity, 3);
//Send mouse input to be interpreted into rotation
//deltas by the ride controller
Vec3 angVecMouse = rollable.rotationOnMouseXY(
cursorDeltaY,
cursorDeltaX ,
yMouseSmoother,
xMouseSmoother,
ridingSensitivity,
movementTime
);
//Perform Rotation using mouse influenced rotation deltas.
rollable.rotate(
(float) angVecMouse.x,
(float) angVecMouse.y,
(float) angVecMouse.z
);
//Gather and apply the current rotation deltas
var angRot = rollable.angRollVel(movementTime);
//Apply smoothing if requested by the controller.
//This Might be best if done by the controller itself?
if( rollable.useAngVelSmoothing() )
{
if( angRot != null ) {
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);
rollable.rotate((float) yaw, (float) pitch, (float) roll);
}
}
//Otherwise simply apply the smoothing
else
{
if( angRot != null ) {
rollable.rotate((float) (
angRot.x * 10 * d),
(float) (angRot.y * 10 * d),
(float) (angRot.z * 10 * d)
);
}
}
var pitch = pitchSmoother.getNewDeltaValue(cursorDeltaY * 0.15f, d);
var roll = rollSmoother.getNewDeltaValue(cursorDeltaX * 0.15f, d);
rollable.rotate(0.0F, (float)pitch, (float)roll);
return false;
}
@Inject(method = "handleAccumulatedMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MouseHandler;isMouseGrabbed()Z", ordinal = 0))
@@ -1278,7 +1278,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
CobblemonHeldItemManager.registerRemap(it, remappedName)
CobblemonHeldItemManager.registerRemap(Items.BONE, "thickclub")
CobblemonHeldItemManager.registerRemap(Items.SNOWBALL, "snowball")
CobblemonHeldItemManager.registerRemap(Items.GOLD_BLOCK, "bignugget")
}
}
)
@@ -28,7 +28,7 @@ class BattleCountableCriterion(
val CODEC: Codec<BattleCountableCriterion> = RecordCodecBuilder.create { it.group(
ContextAwarePredicate.CODEC.optionalFieldOf("player").forGetter(BattleCountableCriterion::playerCtx),
Codec.STRING.listOf().optionalFieldOf("battle_types", listOf("any")).forGetter(BattleCountableCriterion::battleTypes),
Codec.INT.optionalFieldOf("count", 0).forGetter(BattleCountableCriterion::count)
Codec.INT.optionalFieldOf( "count", 0).forGetter(BattleCountableCriterion::count)
).apply(it, ::BattleCountableCriterion) }
}
@@ -31,7 +31,7 @@ data class PokemonInteractionGUICreationEvent(
private fun getNextFreeOrientation(): Orientation {
var largest = Orientation.TOP_LEFT
for (orientation in Orientation.entries) {
for(orientation in Orientation.values()) {
if(!options.containsKey(orientation)) {
return orientation
} else {
@@ -39,8 +39,6 @@ import com.cobblemon.mod.common.api.pokedex.SeenPercent
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.storage.PokemonStore
@@ -166,7 +166,7 @@ open class MoveTemplate(
// TODO: Handle Liquid Voice: need to know what moves have the sound flag
// TODO: Handle weatherball, naturalgift, judgement, technoblast, terrainpulse, and terrablast
if (this.elementalType == ElementalTypes.NORMAL) {
if (this.damageCategory != DamageCategories.STATUS) {
if( this.damageCategory != DamageCategories.STATUS) {
return when (pokemon.ability.name) {
"pixilate" -> ElementalTypes.FAIRY
"aerilate" -> ElementalTypes.FLYING
@@ -61,7 +61,6 @@ import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.sounds.SoundEvent
import net.minecraft.world.effect.MobEffect
import net.minecraft.world.entity.EntityDimensions
import net.minecraft.world.item.Item
@@ -109,7 +108,6 @@ object PokemonSpecies : JsonDataRegistry<Species> {
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
.registerTypeAdapter(RideController::class.java, RideControllerAdapter)
.registerTypeAdapter(RidingStatDefinition::class.java, RidingStatDefinitionAdapter)
.registerTypeAdapter(SoundEvent::class.java, SoundEventAdapter)
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
.disableHtmlEscaping()
.enableComplexMapKeySerialization()
@@ -176,7 +176,7 @@ interface Evolution : EvolutionLike {
pokemonEntity.after(11.2F) {
evolutionMethod(pokemon)
}
pokemonEntity.after(seconds = 12F) {
pokemonEntity.after( seconds = 12F ) {
cryAnimation(pokemonEntity)
pokemonEntity.entityData.set(PokemonEntity.EVOLUTION_STARTED, false)
pokemon.getOwnerPlayer()?.sendSystemMessage(lang("ui.evolve.into", preEvoName, pokemon.species.translatedName))
@@ -10,23 +10,15 @@ package com.cobblemon.mod.common.api.riding
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.toProperties
import com.cobblemon.mod.common.util.withQueryValue
import net.minecraft.ChatFormatting
import net.minecraft.network.chat.Component
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 kotlin.math.sin
import kotlin.math.sin
data class RidingManager(val entity: PokemonEntity) {
var lastSpeed = 0F
@@ -39,17 +31,6 @@ data class RidingManager(val entity: PokemonEntity) {
.withQueryValue("entity", entity.struct)
.also {
it.environment.query.addFunction("passenger_count") { DoubleValue(entity.passengers.size.toDouble()) }
it.environment.query.addFunction("get_ride_stats") { params ->
val rideStat = RidingStat.valueOf(params.getString(0).uppercase())
val rideStyle = RidingStyle.valueOf(params.getString(1).uppercase())
val maxVal = params.getDouble(2)
val minVal = params.getDouble(3)
//TODO: Use the mons actual boost once implemented
val normalizedStat = entity.rideProp.calculate(rideStat, rideStyle, 0) / 100.0f
val trueStatVal = (normalizedStat * (maxVal - minVal)) + minVal
DoubleValue(trueStatVal)
}
}
}
@@ -77,17 +58,8 @@ data class RidingManager(val entity: PokemonEntity) {
val pose = controller.pose(entity)
entity.entityData.set(PokemonEntity.POSE_TYPE, pose)
//val speedPlayer = driver.deltaMovement.horizontalDistance()
//driver.displayClientMessage(Component.literal("Speed player: ").withStyle { it.withColor(ChatFormatting.GREEN) }.append(Component.literal("$speedPlayer b/t")), true)
//val speedEntity = entity.deltaMovement.length() //* 20 * 60 * 60) / ( 1000 )
//println(speedEntity)
//driver.displayClientMessage(Component.literal("Speed: ").withStyle { it.withColor(ChatFormatting.GREEN) }.append(Component.literal("${String.format("%.2f", speedEntity)} km/h")), true)
//val speedPlayer = driver.deltaMovement.horizontalDistance()
//driver.displayClientMessage(Component.literal("Speed player: ").withStyle { it.withColor(ChatFormatting.GREEN) }.append(Component.literal("$speedPlayer b/t")), true)
//val speedEntity = entity.deltaMovement.length() //* 20 * 60 * 60) / ( 1000 )
//println(speedEntity)
//driver.displayClientMessage(Component.literal("Speed: ").withStyle { it.withColor(ChatFormatting.GREEN) }.append(Component.literal("${String.format("%.2f", speedEntity)} km/h")), true)
// driver.displayClientMessage(Component.literal("Speed: ").withStyle { it.withColor(ChatFormatting.GREEN) }.append(Component.literal("$lastSpeed b/t")), true)
}
fun speed(entity: PokemonEntity, driver: Player): Float {
@@ -119,16 +91,6 @@ data class RidingManager(val entity: PokemonEntity) {
return controller.velocity(entity, driver, input)
}
fun setRideBar(entity: PokemonEntity, driver: Player): Float {
val controller = getController(entity) ?: return 0.0f
return controller.setRideBar(entity, driver)
}
fun rideFovMult(entity: PokemonEntity, driver: Player): Float {
val controller = getController(entity) ?: return 1.0f
return controller.rideFovMult(entity, driver)
}
fun canJump(entity: PokemonEntity, driver: Player): Boolean {
val controller = getController(entity) ?: return false
return controller.canJump(entity, driver)
@@ -139,23 +101,11 @@ data class RidingManager(val entity: PokemonEntity) {
return controller.jumpForce(entity, driver, jumpStrength)
}
/**
* Used to supply the delta for the lerp() between velocity
* vectors. Called inertia for now as the way it feels when
* affecting the ride is somewhat akin to inertia.
*/
fun inertia(entity: PokemonEntity): Double {
val controller = getController(entity) ?: return 0.5
return controller.inertia(entity)
}
fun gravity(entity: PokemonEntity, regularGravity: Double): Double? {
val controller = getController(entity) ?: return null
return controller.gravity(entity, regularGravity)
}
//Maybe should be changed to be a function that conveys:
// "turn on use our camera system function"
fun shouldRoll(entity: PokemonEntity): Boolean {
val controller = getController(entity) ?: return false
return controller.shouldRoll(entity)
@@ -171,50 +121,8 @@ data class RidingManager(val entity: PokemonEntity) {
return controller.shouldRotatePokemonHead()
}
fun rotationOnMouseXY(
entity: PokemonEntity,
driver: Player,
yMouse: Double,
xMouse: Double,
yMouseSmoother: SmoothDouble,
xMouseSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
val controller = getController(entity) ?: return Vec3.ZERO
return controller.rotationOnMouseXY(
entity,
driver,
yMouse,
xMouse,
yMouseSmoother,
xMouseSmoother,
sensitivity,
deltaTime
) }
/**
* Determines whether mouse input will affect the rotation of
* the rollable object. Need to remove this as it's not used anymore
*/
//I think this might better serve as a "use smoothing on angular velocity"
fun useAngVelSmoothing(entity: PokemonEntity): Boolean {
val controller = getController(entity) ?: return true
return controller.useAngVelSmoothing(entity)
}
fun turnOffOnGround(entity: PokemonEntity): Boolean {
val controller = getController(entity) ?: return false
return controller.turnOffOnGround(entity)
}
fun angRollVel(entity: PokemonEntity, driver: Player, deltaTime: Double): Vec3 {
val controller = getController(entity) ?: return Vec3(0.0, 0.0, 0.0)
return controller.angRollVel(entity, driver, deltaTime)
}
//FIXME: Make this not just be false for all controllers lol
fun dismountOnShift(): Boolean {
val controller = getController(entity) ?: return false
return controller.dismountOnShift(entity)
return false
}
}
@@ -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.api.riding
import net.minecraft.client.player.LocalPlayer
import net.minecraft.client.resources.sounds.AbstractTickableSoundInstance
import net.minecraft.client.resources.sounds.SoundInstance
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundSource
/**
* put what it is here
*
* @author Jackowes
* @since March 4th, 2025
*/
class RidingOnPlayerSoundEventInstance(
private val player: LocalPlayer,
soundEvent: SoundEvent
) : AbstractTickableSoundInstance(soundEvent, SoundSource.PLAYERS, SoundInstance.createUnseededRandom()) {
init {
this.looping = true
this.delay = 0
this.volume = 0.1f
}
fun setPitch( pitch: Float ){
this.pitch = pitch
}
fun setVolume( volume: Float){
this.volume = volume
}
override fun tick() {
//TODO: implement effectively
//For now it should just play the sound?
setPitch(1.0f)
println("ticking")
}
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.api.riding.controller
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.net.Decodable
import com.cobblemon.mod.common.api.net.Encodable
import com.cobblemon.mod.common.api.riding.RidingState
@@ -18,7 +17,6 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.writeIdentifier
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
@@ -73,14 +71,6 @@ interface RideController : Encodable, Decodable {
*/
fun rotation(entity: PokemonEntity, driver: LivingEntity) : Vec2
/**
* Sets the change current change in rotation for that tick in three
* dimensions
*/
fun angRollVel(entity: PokemonEntity, driver: Player, deltaTime: Double): Vec3 {
return Vec3(0.0, 0.0, 0.0)
}
fun clampPassengerRotation(entity: PokemonEntity, driver: LivingEntity) {}
fun updatePassengerRotation(entity: PokemonEntity, driver: LivingEntity) {
@@ -94,44 +84,13 @@ interface RideController : Encodable, Decodable {
*/
fun velocity(entity: PokemonEntity, driver: Player, input: Vec3) : Vec3
fun setRideBar(entity: PokemonEntity, driver: Player): Float = 0.0f
fun rideFovMult(entity: PokemonEntity, driver: Player): Float = 1.0f
fun canJump(entity: PokemonEntity, driver: Player) : Boolean
fun jumpForce(entity: PokemonEntity, driver: Player, jumpStrength: Int) : Vec3
fun gravity(entity: PokemonEntity, regularGravity: Double) : Double? = null
fun inertia(entity: PokemonEntity ) : Double = 0.5
fun shouldRoll(entity: PokemonEntity): Boolean = false
fun useAngVelSmoothing(entity: PokemonEntity): Boolean = false
//If function is not overwritten by controllers then just perform the defualt
//rolling function which is roll on mousex and pitch on mousey
fun rotationOnMouseXY(
entity: PokemonEntity,
driver: Player,
yMouse: Double,
xMouse: Double,
yMouseSmoother: SmoothDouble,
xMouseSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if(driver !is Rollable) return Vec3.ZERO
//Might need to add the smoothing here for default.
val rollable = driver as Rollable
return Vec3(0.0, yMouse, xMouse )
}
fun turnOffOnGround(entity: PokemonEntity): Boolean = false
fun dismountOnShift(entity: PokemonEntity): Boolean = false
fun getRuntime(entity: PokemonEntity) = entity.riding.runtime
fun shouldRotatePokemonHead(): Boolean = false
@@ -110,7 +110,7 @@ open class BottomlessStore(override val uuid: UUID) : PokemonStore<BottomlessPos
} else {
this.pokemon.removeAt(position.currentIndex)
}
for (i in startIndex until this.pokemon.size) {
for(i in startIndex until this.pokemon.size) {
this.pokemon[i].storeCoordinates.set(StoreCoordinates(this, BottomlessPosition(i)))
}
storeChangeObservable.emit(Unit)
@@ -785,7 +785,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
/*availableSwitches.forEach {
estimateMatchup(request, battle, it.effectedPokemon)
}*/
val bestEstimation = if (1 == 1 /* todo if player is in bad matchup and switches out a lot and has a better matchup in revealed party */) {
val bestEstimation = if ( 1 == 1 /* todo if player is in bad matchup and switches out a lot and has a better matchup in revealed party */) {
// todo make it so that bestEstimation is actually in comparison to that potential pokemon instead and be sure to switch to that instead
availableSwitches.maxOfOrNull { estimateMatchup(activeBattlePokemon, request, battle, it.effectedPokemon) }
@@ -90,7 +90,7 @@ class MoveInstruction(
val providers = mutableListOf<Any>(battle)
userPokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
if (spreadTargetPokemon.isNotEmpty()) {
providers.add(TargetsProvider(spreadTargetPokemon.filter { it?.effectedPokemon?.entity != null}.mapNotNull { spreadTarget -> spreadTarget?.effectedPokemon?.entity }))
providers.add(TargetsProvider( spreadTargetPokemon.filter { it?.effectedPokemon?.entity != null}.mapNotNull { spreadTarget -> spreadTarget?.effectedPokemon?.entity } ))
} else {
targetPokemon?.effectedPokemon?.entity?.let { TargetsProvider(it) }?.let(providers::add)
}
@@ -26,7 +26,7 @@ import com.cobblemon.mod.common.util.asTranslated
* @author Hiroku
* @since October 3rd, 2022
*/
class StatusInstruction(val message: BattleMessage): InterpreterInstruction {
class StatusInstruction( val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
val (pnx, _) = message.pnxAndUuid(0) ?: return
@@ -114,8 +114,8 @@ class RevivalHerbBlock(settings: Properties) : CropBlock(settings), Mulchable {
var growthSpeed = 1.0F
var ground = pos.below()
for (x in -1..1) {
for (z in -1..1) {
for(x in -1..1) {
for(z in -1..1) {
var nearbyFarmLandBoost = 0.0F
var blockState = level.getBlockState(ground.offset(x, 0, z))
if (blockState.`is`(Blocks.FARMLAND)) {
@@ -94,7 +94,7 @@ class FossilAnalyzerBlockEntity(
return stack?.let { Fossils.isFossilIngredient(it) } == true
&& structure.fossilInventory.size < Cobblemon.config.maxInsertedFossilItems
&& structure.isRunning() == false && structure.resultingFossil == null
&& Fossils.getSubFossilByItemStacks(structure.fossilInventory + mutableListOf(stack)) != null
&& Fossils.getSubFossilByItemStacks( structure.fossilInventory + mutableListOf(stack) ) != null
}
return false
}
@@ -491,16 +491,16 @@ class PokedexGUI private constructor(
when (tabIndex) {
TAB_DESCRIPTION -> {
tabInfoElement = DescriptionWidget(x + 180, y + 135)
tabInfoElement = DescriptionWidget( x + 180, y + 135)
}
TAB_ABILITIES -> {
tabInfoElement = AbilitiesWidget(x + 180, y + 135)
tabInfoElement = AbilitiesWidget( x + 180, y + 135)
}
TAB_SIZE -> {
tabInfoElement = SizeWidget(x + 180, y + 135)
tabInfoElement = SizeWidget( x + 180, y + 135)
}
TAB_STATS -> {
tabInfoElement = StatsWidget(x + 180, y + 135)
tabInfoElement = StatsWidget( x + 180, y + 135)
}
TAB_DROPS -> {
tabInfoElement = DropsScrollingWidget(x + 189, y + 135)
@@ -17,12 +17,8 @@ import com.cobblemon.mod.common.util.adapters.*
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.toRGBA
import com.google.gson.GsonBuilder
import com.google.gson.annotations.SerializedName
import com.mojang.blaze3d.platform.NativeImage
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.texture.DynamicTexture
import kotlin.math.floor
import net.minecraft.resources.ResourceLocation
import org.joml.Vector3f
@@ -200,78 +196,14 @@ class StaticModelTextureSupplier(val texture: ResourceLocation): ModelTextureSup
class AnimatedModelTextureSupplier(
val loop: Boolean,
val fps: Float,
val frames: List<ResourceLocation>,
val interpolation: Boolean
val frames: List<ResourceLocation>
): ModelTextureSupplier {
override fun invoke(state: PosableState): ResourceLocation {
val frameIndex = floor(state.animationSeconds * fps).toInt()
return if (frameIndex >= frames.size && !loop) {
frames.last()
} else {
frames[frameIndex % frames.size]
if (frameIndex >= frames.size && !loop) {
return frames.last()
}
}
fun interpolatedTexture(state: PosableState): DynamicTexture? {
val resourceManager = Minecraft.getInstance().resourceManager
val frameIndex = floor(state.animationSeconds * fps).toInt()
try {
if (frameIndex >= frames.size && !loop) {
return DynamicTexture(NativeImage.read(resourceManager.getResourceOrThrow(frames.last()).open()))
}
} catch (e : Exception) {
return null
}
val o = frameIndex % frames.size
val i = state.animationSeconds * fps - frameIndex
val texture1 : NativeImage
val texture2 : NativeImage
try { //Try getting images of the frames so we can use them to make a new one
texture1 = NativeImage.read(resourceManager.getResourceOrThrow(frames[o]).open())
val s = if (o == frames.size - 1) 0 else o + 1
texture2 = NativeImage.read(resourceManager.getResourceOrThrow(frames[s]).open())
} catch (e : Exception) {
return null
}
for (x in 0..<texture1.width) {
for (y in 0..<texture1.height) {
val color1 = texture1.getPixelRGBA(x,y).toRGBA()
val color2 = texture2.getPixelRGBA(x,y).toRGBA()
var newRed : Int
var newGreen : Int
var newBlue : Int
var newAlpha : Int
//Usually full transparent pixel is black, so this makes it copy the other texture colours to look better, however considering if we should allow to disable this feature
if (color1.w == 0F && color2.w == 1F) {
newRed = floor(color2.x * 255).toInt()
newGreen = floor(color2.y * 255).toInt()
newBlue = floor(color2.z * 255).toInt()
} else if (color1.w == 1F && color2.w == 0F) {
newRed = floor(color1.x * 255).toInt()
newGreen = floor(color1.y * 255).toInt()
newBlue = floor(color1.z * 255).toInt()
} else {
newRed = floor((color1.x + (color2.x - color1.x) * i) * 255).toInt()
newGreen = floor((color1.y + (color2.y - color1.y) * i) * 255).toInt()
newBlue = floor((color1.z + (color2.z - color1.z) * i) * 255).toInt()
}
newAlpha = floor((color1.w + (color2.w - color1.w) * i) * 255).toInt()
val finalColor = (newAlpha shl 24) or (newRed shl 16) or (newGreen shl 8) or newBlue
texture1.setPixelRGBA(x,y,finalColor) //We aren't using texture 1 anymore so it's easier to override it
}
}
val newTexture = DynamicTexture(texture1)
newTexture.setFilter(false, false)
return newTexture
return frames[frameIndex % frames.size]
}
}
@@ -224,7 +224,7 @@ class PokeBobberEntityRenderer(context: EntityRendererProvider.Context?) : Entit
// Iris Shader Compatibility: Keeps Iris from connecting the fishing lines of multiple players' Pokerods.
// Iris performs a mixin into FishingHookRenderer to achieve the same result.
// So we mimic their fix here.
vertexConsumer2.addVertex(0f, 0f, 0f).setColor(0, 0, 0, 255).setNormal(0f, 0f, 0f);
vertexConsumer2.addVertex( 0f, 0f, 0f).setColor(0, 0, 0, 255).setNormal( 0f, 0f, 0f);
super.render(fishingBobberEntity, elapsedPartialTicks, tickDelta, matrixStack, vertexConsumerProvider, light)
}
@@ -18,7 +18,6 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.client.ClientMoLangFunctions.animationFunctions
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.client.render.AnimatedModelTextureSupplier
import com.cobblemon.mod.common.client.render.ModelLayer
import com.cobblemon.mod.common.client.render.models.blockbench.animation.*
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockActiveAnimation
@@ -42,7 +41,6 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.plus
import com.cobblemon.mod.common.util.toRGBA
import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.VertexConsumer
@@ -52,7 +50,6 @@ import net.minecraft.client.model.geom.ModelPart
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.RenderStateShard
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.texture.DynamicTexture
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
import net.minecraft.world.entity.Entity
@@ -391,13 +388,6 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
fun registerRelevantPart(pairing: Pair<String, ModelPart>) = registerRelevantPart(pairing.first, pairing.second)
/** Needed a custom one, so I can make it take in a dynamic texture instead of resource location */
class DynamicStateShard(texture: DynamicTexture) : RenderStateShard.EmptyTextureStateShard(Runnable {
RenderSystem.setShaderTexture(0, texture.id)
}, Runnable {
texture.close() // Cleanup
})
/** Renders the model. Assumes rotations have been set. Will simply render the base model and then any extra layers. */
fun render(
context: RenderContext,
@@ -432,16 +422,8 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
val provider = bufferProvider
if (provider != null) {
for (layer in currentLayers) {
var renderLayer : RenderType
if (layer.texture is AnimatedModelTextureSupplier && layer.texture.interpolation) {
//Handle Interpolation
val texture = layer.texture.interpolatedTexture(currentState ?: FloatingState()) ?: continue
renderLayer = makeLayer(DynamicStateShard(texture), layer.emissive, layer.translucent)
}
else {
val texture = layer.texture?.invoke(currentState ?: FloatingState()) ?: continue
renderLayer = getLayer(texture, layer.emissive, layer.translucent)
}
val texture = layer.texture?.invoke(currentState ?: FloatingState()) ?: continue
val renderLayer = getLayer(texture, layer.emissive, layer.translucent)
val consumer = provider.getBuffer(renderLayer)
val tint = layer.tint
val tintRed = (tint.x * r2 * 255).toInt()
@@ -465,7 +447,7 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
}
/** Generates a [RenderType] by the power of god and anime. Only possible thanks to 100 access wideners. */
fun makeLayer(texture: RenderStateShard.EmptyTextureStateShard, emissive: Boolean, translucent: Boolean): RenderType {
fun makeLayer(texture: ResourceLocation, emissive: Boolean, translucent: Boolean): RenderType {
val multiPhaseParameters: RenderType.CompositeState = RenderType.CompositeState.builder()
.setShaderState(
when {
@@ -475,7 +457,7 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
else -> RenderStateShard.RENDERTYPE_ENTITY_TRANSLUCENT_EMISSIVE_SHADER // This one should be changed to maybe a custom shader? Translucent stuffs with things
}
)
.setTextureState(texture)
.setTextureState(RenderStateShard.TextureStateShard(texture, false, false))
.setTransparencyState(if (translucent) RenderStateShard.TRANSLUCENT_TRANSPARENCY else RenderStateShard.NO_TRANSPARENCY)
.setCullState(RenderStateShard.CULL)
.setWriteMaskState(RenderStateShard.COLOR_DEPTH_WRITE)
@@ -500,7 +482,7 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
} else if (!emissive) {
RenderType.entityTranslucent(texture)
} else {
makeLayer(RenderStateShard.TextureStateShard(texture, false, false), emissive = emissive, translucent = translucent)
makeLayer(texture, emissive = emissive, translucent = translucent)
}
}
@@ -83,12 +83,12 @@ class VibravaModel (root: ModelPart) : PokemonPosableModel(root), QuadrupedFram
singleBoneLook(pitchMultiplier = 0.6F, yawMultiplier = 0.3F),
bedrock("vibrava", "ground_idle"),
wingFrame1.wingFlap(
flapFunction = triangleFunction(period = 0.08F, amplitude = 0.6F),
flapFunction = triangleFunction( period = 0.08F, amplitude = 0.6F),
timeVariable = { state, _, _ -> state.animationSeconds },
axis = ModelPartTransformation.Z_AXIS
),
wingFrame2.wingFlap(
flapFunction = triangleFunction(period = 0.1F, amplitude = 0.4F),
flapFunction = triangleFunction( period = 0.1F, amplitude = 0.4F),
timeVariable = { state, _, _ -> 0.01F + state.animationSeconds },
axis = ModelPartTransformation.Z_AXIS
)
@@ -35,7 +35,7 @@ class DarumakaModel (root: ModelPart) : PokemonPosableModel(root) {
override fun registerPoses() {
val blink = quirk { bedrockStateful("darumaka", "blink") }
val quirk = quirk(secondsBetweenOccurrences = 15F to 100F) { bedrockStateful("darumaka", "quirk") }
val quirk2 = quirk(secondsBetweenOccurrences = 20F to 360F) { bedrockStateful("darumaka","quirk2")}
val quirk2 = quirk(secondsBetweenOccurrences = 20F to 360F) { bedrockStateful( "darumaka","quirk2")}
sleep = registerPose(
poseType = PoseType.SLEEP,
@@ -62,12 +62,12 @@ class VolcaronaModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
singleBoneLook(pitchMultiplier = 0.2F, yawMultiplier = 0.3F),
bedrock("volcarona", "ground_idle"),
wingFrame.wingFlap(
flapFunction = sineFunction(period = 1F, amplitude = 0.25F),
flapFunction = sineFunction( period = 1F, amplitude = 0.25F),
timeVariable = { state, _, _ -> state.animationSeconds },
axis = ModelPartTransformation.Y_AXIS
),
fluffFrame.wingFlap(
flapFunction = sineFunction(period = 1F, amplitude = 0.3F),
flapFunction = sineFunction( period = 1F, amplitude = 0.3F),
timeVariable = { state, _, _ -> state.animationSeconds },
axis = ModelPartTransformation.Y_AXIS
)
@@ -88,12 +88,12 @@ class VolcaronaModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
singleBoneLook(pitchMultiplier = 0.2F, yawMultiplier = 0.3F),
bedrock("volcarona", "ground_idle"),
wingFrame.wingFlap(
flapFunction = sineFunction(period = 0.8F, amplitude = 0.25F),
flapFunction = sineFunction( period = 0.8F, amplitude = 0.25F),
timeVariable = { state, _, _ -> state.animationSeconds },
axis = ModelPartTransformation.Y_AXIS
),
fluffFrame.wingFlap(
flapFunction = sineFunction(period = 0.8F, amplitude = 0.3F),
flapFunction = sineFunction( period = 0.8F, amplitude = 0.3F),
timeVariable = { state, _, _ -> state.animationSeconds },
axis = ModelPartTransformation.Y_AXIS
)
@@ -48,10 +48,10 @@ object MountedPlayerRenderer {
val locatorOffsetToCenter = locatorOffset.sub(center, Vector3f())
val transformationMatrix = Matrix4f()
if (player is Rollable && player.shouldRoll()){
//if (player is Rollable && player.shouldRoll()){
//transformationMatrix.rotate(Axis.YP.rotationDegrees(-yBodyRot))
//transformationMatrix.mulLocal(player.orientation)
}
//}
val rotatedOffset = transformationMatrix.transformPosition(locatorOffsetToCenter, Vector3f()).add(center).sub(Vector3f(0f, player.bbHeight/2, 0f))
matrix.translate(rotatedOffset)
@@ -59,7 +59,7 @@ class CancellableSoundInstance(sound: SoundEvent, pos: BlockPos = BlockPos.ZERO,
// listener is very far away, kill it
this.done = true
BlockEntitySoundTracker.stop(this.pos, this)
} else if (listenerPos > ATTENUATION_DISTANCE_MAX_SQUARED) {
} else if( listenerPos > ATTENUATION_DISTANCE_MAX_SQUARED) {
++unheardTicks
// Purpose of this is if a client is pushing in and out of the attenuation range, they won't notice the sound abruptly stopping
if(unheardTicks > UNHEARD_TICKS_MAX) {
@@ -28,7 +28,8 @@ object ClearPartyCommand {
fun register(dispatcher : CommandDispatcher<CommandSourceStack>) {
val command = Commands.literal(NAME)
.permission(CobblemonPermissions.CLEAR_PARTY)
.then(Commands.argument(PLAYER, EntityArgument.players()).executes(::execute))
.then( Commands.argument(PLAYER, EntityArgument.players()).executes(::execute)
)
dispatcher.register(command)
}
@@ -26,7 +26,7 @@ class RaisedPPStagesFix(output: Schema) : PokemonFix(output) {
}
var moves = moveSetJson.get()
for (i in 0..moves.size) {
for(i in 0..moves.size) {
var move = moves.getOrNull(i)
if(move != null && move.get("RaisedPPStages").asNumber(0).toInt() > 3) {
moves[i] = move.set("RaisedPPStages", dynamic.createInt(3))
@@ -26,7 +26,6 @@ enum class PoseType {
FLY,
FLOAT,
SWIM,
GLIDE,
/** A pose for rendering on the left shoulder. Pose animations are given the player head yaw, pitch, and ageInTicks. */
SHOULDER_LEFT,
/** A pose for rendering on the right shoulder. Pose animations are given the player head yaw, pitch, and ageInTicks. */
@@ -36,9 +36,10 @@ import com.cobblemon.mod.common.api.pokemon.feature.StringSpeciesFeature
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.reactive.ObservableSubscription
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.riding.*
import com.cobblemon.mod.common.api.riding.Rideable
import com.cobblemon.mod.common.api.riding.RidingManager
import com.cobblemon.mod.common.api.riding.Seat
import com.cobblemon.mod.common.api.riding.events.SelectDriverEvent
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.scheduling.Schedulable
import com.cobblemon.mod.common.api.scheduling.SchedulingTracker
import com.cobblemon.mod.common.api.scheduling.afterOnServer
@@ -120,7 +121,15 @@ import net.minecraft.world.InteractionResult
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.damagesource.DamageTypes
import net.minecraft.world.effect.MobEffects
import net.minecraft.world.entity.*
import net.minecraft.world.entity.AgeableMob
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.EntityDimensions
import net.minecraft.world.entity.EntityType
import net.minecraft.world.entity.EquipmentSlot
import net.minecraft.world.entity.ExperienceOrb
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.Pose
import net.minecraft.world.entity.Shearable
import net.minecraft.world.entity.ai.attributes.AttributeSupplier
import net.minecraft.world.entity.ai.attributes.Attributes
import net.minecraft.world.entity.ai.control.MoveControl
@@ -229,8 +238,7 @@ open class PokemonEntity(
get() = entityData.get(FRIENDSHIP)
val seats: List<Seat>
get() = form.riding.seats
val rideProp: RidingProperties
get() = form.riding
var shownItem: ItemStack
get() = entityData.get(SHOWN_HELD_ITEM)
set(value) = entityData.set(SHOWN_HELD_ITEM, value)
@@ -1402,46 +1410,10 @@ open class PokemonEntity(
}
}
override fun handleRelativeFrictionAndCalculateMovement(deltaMovement: Vec3 , friction: Float ): Vec3 {
val riders = this.passengers.filterIsInstance<LivingEntity>()
if (riders.isEmpty()) {
super.handleRelativeFrictionAndCalculateMovement(deltaMovement, friction)
} else {
//Handle ridden pokemon differently to allow vector lerp instead of simple addition.
val v = Entity.getInputVector(riding.velocity(this, this.controllingPassenger as Player, Vec3.ZERO), 1.0f, this.getYRot());
//changing this will give the ride more or less inertia/handling/drift
this.deltaMovement = this.deltaMovement.lerp(v, riding.inertia(this));
this.move(MoverType.SELF, this.deltaMovement.scale(this.speed.toDouble()))
}
return this.deltaMovement
}
/*
override fun shouldDiscardFriction(): Boolean {
val riders = this.passengers.filterIsInstance<LivingEntity>()
if (riders.isEmpty()) {
return super.shouldDiscardFriction()
} else {
return true
}
}
*/
override fun travel(movementInput: Vec3) {
val prevBlockPos = this.blockPosition()
if (beamMode != 3) { // Don't let Pokémon move during recall
//Prevent current travel logic when riding a pokemon.
/*
val riders = this.passengers.filterIsInstance<LivingEntity>()
if ( riders.isEmpty() ) {
super.travel(movementInput)
}
*/
super.travel(movementInput)
this.updateBlocksTraveled(prevBlockPos)
}
if (isBattling && this.isInWater) {
@@ -1665,15 +1637,6 @@ open class PokemonEntity(
return platform == PlatformType.NONE && super.canRide(entity)
}
// Takes in a requested stat type with a base minimum and base maximum and returns the interpolated
// stat based on the boost of that pokemons stat
fun getRideStat(stat: RidingStat, style: RidingStyle, baseMin: Double, baseMax: Double ): Double {
//TODO: Change from static zero boost once aprijuice is implemented.
val stat = this.rideProp.calculate(stat, style, 0 )
val statVal = (((baseMax - baseMin) / 100) * stat) + baseMin
return statVal
}
override fun canAddPassenger(passenger: Entity): Boolean {
return passengers.size < seats.size
}
@@ -1692,22 +1655,6 @@ open class PokemonEntity(
this.yBodyRot = this.yRot
this.yRotO = this.yRot
val riders = this.passengers.filterIsInstance<LivingEntity>()
//These are the only functions that matter and I have isolated them to
//avoid minecrafts changes they do to velocity so that the ride controllers
//can be tested without that interference.
if ( false ) {
val v = Entity.getInputVector(riding.velocity(this, driver, Vec3.ZERO), 1.0f, this.getYRot());
//changing this will give the ride more or less inertia/handling/drift?
//A bit of overlap with other things but.
this.setDeltaMovement(this.deltaMovement.lerp(v, riding.inertia(this)));
//maybe move this to the travel function. The jump below never gets properly used
this.move( MoverType.SELF, this.deltaMovement );
}
if (this.riding.canJump(this, driver)) {
if (this.onGround()) {
if (this.jumpInputStrength > 0) {
@@ -1860,42 +1807,8 @@ open class PokemonEntity(
// Set back to land pose type?
}
/*
These two functions (fluids and ground) need to become riding configurable and
dependent. Also, the onGround() function seems to affect quite a few spots in
code and would likely need to be changed to be something more robust instead of
just overriding this method.
*/
override fun isAffectedByFluids(): Boolean {
var fluidAffected = true
if (this.hasControllingPassenger()) {
//Change this so it calls something from the controller to check
//if the specific controller wants to ignore fluid physics since
//not every single one will want to
fluidAffected = false
}
return fluidAffected
}
//this seems a bit hacky to me seeing as how many spots in the base classes its used.
//However, there are odd interactions with the controllers when they are meant to be
//flying or swimming but they are touching the ground and this needs to be prevented.
//Having it be able to be turned off by the flying or swimming controllers is the
//temp solution I have found.
override fun onGround() : Boolean {
if (riding.turnOffOnGround(this)) {
return false
}
return super.onGround()
}
//I think already mentioned but should maybe be riding controller configurable
override fun dismountsUnderwater(): Boolean {
return false
return true
}
override fun getDefaultGravity(): Double {
@@ -1906,16 +1819,6 @@ open class PokemonEntity(
return riding.gravity(this, regularGravity) ?: regularGravity
}
fun setRideBar(): Float {
val driver = this.controllingPassenger as? Player ?: return 0.0f
return this.riding.setRideBar(this, driver)
}
fun rideFovMult(): Float {
val driver = this.controllingPassenger as? Player ?: return 1.0f
return this.riding.rideFovMult(this, driver)
}
/**
* A utility method to resolve the [Codec] of [Pokemon] aware if the [world] is client sided or not.
*
@@ -32,7 +32,7 @@ object CobblemonItemGroups {
private val INJECTORS = hashMapOf<ResourceKey<CreativeModeTab>, (injector: Injector) -> Unit>()
@JvmStatic val BLOCKS_KEY = this.create("blocks", this::blockEntries) {
ItemStack(CobblemonItems.PC)
ItemStack( CobblemonItems.PC)
}
@JvmStatic val UTILITY_ITEMS_KEY = this.create("utility_item", this::utilityItemEntries) {
ItemStack(CobblemonItems.POKE_BALL)
@@ -593,7 +593,6 @@ object CobblemonItemGroups {
entries.accept(CobblemonItems.MEDICINAL_LEEK)
entries.accept(Items.BONE)
entries.accept(Items.SNOWBALL)
entries.accept(Items.GOLD_BLOCK)
entries.accept(CobblemonItems.NORMAL_GEM)
entries.accept(CobblemonItems.FIRE_GEM)
@@ -626,7 +625,7 @@ object CobblemonItemGroups {
}
private fun blocksInjections(injector: Injector) {
if(Cobblemon.implementation.isModInstalled("adorn")) { //adorn modifies vanilla block tab and if we don't follow suit we crash the game
if(Cobblemon.implementation.isModInstalled("adorn")) { //adorn modifies vanilla block tab and if we dont follow suit we crash the game
injector.putLast(CobblemonItems.APRICORN_LOG)
injector.putLast(CobblemonItems.APRICORN_WOOD)
injector.putLast(CobblemonItems.STRIPPED_APRICORN_LOG)
@@ -41,7 +41,7 @@ class BenchMovePacket(val isParty: Boolean, val uuid: UUID, val oldMove: MoveTem
if (oldMove != null) {
buffer.writeString(oldMove.name)
}
buffer.writeBoolean(newMove != null)
buffer.writeBoolean( newMove != null)
if (newMove != null) {
buffer.writeString(newMove.name)
}
@@ -9,79 +9,45 @@
package com.cobblemon.mod.common.pokemon.riding.controllers
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.controller.posing.PoseOption
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.riding.states.BirdAirState
import com.cobblemon.mod.common.util.*
import net.minecraft.client.Minecraft
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.toVec3d
import com.cobblemon.mod.common.util.withQueryValue
import com.cobblemon.mod.common.util.writeString
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
import net.minecraft.world.phys.Vec3
import kotlin.math.*
class BirdAirController : RideController {
override val key = KEY
override val poseProvider = PoseProvider(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) {
val player = it.passengers.firstOrNull() as? Player ?: return@PoseOption false
it.deltaMovement.length() > 0.5 })
/*
.with(PoseOption(PoseType.FLY) {
val player = it.passengers.firstOrNull() as? Player ?: return@PoseOption false
player.zza > 0.0f // Checks if the player is actively attempting to move forward
})
*/
.with(PoseOption(PoseType.FLY) {
val player = it.passengers.firstOrNull() as? Player ?: return@PoseOption false
it.deltaMovement.length() > 0.5 })
/*
.with(PoseOption(PoseType.FLY) {
val player = it.passengers.firstOrNull() as? Player ?: return@PoseOption false
player.zza > 0.0f // Checks if the player is actively attempting to move forward
})
*/
.with(PoseOption(PoseType.FLY) { it.deltaMovement.horizontalDistance() > 0.1 })
override val condition: (PokemonEntity) -> Boolean = { true }
var jumpVector = listOf("0".asExpression(), "0.3".asExpression(), "0".asExpression())
private set
var gravity: Expression = "0".asExpression()
private set
var horizontalAcceleration: Expression = "0.1".asExpression()
private set
var verticalAcceleration: Expression = "0.1".asExpression()
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 135.0, 45.0)".asExpression()
private set
var topSpeedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 1.0, 0.35)".asExpression()
private set
var glideTopSpeedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 2.0, 1.0)".asExpression()
private set
// Max accel is a whole 1.0 in 3 seconds. The conversion in the function below is to convert seconds to ticks
var accelExpr: Expression = "q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 3.0)), (1.0 / (20.0 * 8.0)))".asExpression()
private set
//Seconds self propelled flight (glide is not self propelled in this case)
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 60.0, 20.0)".asExpression()
private set
var infiniteStamina: Expression = "false".asExpression()
var speed: Expression = "1.0".asExpression()
private set
override fun speed(entity: PokemonEntity, driver: Player): Float {
//Retrieve the current bird controller state and stats
val state = getState(entity, ::BirdAirState)
return state.rideVel.length().toFloat()
return getRuntime(entity).resolveFloat(speed)
}
override fun rotation(entity: PokemonEntity, driver: LivingEntity): Vec2 {
@@ -89,128 +55,33 @@ class BirdAirController : RideController {
}
override fun velocity(entity: PokemonEntity, driver: Player, input: Vec3): Vec3 {
var yVel = 0.0
var leftForce = 0.0
var upForce = 0.0
var forwardForce = 0.0
val state = getState(entity, ::BirdAirState)
//Perform ride velocity update
calculateRideSpaceVel(entity, driver, state)
//Translate ride space velocity to world space velocity.
val rollable = driver as? Rollable
if (rollable != null) {
//Need to deadzone this when straight up or down
upForce += -1.0 * sin(Math.toRadians(rollable.pitch.toDouble())) * state.rideVel.z
forwardForce += cos(Math.toRadians(rollable.pitch.toDouble())) * state.rideVel.z
upForce += cos(Math.toRadians(rollable.pitch.toDouble())) * state.rideVel.y
forwardForce += sin(Math.toRadians(rollable.pitch.toDouble())) * state.rideVel.y
val f = driver.xxa * 0.2f
var g = driver.zza
if (g <= 0.0f) {
g *= 0.25f
}
//TODO: implement altitude limit
//Bring the ride out of the sky when stamina is depleted.
if (state.stamina <= 0.0) {
upForce -= 0.3
var jumpVector = Vec3.ZERO
if (entity.jumpInputStrength > 0) {
val runtime = getRuntime(entity)
runtime.environment.query.addFunction("jump_strength") { DoubleValue(entity.jumpInputStrength.toDouble()) }
val jumpForces = this.jumpVector.map { runtime.resolveFloat(it) }
jumpVector = Vec3(jumpForces[0].toDouble(), jumpForces[1].toDouble(), jumpForces[2].toDouble())
jumpVector = jumpVector.toVector3f().mul((driver as Rollable).orientation).toVec3d()
entity.jumpInputStrength = 0
entity.addDeltaMovement(jumpVector)
}
val velocity = Vec3(state.rideVel.x , upForce, forwardForce)
val velocity = Vec3(f.toDouble(), 0.0, g.toDouble())
return velocity
}
override fun angRollVel(entity: PokemonEntity, driver: Player, deltaTime: Double): Vec3 {
val rollable = driver as? Rollable
val state = getState(entity, ::BirdAirState)
//TODO: Tie in handling
val handling = getRuntime(entity).resolveDouble(handlingExpr)
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
val rotationChangeRate = 10.0
if (rollable != null) {
var yawForce = rotationChangeRate * sin(Math.toRadians(rollable.roll.toDouble()))
//for a bit of correction on the rolls limit it to a quarter the amount
var pitchForce = -0.35 * rotationChangeRate * abs(sin(Math.toRadians(rollable.roll.toDouble())))
//limit rotation modulation when pitched up heavily or pitched down heavily
yawForce *= abs(cos(Math.toRadians(rollable.pitch.toDouble())))
pitchForce *= abs(cos(Math.toRadians(rollable.pitch.toDouble()))) * 1.5
//Create a deadzone for easier leveling out
if (rollable.roll <= 0.0 && rollable.roll >= -0.0) {
yawForce = 0.0
pitchForce = 0.0
}
return Vec3(yawForce, pitchForce, 0.0)
}
return Vec3(0.0, 0.0, 0.0)
}
override fun rotationOnMouseXY(
entity: PokemonEntity,
driver: Player,
yMouse: Double,
xMouse: Double,
yMouseSmoother: SmoothDouble,
xMouseSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is Rollable) return Vec3.ZERO
val rollable = driver as Rollable
val state = getState(entity, ::BirdAirState)
val handling = getRuntime(entity).resolveDouble(handlingExpr)
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
//Smooth out mouse input.
val smoothingSpeed = 4
val xInput = xMouseSmoother.getNewDeltaValue(xMouse * 0.1, deltaTime * smoothingSpeed);
val yInput = yMouseSmoother.getNewDeltaValue(yMouse * 0.1, deltaTime * smoothingSpeed);
//limit rolling based on handling and current speed.
//modulated by speed so that when flapping idle in air you are ont wobbling around to look around
val rotMin = 15.0
var rollForce = xInput
val rotLimit = max(handling * normalizeVal(state.rideVel.length(), 0.0, topSpeed).pow(3), rotMin)
//Limit roll by non linearly decreasing inputs towards
// a rotation limit based on the current distance from
// that rotation limit
if (abs(rollable.roll + rollForce) < rotLimit) {
if (sign(rollForce) == sign(rollable.roll).toDouble()) {
val d = abs(abs(rollable.roll) - rotLimit)
rollForce *= (d.pow(2)) / (rotLimit.pow(2))
}
} else if (sign(rollForce) == sign(rollable.roll).toDouble()) {
rollForce = 0.0
}
//Give the ability to yaw with x mouse input when at low speeds.
val yawForce = xInput * ( 1.0 - normalizeVal(state.rideVel.length(), 0.0, topSpeed).pow(3))
//yaw, pitch, roll
return Vec3(yawForce, yInput, rollForce)
}
override fun canJump(
entity: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(entity: PokemonEntity, driver: Player): Float {
//Retrieve stamina from state
val state = getState(entity, ::BirdAirState)
return (state.stamina / 1.0f)
return true
}
override fun jumpForce(
@@ -221,185 +92,32 @@ class BirdAirController : RideController {
return Vec3.ZERO
}
override fun gravity(entity: PokemonEntity, regularGravity: Double): Double {
return 0.0
override fun gravity(entity: PokemonEntity, regularGravity: Double): Double? {
return getRuntime(entity)
.withQueryValue("gravity", DoubleValue(regularGravity))
.resolveFloat(gravity)
.toDouble()
}
override fun shouldRoll(entity: PokemonEntity) = true
override fun rideFovMult(entity: PokemonEntity, driver: Player): Float {
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
val glideTopSpeed = getRuntime(entity).resolveDouble(glideTopSpeedExpr)
val state = getState(entity, ::BirdAirState)
//Must I ensure that topspeed is greater than minimum?
val normalizedGlideSpeed = normalizeVal(state.rideVel.length(), topSpeed, glideTopSpeed)
//Only ever want the fov change to be a max of 0.2 and for it to have non linear scaling.
return 1.0f + normalizedGlideSpeed.pow(2).toFloat() * 0.2f
}
override fun useAngVelSmoothing(entity: PokemonEntity): Boolean = false
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeString(gravity.toString())
buffer.writeString(horizontalAcceleration.toString())
buffer.writeString(verticalAcceleration.toString())
buffer.writeString(topSpeedExpr.toString())
buffer.writeString(accelExpr.toString())
buffer.writeString(handlingExpr.toString())
buffer.writeString(topSpeedExpr.toString())
buffer.writeString(accelExpr.toString())
buffer.writeString(handlingExpr.toString())
buffer.writeString(speed.toString())
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
gravity = buffer.readString().asExpression()
horizontalAcceleration = buffer.readString().asExpression()
verticalAcceleration = buffer.readString().asExpression()
topSpeedExpr = buffer.readString().asExpression()
accelExpr = buffer.readString().asExpression()
handlingExpr = buffer.readString().asExpression()
topSpeedExpr = buffer.readString().asExpression()
accelExpr = buffer.readString().asExpression()
handlingExpr = buffer.readString().asExpression()
speed = buffer.readString().asExpression()
}
companion object {
val KEY = cobblemonResource("air/bird")
}
/*
* Normalizes the given value between a min and a max.
* The result is clamped between 0.0 and 1.0, where 0.0 represents x is at or below min
* and 1.0 represents x is at or above it.
*/
fun normalizeVal(x: Double, min: Double, max: Double): Double {
require(max > min) { "max must be greater than min" }
return ((x - min) / (max - min)).coerceIn(0.0, 1.0)
}
/*
* Calculates the change in the ride space vector due to player input and ride state
*/
fun calculateRideSpaceVel( entity: PokemonEntity, driver: Player, state: BirdAirState){
//retrieve stats
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
val glideTopSpeed = getRuntime(entity).resolveDouble(glideTopSpeedExpr)
val accel = getRuntime(entity).resolveDouble(accelExpr)
val staminaStat = getRuntime(entity).resolveDouble(staminaExpr)
var glideSpeedChange = 0.00
val currSpeed = state.rideVel.length()
//Flag for determining if stamina should be lost
var loseStamina = false
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina > 0.0) {
//make sure it can't exceed top speed
val forwardInput = when {
driver.zza > 0 && state.rideVel.z > topSpeed -> 0.0
driver.zza < 0 && state.rideVel.z < (-topSpeed / 3.0) -> 0.0
else -> driver.zza.sign
}
state.rideVel = Vec3(
state.rideVel.x,
state.rideVel.y,
(state.rideVel.z + (accel * forwardInput.toDouble())))
loseStamina = true
}
val rollable = driver as? Rollable
if (rollable != null) {
//Base glide speed change on current pitch of the ride.
glideSpeedChange = sin(Math.toRadians(rollable.pitch.toDouble()))
glideSpeedChange = glideSpeedChange.pow(3) * 0.5
//TODO: Possibly create a deadzone around parallel where glide doesn't affect speed?
if (glideSpeedChange <= 0.0) {
//Ensures that a propelling force is still able to be applied when
//climbing in height
if (driver.zza <= 0) {
//speed decrease should be 2x speed increase?
//state.currSpeed = max(state.currSpeed + (0.0166) * glideSpeedChange, 0.0 )
state.rideVel = Vec3(
state.rideVel.x,
state.rideVel.y,
lerp( state.rideVel.z, 0.0,glideSpeedChange * -0.0166 * 2 ))
}
} else {
// only add to the speed if it hasn't exceeded the current
//glide angles maximum amount of speed that it can give.
//state.currSpeed = min(state.currSpeed + ((0.0166 * 2) * glideSpeedChange), maxGlideSpeed)
state.rideVel = Vec3(
state.rideVel.x,
state.rideVel.y,
min(state.rideVel.z + ((0.0166 * 2) * glideSpeedChange), glideTopSpeed))
}
}
//Lateral movement based on driver input.
val latTopSpeed = topSpeed / 2.0
if (driver.xxa != 0.0f && state.stamina > 0.0) {
state.rideVel = Vec3(
(state.rideVel.x + (accel * driver.xxa)).coerceIn(-latTopSpeed, latTopSpeed),
state.rideVel.y,
state.rideVel.z)
loseStamina = true
}
else {
state.rideVel = Vec3(
lerp(state.rideVel.x, 0.0, latTopSpeed / 20.0),
state.rideVel.y,
state.rideVel.z)
}
//Vertical movement based on driver input.
val vertTopSpeed = topSpeed / 2.0
val vertInput = when {
Minecraft.getInstance().options.keyJump.isDown() -> 1.0
Minecraft.getInstance().options.keyShift.isDown() -> -1.0
else -> 0.0
}
if (vertInput != 0.0 && state.stamina > 0.0) {
state.rideVel = Vec3(
state.rideVel.x,
(state.rideVel.y + (accel * vertInput)).coerceIn(-vertTopSpeed, vertTopSpeed),
state.rideVel.z)
loseStamina = true
}
else {
state.rideVel = Vec3(
state.rideVel.x,
lerp(state.rideVel.y, 0.0, vertTopSpeed / 20.0),
state.rideVel.z)
}
//air resistance
//TODO: This should not affect the ride if the player is inputting a force?
//state.currSpeed = max(state.currSpeed - 0.005, 0.0)
//Ensure it would take ten whole seconds to come to a complete stop through air resistance.
if ( loseStamina ) {
state.stamina -= (0.05 / staminaStat).toFloat()
}
//Lose a base amount of stamina just for being airborne
state.stamina -= (0.01 / staminaStat).toFloat()
state.rideVel = Vec3(
state.rideVel.x,
state.rideVel.y,
lerp( state.rideVel.z,0.0, topSpeed / ( 20.0 * 30.0)))
}
}
@@ -1,197 +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.pokemon.riding.controllers
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.controller.posing.PoseOption
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.riding.states.DolphinState
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.*
class DolphinController : RideController {
companion object {
val KEY: ResourceLocation = cobblemonResource("swim/dolphin")
}
override val key: ResourceLocation = KEY
override val poseProvider: PoseProvider = PoseProvider(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { it.deltaMovement.length() >= 0.05 })
override val condition: (PokemonEntity) -> Boolean = { entity ->
//If there are only fluid blocks or air block below the ride
//or if the entity is in water then activate the controller
Shapes.create(entity.boundingBox).blockPositionsAsListRounded().any {
if (it.y.toDouble() == (entity.position().y)) {
val blockState = entity.level().getBlockState(it.below())
return@any (blockState.isAir || !blockState.fluidState.isEmpty ) ||
(entity.isInWater || entity.isUnderWater)
}
true
}
}
var handlingExpr: Expression = "(q.get_ride_stats('SKILL', 'LAND') / 100) * (140.0 - 20.0) + 20.0".asExpression()
private set
var topSpeedExpr: Expression = "(q.get_ride_stats('SPEED', 'LAND') / 100) * (1.0 - 0.5) + 0.5".asExpression()
private set
var accelExpr: Expression = ("(q.get_ride_stats('ACCELERATION', 'LAND') / 100.0) * " +
"( (1.0 / (1.0 * 2)) - (1.0 / (20 * 6.0))) + (1.0 / (20 * 6.0))").asExpression()
private set
var staminaExpr: Expression = "(q.get_ride_stats('SKILL', 'LAND') / 100) * (60.0 - 10.0) + 10.0".asExpression()
private set
var jumpExpr: Expression = "(q.get_ride_stats('SKILL', 'LAND') / 100) * (300.0 - 128.0) + 128.0".asExpression()
private set
override fun speed(entity: PokemonEntity, driver: Player): Float {
//Retrieve Vehicle state
val state = getState(entity, ::DolphinState)
//retrieve stats
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr) * 2
val accel = getRuntime(entity).resolveDouble(accelExpr) * 2
//speed up and slow down based on input
if( driver.zza > 0.0 && state.currSpeed < topSpeed)
{
state.currSpeed = min( state.currSpeed + accel , topSpeed)
}
else if ( driver.zza < 0.0 && state.currSpeed > 0.0)
{
//Decelerate is currently always half of accel
state.currSpeed = max( state.currSpeed - (accel * 2) , 0.0)
}
//If neither forcing deccel or accel then apply water resistance
else{
state.currSpeed = max( state.currSpeed - (accel / 4.0) , 0.0)
}
return state.currSpeed.toFloat()
}
override fun rotation(entity: PokemonEntity, driver: LivingEntity): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot )
}
override fun gravity(entity: PokemonEntity, regularGravity: Double): Double? {
return 0.0
}
override fun inertia(entity: PokemonEntity ): Double
{
return 0.1
}
override fun velocity(entity: PokemonEntity, driver: Player, input: Vec3): Vec3 {
val runtime = getRuntime(entity)
if (!entity.isInWater && !entity.isUnderWater)
{
return Vec3(entity.deltaMovement.x, entity.deltaMovement.x - 0.035, entity.deltaMovement.z )
}
var yComp = 0.0
//TODO:Determine a better way to move around at low speed.
//Get roll to add a left and right strafe during roll
//Not sure if this is desired? Will need to mess around with this
//a bit more
var zComp = 0.0
val rollable = driver as? Rollable
if (rollable != null)
{
//Can be used again if I better figure out how to deadzone it near the tops?
zComp = -1.0 * sin(Math.toRadians( rollable.roll.toDouble() ) )
yComp = -1.0 * sin(Math.toRadians( rollable.pitch.toDouble() ) )
}
val currVelocity = Vec3(0.0, yComp , 1.0)
return currVelocity
}
override fun angRollVel(entity: PokemonEntity, driver: Player, deltaTime: Double): Vec3 {
val rollable = driver as? Rollable
//TODO: Tie in handling
val handling = getRuntime(entity).resolveDouble(handlingExpr)
val rotationChangeRate = 10.0
if( rollable != null )
{
var yawForce = rotationChangeRate * sin( Math.toRadians( rollable.roll.toDouble() ))
//for a bit of correction on the rolls limit it to a quarter the amount
var pitchForce = -0.25 * rotationChangeRate * abs(sin( Math.toRadians( rollable.roll.toDouble() )))
//limit rotation modulation when pitched up heavily or pitched down heavily
yawForce *= abs(cos( Math.toRadians( rollable.pitch.toDouble() )))
pitchForce *= abs(cos( Math.toRadians( rollable.pitch.toDouble() ))) * 1.5
//Create a deadzone for easier leveling out
if( rollable.roll <= 0.0 && rollable.roll >= -0.0)
{
yawForce = 0.0
pitchForce = 0.0
}
return Vec3( yawForce, pitchForce, 0.0)
}
return Vec3(0.0, 0.0, 0.0)
}
override fun canJump(entity: PokemonEntity, driver: Player) = false
override fun jumpForce(
entity: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun shouldRoll(entity: PokemonEntity) = true
override fun turnOffOnGround(entity: PokemonEntity): Boolean = true
override fun dismountOnShift(entity: PokemonEntity): Boolean = false
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeString(this.handlingExpr.getString())
buffer.writeString(this.staminaExpr.getString())
buffer.writeString(this.jumpExpr.getString())
buffer.writeString(this.topSpeedExpr.getString())
buffer.writeString(this.accelExpr.getString())
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
this.handlingExpr = buffer.readString().asExpression()
this.staminaExpr = buffer.readString().asExpression()
this.jumpExpr = buffer.readString().asExpression()
this.topSpeedExpr = buffer.readString().asExpression()
this.accelExpr = buffer.readString().asExpression()
}
}
@@ -8,27 +8,27 @@
package com.cobblemon.mod.common.pokemon.riding.controllers
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.controller.posing.PoseOption
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.riding.states.GenericLandState
import com.cobblemon.mod.common.pokemon.riding.states.JetAirState
import com.cobblemon.mod.common.util.*
import net.minecraft.client.Minecraft
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.getString
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.writeString
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.*
class GenericLandController : RideController {
companion object {
@@ -50,20 +50,6 @@ class GenericLandController : RideController {
var strafeFactor = "0.2".asExpression()
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 140.0, 20.0)".asExpression()
private set
var topSpeedExpr: Expression = "q.get_ride_stats('SPEED', 'LAND', 1.0, 0.3)".asExpression()
private set
// Max accel is a whole 1.0 in 1 second. The conversion in the function below is to convert seconds to ticks
var accelExpr: Expression = "q.get_ride_stats('ACCELERATION', 'LAND', (1.0 / (20.0 * 0.5)), (1.0 / (20.0 * 5.0)))".asExpression()
private set
// Between 30 seconds and 10 seconds at the lowest when at full speed.
var staminaExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 30.0, 10.0)".asExpression()
private set
//Between a one block jump and a ten block jump
var jumpExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 10.0, 1.0)".asExpression()
private set
@Transient
override val key: ResourceLocation = KEY
@Transient
@@ -88,8 +74,7 @@ class GenericLandController : RideController {
}
override fun speed(entity: PokemonEntity, driver: Player): Float {
val state = getState(entity, ::GenericLandState)
return state.rideVel.length().toFloat()
return getRuntime(entity).resolveFloat(speed)
}
override fun rotation(entity: PokemonEntity, driver: LivingEntity): Vec2 {
@@ -97,8 +82,6 @@ class GenericLandController : RideController {
}
override fun velocity(entity: PokemonEntity, driver: Player, input: Vec3): Vec3 {
val currSpeed = 1.0
val runtime = getRuntime(entity)
val driveFactor = runtime.resolveFloat(this.driveFactor)
val strafeFactor = runtime.resolveFloat(this.strafeFactor)
@@ -107,64 +90,13 @@ class GenericLandController : RideController {
if (g <= 0.0f) {
g *= runtime.resolveFloat(this.reverseDriveFactor)
}
val gravity = -1.0
val state = getState(entity, ::GenericLandState)
val velocity = Vec3(f.toDouble(), 0.0, g.toDouble())
calculateRideSpaceVel(entity, driver, state)
//Jump the thang!
if (Minecraft.getInstance().options.keyJump.isDown() && entity.onGround()) {
state.rideVel = Vec3(state.rideVel.x, 2.0, state.rideVel.z)
}
//This is cheap.
//Also make it stop quicker the slower it is.
val maxSpeed = 1.0
//state.currVel = state.currVel.lerp( Vec3(0.0, state.currVel.y, 0.0), 1.0/20.0 )
//entity.deltaMovement = entity.deltaMovement.lerp(velocity, 1.0)
//state.currVel = state.currVel.add( f.toDouble() / 20.0, gravity / 20.0 , g.toDouble() / 20.0)
return state.rideVel
return velocity
}
override fun shouldRoll(entity: PokemonEntity): Boolean {
return false
}
override fun rotationOnMouseXY(
entity: PokemonEntity,
driver: Player,
yMouse: Double,
xMouse: Double,
yMouseSmoother: SmoothDouble,
xMouseSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
//Smooth out mouse input.
val smoothingSpeed = 4
val xInput = xMouseSmoother.getNewDeltaValue(xMouse * 0.1, deltaTime * smoothingSpeed);
val yInput = yMouseSmoother.getNewDeltaValue(yMouse * 0.1, deltaTime * smoothingSpeed);
//yaw, pitch, roll
return Vec3(0.0, yInput, xInput)
}
override fun canJump(entity: PokemonEntity, driver: Player) = false
//TODO: bring in stamina stats
override fun setRideBar(entity: PokemonEntity, driver: Player): Float {
//Retrieve stamina from state and tick up at a rate of 0.1 a second
val state = getState(entity, ::GenericLandState)
val staminaGain = (1.0 / 20.0)*0.1
state.stamina = min(state.stamina + staminaGain,1.0).toFloat()
return (state.stamina / 1.0f)
}
override fun canJump(entity: PokemonEntity, driver: Player) = getRuntime(entity).resolveBoolean(canJump)
override fun jumpForce(entity: PokemonEntity, driver: Player, jumpStrength: Int): Vec3 {
val runtime = getRuntime(entity)
@@ -173,12 +105,6 @@ class GenericLandController : RideController {
return Vec3(jumpVector[0].toDouble(), jumpVector[1].toDouble(), jumpVector[2].toDouble())
}
override fun gravity(entity: PokemonEntity, regularGravity: Double): Double {
return 0.0
}
override fun inertia(entity: PokemonEntity ): Double = 0.3
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeString(this.speed.getString())
@@ -203,50 +129,4 @@ class GenericLandController : RideController {
this.reverseDriveFactor = buffer.readString().asExpression()
this.strafeFactor = buffer.readString().asExpression()
}
/*
* Normalizes the current speed between minSpeed and maxSpeed.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minSpeed and 1.0 represents maxSpeed.
*/
fun normalizeSpeed(currSpeed: Double, minSpeed: Double, maxSpeed: Double): Double {
require(maxSpeed > minSpeed) { "maxSpeed must be greater than minSpeed" }
return ((currSpeed - minSpeed) / (maxSpeed - minSpeed)).coerceIn(0.0, 1.0)
}
fun calculateRideSpaceVel( entity: PokemonEntity, driver: Player, state: GenericLandState){
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
val accel = getRuntime(entity).resolveDouble(accelExpr)
val speed = state.rideVel.length()
//Minimum speed aka how fast you can go backwards.
//val minSpeed = -( topSpeed / 3.0)
val minSpeed = 0.0
//speed up and slow down based on input
if( driver.zza > 0.0 && speed <= topSpeed && state.stamina > 0.0f ) {
//modify acceleration to be slower when at closer speeds to top speed
val accelMod = max( (normalizeSpeed(speed, minSpeed, topSpeed) - 1).pow(2), 0.1)
state.rideVel = Vec3(state.rideVel.x, state.rideVel.y, min( state.rideVel.z + (accel * accelMod) , topSpeed))
}
else if( driver.zza >= 0.0 && (state.stamina == 0.0f) || speed > topSpeed) {
state.rideVel = Vec3(state.rideVel.x, state.rideVel.y, max( state.rideVel.z - ((accel) / 4) , minSpeed))
}
//The minimumspeed you can
else if ( driver.zza < 0.0 && speed > minSpeed) {
//modify deccel to be slower when at closer speeds to minimum speed
val deccelMod = max( -(normalizeSpeed(speed, minSpeed, topSpeed)) + 1, 0.0)
//Decelerate currently always a constant half of max acceleration.
state.rideVel = Vec3(state.rideVel.x, state.rideVel.y, max( state.rideVel.z - ((accel * deccelMod) / 2) , minSpeed))
}
state.rideVel= Vec3(state.rideVel.x, max(state.rideVel.y - 0.2, -1.0) , state.rideVel.z)
if( entity.onGround())
{
state.rideVel= Vec3(state.rideVel.x, 0.0 , state.rideVel.z)
}
}
}
@@ -9,11 +9,9 @@
package com.cobblemon.mod.common.pokemon.riding.controllers
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.controller.posing.PoseOption
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
@@ -56,7 +54,6 @@ class GliderAirController : RideController {
val xVector = if (getRuntime(entity).resolveBoolean(canStrafe)) driver.xxa.toDouble() else 0.0
val yVector = -getRuntime(entity).resolveDouble(glideSpeed)
val zVector = driver.zza.toDouble()
val statSpeed = entity.rideProp.calculate(RidingStat.SPEED, RidingStyle.AIR, 0 )
return Vec3(xVector, yVector, zVector)
}
@@ -9,48 +9,35 @@
package com.cobblemon.mod.common.pokemon.riding.controllers
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.controller.posing.PoseOption
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.writeString
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
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import java.lang.Math.pow
import kotlin.math.*
/**
* Allows for flight like a bird controller, except you don't have to do any "flapping" motion,
* you can just go up and down like creative
*
* @author Apion, Jackowes
* @author Apion
* @since January 1, 2025
*/
class HelicopterAirController : RideController {
override val key = KEY
override val poseProvider = PoseProvider(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { it.deltaMovement.horizontalDistance() > 0.1 })
//If there are only fluid blocks or air block below the ride
//then activate the controller. If it is in water the ride will
//dismount accordingly
override val condition: (PokemonEntity) -> Boolean = { entity ->
Shapes.create(entity.boundingBox).blockPositionsAsListRounded().any {
if (it.y.toDouble() == (entity.position().y)) {
val blockState = entity.level().getBlockState(it.below())
return@any (blockState.isAir || !blockState.fluidState.isEmpty )
}
true
}
}
override val condition: (PokemonEntity) -> Boolean = { true }
var gravity: Expression = "1.0".asExpression()
private set
@@ -61,12 +48,8 @@ class HelicopterAirController : RideController {
var speed: Expression = "1.0".asExpression()
private set
override fun speed(entity: PokemonEntity, driver: Player): Float {
//Increased max speed to exaggerate movement.
//This likely just needs to be a static number if it really is just
//the scalar for the velocity vector
return 2.0f
return getRuntime(entity).resolveFloat(speed)
}
override fun rotation(entity: PokemonEntity, driver: LivingEntity): Vec2 {
@@ -75,83 +58,21 @@ class HelicopterAirController : RideController {
override fun velocity(entity: PokemonEntity, driver: Player, input: Vec3): Vec3 {
val runtime = getRuntime(entity)
val f = driver.xxa * 0.2f
var g = driver.zza
if (g <= 0.0f) {
g *= 0.25f
}
//If the player is not modulating height then hover
var yVel = if (driver.jumping) runtime.resolveDouble(verticalVelocity)
val yVel = if (driver.jumping) runtime.resolveDouble(verticalVelocity)
else if (driver.isShiftKeyDown) -runtime.resolveDouble(verticalVelocity)
else (0.0)
else (entity.gravity)
yVel *= 0.25
var xVel = 0.0
var zVel = 0.0
val velocity = Vec3(f.toDouble(), yVel, g.toDouble())
val rollable = driver as? Rollable
//Horizontal velocity is based on pitch and roll
if (rollable != null) {
xVel = -1.0 * sin(Math.toRadians(rollable.roll.toDouble()))
zVel = sin(Math.toRadians(rollable.pitch.toDouble()))
}
return Vec3(xVel, yVel, zVel)
return velocity
}
override fun angRollVel(entity: PokemonEntity, driver: Player, deltaTime: Double): Vec3 {
val rollable = driver as? Rollable
//In degrees per second? It was supposed to be I think but
//I have messed something up
val rotationChangeRate = 2.0
val rotLimit = 30.0
var pitchForce = driver.zza * rotationChangeRate
var rollForce = -1.0 * driver.xxa * rotationChangeRate
//TODO: Fix accumulated movement causing pitch and roll out of set bounds
if (rollable != null) {
//If the roll or pitch have exceeded the limit then do not modulate that
//rotation in that direction further
//modulate the forces so that they trend towards 0 as they approach rotation limit.
//The current function has it drop off very quickly which may not be ideal.
//Prev function:
// pitchForce = pitchForce * max(rotLim - abs(rollable.pitch.toDouble()), 0.0)
//The three denotes that the force will be 1/3 what it would have been at
//the rotation limit
rollForce *= 2.0.pow(-1.0 * (abs(rollable.roll.toDouble()) / ROTATION_LIMIT))
pitchForce *= 2.0.pow(-1.0 * (abs(rollable.pitch.toDouble()) / ROTATION_LIMIT))
if (rollable.roll >= ROTATION_LIMIT && rollForce > 0.0) {
rollForce = 0.0;
}
if (rollable.roll <= -ROTATION_LIMIT && rollForce < 0.0) {
rollForce = 0.0;
}
if (rollable.pitch >= ROTATION_LIMIT && pitchForce > 0.0) {
pitchForce = 0.0;
}
if (rollable.pitch <= -ROTATION_LIMIT && pitchForce < 0.0) {
pitchForce = 0.0;
}
//Ignore x,y,z its angular velocity:
//yaw, pitch, roll
return Vec3(0.0, pitchForce.toDouble(), rollForce)
}
return Vec3(0.0, 0.0, 0.0)
}
override fun useAngVelSmoothing(entity: PokemonEntity): Boolean = true
override fun inertia(entity: PokemonEntity ): Double = 0.1
override fun canJump(
entity: PokemonEntity,
driver: Player
@@ -167,32 +88,10 @@ class HelicopterAirController : RideController {
return Vec3.ZERO
}
override fun shouldRoll(entity: PokemonEntity): Boolean = true
override fun rotationOnMouseXY(
entity: PokemonEntity,
driver: Player,
yMouse: Double,
xMouse: Double,
yMouseSmoother: SmoothDouble,
xMouseSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if(driver !is Rollable) return Vec3.ZERO
val rollable = driver as Rollable
//yaw, pitch, roll
return Vec3(xMouse, 0.0, 0.0 )
}
override fun gravity(entity: PokemonEntity, regularGravity: Double): Double {
override fun gravity(entity: PokemonEntity, regularGravity: Double): Double? {
return 0.0
}
//Need to ensure that crouching lowers altitude and doesn't eject the player
override fun dismountOnShift(entity: PokemonEntity): Boolean = false
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeString(gravity.toString())
@@ -211,6 +110,5 @@ class HelicopterAirController : RideController {
companion object {
val KEY = cobblemonResource("air/helicopter")
val ROTATION_LIMIT = 30.0f
}
}
@@ -1,345 +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.pokemon.riding.controllers
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.controller.posing.PoseOption
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.riding.states.JetAirState
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
import net.minecraft.world.phys.Vec3
import kotlin.math.*
class JetAirController : RideController {
override val key = KEY
override val poseProvider = PoseProvider(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { it.deltaMovement.length() > 0 })
override val condition: (PokemonEntity) -> Boolean = { true }
var gravity: Expression = "0".asExpression()
private set
var minSpeed: Expression = "1.2".asExpression()
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 140.0, 20.0)".asExpression()
private set
var handlingYawExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 25.0, 8.0)".asExpression()
private set
var topSpeedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 2.5, 1.0)".asExpression()
private set
// Max accel is a whole 1.0 in 1 second. The conversion in the function below is to convert seconds to ticks
var accelExpr: Expression = "q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 1.0)), (1.0 / (20.0 * 5.0)))".asExpression()
private set
// Between 60 seconds and 10 seconds at the lowest when at full speed.
var staminaExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 60.0, 10.0)".asExpression()
private set
var jumpExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 300.0, 128.0)".asExpression()
private set
// Make configurable by json
var infiniteStamina: Expression = "false".asExpression()
private set
var infiniteAltitude: Expression = "false".asExpression()
private set
override fun speed(entity: PokemonEntity, driver: Player): Float {
//retrieve stats
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
val staminaStat = getRuntime(entity).resolveDouble(staminaExpr)
//retrieve state
val state = getState(entity, ::JetAirState)
//retrieve minSpeed
val minSpeed = getRuntime(entity).resolveDouble(minSpeed)
//Reduce stamina unless stamina is infinite
if( !getRuntime(entity).resolveBoolean(infiniteStamina)) {
//Calculate stamina loss due to speed
//At max speed it will tick down 0.1 a second so the stamina will last ten seconds
//There has got to be a better way to express this equation. It interpolates between 0.5 and 1.0
var staminaRate = (normalizeSpeed(state.rideVel.length(), minSpeed, topSpeed))
//interpolate between 0.25 and 1.0 so that you always have at least a min of 0.25 stam loss
staminaRate = 0.25 + (0.75 * staminaRate.pow(3))
//Calculate stamina loss in seconds achievable at top speed
val staminaLoss = staminaRate * (1.0 / (20.0 * staminaStat))
state.stamina = max(state.stamina - staminaLoss, 0.0).toFloat()
}
else{
state.stamina = 1.0f
}
return state.rideVel.length().toFloat()
}
override fun rotation(entity: PokemonEntity, driver: LivingEntity): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(entity: PokemonEntity, driver: Player, input: Vec3): Vec3 {
var upForce = 0.0
var forwardForce = 0.0
//Gather state information
val state = getState(entity, ::JetAirState)
val rollable = driver as? Rollable
//Calculate ride space velocity
calculateRideSpaceVel(entity, driver, state)
//Translate ride space velocity to world space velocity.
if( rollable != null ) {
upForce = -1.0 * sin(Math.toRadians( rollable.pitch.toDouble() )) * state.rideVel.z
forwardForce = cos(Math.toRadians( rollable.pitch.toDouble() )) * state.rideVel.z
}
//If stamina has run out then initiate forced glide down.
upForce = if(state.stamina > 0.0) upForce else -0.7
val altitudeLimit = getRuntime(entity).resolveDouble(jumpExpr)
val pushingHeightLimit = if(getRuntime(entity).resolveBoolean(infiniteAltitude)) false
else (entity.y >= 128 && entity.xRot <= 0)
//Incorporate the altitude limiter.
//Apply downwards force if limit is reached. Resist upwards force on a buffer of 30 blocks past the limit.
upForce = if(pushingHeightLimit) upForce - upForce * ((entity.y - altitudeLimit) / 30) - 0.5
else upForce
val velocity = Vec3(0.0 , upForce, forwardForce )
return velocity
}
override fun useAngVelSmoothing(entity: PokemonEntity): Boolean = true
override fun angRollVel(entity: PokemonEntity, driver: Player, deltaTime: Double): Vec3 {
//Retrieve state
val state = getState(entity, ::JetAirState)
//Cap at a rate of 5fps so frame skips dont lead to huge jumps
val cappedDeltaTime = min( deltaTime, 0.2)
//Get handling in degrees per second
val yawRotRate = getRuntime(entity).resolveDouble(handlingYawExpr)
//Base the change off of deltatime.
var handlingYaw = yawRotRate * (cappedDeltaTime)
//apply stamina debuff if applicable
handlingYaw *= if(state.stamina > 0.0) 1.0 else 0.5
//A+D to yaw
val yawForce = driver.xxa * handlingYaw * -1
return Vec3( yawForce, 0.0, 0.0)
}
override fun rotationOnMouseXY
(entity: PokemonEntity,
driver: Player,
yMouse: Double,
xMouse: Double,
yMouseSmoother: SmoothDouble,
xMouseSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double ): Vec3
{
if(driver !is Rollable) return Vec3.ZERO
//TODO: figure out a cleaner solution to this issue of large jumps when skipping frames or lagging
//Cap at a rate of 5fps so frame skips dont lead to huge jumps
val cappedDeltaTime = min( deltaTime, 0.2)
//Retrieve state
val state = getState(entity, ::JetAirState)
// Accumulate the mouse input
state.currMouseXForce = (state.currMouseXForce + ( 0.0015 * xMouse )).coerceIn(-1.0, 1.0)
state.currMouseYForce = (state.currMouseYForce + ( 0.0015 * yMouse )).coerceIn(-1.0, 1.0)
//Get handling in degrees per second
var handling = getRuntime(entity).resolveDouble(handlingExpr)
//convert it to delta time
handling *= (cappedDeltaTime)
//apply stamina debuff if applicable
handling *= if(state.stamina > 0.0) 1.0 else 0.5
val poke = driver.vehicle as? PokemonEntity
//TODO: reevaluate if deadzones are needed and if they are still causing issues.
//create deadzones for the constant input values.
//val xInput = remapWithDeadzone(state.currMouseXForce, 0.025, 1.0)
//val yInput = remapWithDeadzone(state.currMouseYForce, 0.025, 1.0)
val pitchRot = handling * state.currMouseYForce
//Roll is 1.5 times as fast as pitch
val rollRot = handling * 1.5 * state.currMouseXForce
//yaw, pitch, roll
return Vec3(0.0, pitchRot, rollRot)
}
override fun setRideBar(entity: PokemonEntity, driver: Player): Float {
//Retrieve stamina from state
val state = getState(entity, ::JetAirState)
return (state.stamina / 1.0f)
}
override fun canJump(
entity: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun jumpForce(
entity: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(entity: PokemonEntity, regularGravity: Double): Double {
val grav = 0.0
return grav
}
override fun shouldRoll(entity: PokemonEntity) = true
override fun rideFovMult(entity: PokemonEntity, driver: Player): Float {
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
val minSpeed = getRuntime(entity).resolveDouble(minSpeed)
val state = getState(entity, ::JetAirState)
//Must I ensure that topspeed is greater than minimum?
val normalizedSpeed = normalizeSpeed(state.rideVel.length(), minSpeed, topSpeed)
//TODO: Determine if this should be based on max possible speed instead of top speed.
//Only ever want the fov change to be a max of 0.2 and for it to have non linear scaling.
return 1.0f + normalizedSpeed.pow(2).toFloat() * 0.2f
}
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeString(gravity.toString())
buffer.writeString(minSpeed.toString())
buffer.writeString(handlingExpr.toString())
buffer.writeString(handlingYawExpr.toString())
buffer.writeString(topSpeedExpr.toString())
buffer.writeString(accelExpr.toString())
buffer.writeString(jumpExpr.toString())
buffer.writeString(staminaExpr.toString())
buffer.writeString(infiniteStamina.toString())
buffer.writeString(infiniteAltitude.toString())
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
gravity = buffer.readString().asExpression()
minSpeed = buffer.readString().asExpression()
handlingExpr = buffer.readString().asExpression()
handlingYawExpr = buffer.readString().asExpression()
topSpeedExpr = buffer.readString().asExpression()
accelExpr = buffer.readString().asExpression()
jumpExpr = buffer.readString().asExpression()
staminaExpr = buffer.readString().asExpression()
infiniteStamina = buffer.readString().asExpression()
infiniteAltitude = buffer.readString().asExpression()
}
companion object {
val KEY = cobblemonResource("air/jet")
}
/*
* Calculates the change in the ride space vector due to player input and ride state
*/
fun calculateRideSpaceVel( entity: PokemonEntity, driver: Player, state: JetAirState){
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
val accel = getRuntime(entity).resolveDouble(accelExpr)
val altitudeLimit = getRuntime(entity).resolveDouble(jumpExpr)
val minSpeed = getRuntime(entity).resolveDouble(minSpeed)
val speed = state.rideVel.length()
//Give no altitude limit if at max jump stat.
val pushingHeightLimit = if(getRuntime(entity).resolveBoolean(infiniteStamina)) false
else (entity.y >= altitudeLimit && entity.xRot <= 0)
//speed up and slow down based on input
if( driver.zza > 0.0 && speed < topSpeed && state.stamina > 0.0f && !pushingHeightLimit) {
//modify acceleration to be slower when at closer speeds to top speed
val accelMod = max( -(normalizeSpeed(speed, minSpeed, topSpeed)) + 1, 0.0)
state.rideVel = Vec3(state.rideVel.x, state.rideVel.y, min( state.rideVel.z + (accel * accelMod) , topSpeed))
}
else if( driver.zza >= 0.0 && (state.stamina == 0.0f || pushingHeightLimit) ) {
state.rideVel = Vec3(state.rideVel.x, state.rideVel.y, max( state.rideVel.z - ((accel) / 4) , minSpeed))
}
else if ( driver.zza < 0.0 && speed > minSpeed) {
//modify deccel to be slower when at closer speeds to minimum speed
val deccelMod = max( (normalizeSpeed(speed, minSpeed, topSpeed) - 1).pow(2), 0.1)
//Decelerate currently always a constant half of max acceleration.
state.rideVel = Vec3(state.rideVel.x, state.rideVel.y, max( state.rideVel.z - ((accel * deccelMod) / 2) , minSpeed))
}
}
//TODO: Move these functions to a riding util class.
/*
* Normalizes the current speed between minSpeed and maxSpeed.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minSpeed and 1.0 represents maxSpeed.
*/
fun normalizeSpeed(currSpeed: Double, minSpeed: Double, maxSpeed: Double): Double {
require(maxSpeed > minSpeed) { "maxSpeed must be greater than minSpeed" }
return ((currSpeed - minSpeed) / (maxSpeed - minSpeed)).coerceIn(0.0, 1.0)
}
/*
* Creates a deadzone around zero that extends out to deadzoneLow in both +- and then remaps
* the value linearly between -1.0 and 1.0 outside of the deadzone
*/
fun remapWithDeadzone(value: Double, deadzoneLow: Double = 0.2, deadzoneHigh: Double = 1.0): Double {
// If the absolute value is below the deadzone, return 0.
if (kotlin.math.abs(value) < deadzoneLow) return 0.0
// Otherwise, subtract the deadzone and remap the remaining range to 0-1 (preserving the sign).
val sign = kotlin.math.sign(value)
val adjusted = (kotlin.math.abs(value) - deadzoneLow) / (deadzoneHigh - deadzoneLow)
//Attach the sign back to the vale
return adjusted * sign
}
}
@@ -15,7 +15,7 @@ import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.riding.states.*
import com.cobblemon.mod.common.pokemon.riding.states.CompositeState
import com.cobblemon.mod.common.util.adapters.RideControllerAdapter
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.cobblemonResource
@@ -23,9 +23,7 @@ import com.cobblemon.mod.common.util.getString
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.resolveInt
import com.cobblemon.mod.common.util.writeString
import net.minecraft.client.Minecraft
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
@@ -52,7 +50,7 @@ class RunUpToFlightCompositeController : RideController {
}
fun getActiveController(entity: PokemonEntity): RideController {
val state = getState(entity, ::RunUpToFlightCompositeState)
val state = getState(entity, ::CompositeState)
return state.activeController ?: let {
val controller = if (entity.getCurrentPoseType() in PoseType.FLYING_POSES) {
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
@@ -71,36 +69,10 @@ class RunUpToFlightCompositeController : RideController {
entity: PokemonEntity,
driver: Player
): Float {
val state = getState(entity, ::RunUpToFlightCompositeState)
val state = getState(entity, ::CompositeState)
if (state.activeController == flightController && entity.onGround() && state.timeTransitioned + 20 < entity.level().gameTime) {
//Pass the speed to the next state
val flightState = flightController.getState( entity, ::BirdAirState )
val groundState = landController.getState( entity, ::GenericLandState )
groundState.currSpeed = flightState.currSpeed
groundState.rideVel = flightState.rideVel
groundState.stamina = flightState.stamina
getState(entity, ::RunUpToFlightCompositeState).let {
it.activeController = landController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
it.timeTransitioned = entity.level().gameTime
}
}
else if (state.activeController == landController && Minecraft.getInstance().options.keyJump.isDown && state.timeTransitioned + 20 < entity.level().gameTime) {
//Pass the speed to the next state
val flightState = flightController.getState( entity, ::BirdAirState )
val groundState = landController.getState( entity, ::GenericLandState )
flightState.currSpeed = groundState.currSpeed
flightState.rideVel = groundState.rideVel
flightState.stamina = groundState.stamina
getState(entity, ::RunUpToFlightCompositeState).let {
it.activeController = flightController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
it.timeTransitioned = entity.level().gameTime
}
getState(entity, ::CompositeState).activeController = landController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
}
return getActiveController(entity).speed(entity, driver)
@@ -118,21 +90,7 @@ class RunUpToFlightCompositeController : RideController {
return getActiveController(entity).velocity(entity, driver, input)
}
override fun angRollVel(entity: PokemonEntity, driver: Player, deltaTime: Double): Vec3 {
return getActiveController(entity).angRollVel(entity, driver, deltaTime)
}
override fun rideFovMult(entity: PokemonEntity, driver: Player): Float {
return getActiveController(entity).rideFovMult(entity, driver)
}
override fun setRideBar(entity: PokemonEntity, driver: Player): Float {
return getActiveController(entity).setRideBar(entity, driver)
}
override fun canJump(entity: PokemonEntity, driver: Player): Boolean {
return getActiveController(entity).canJump(entity, driver)
}
override fun canJump(entity: PokemonEntity, driver: Player) = true
override fun jumpForce(
entity: PokemonEntity,
@@ -140,9 +98,13 @@ class RunUpToFlightCompositeController : RideController {
jumpStrength: Int
): Vec3 {
val controller = getActiveController(entity)
val state = getState(entity, ::CompositeState)
val isJumping = Minecraft.getInstance().options.keyJump.isDown
if (controller == landController && jumpStrength >= getRuntime(entity).resolveInt(minimumJump)) {
getState(entity, ::CompositeState).let {
it.activeController = flightController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
it.timeTransitioned = entity.level().gameTime
}
}
return controller.jumpForce(entity, driver, jumpStrength)
}
@@ -150,36 +112,8 @@ class RunUpToFlightCompositeController : RideController {
return getActiveController(entity).gravity(entity, regularGravity)
}
override fun inertia(entity: PokemonEntity): Double {
return getActiveController(entity).inertia(entity)
}
override fun shouldRoll(entity: PokemonEntity): Boolean = getActiveController(entity).shouldRoll(entity)
override fun useAngVelSmoothing(entity: PokemonEntity): Boolean = getActiveController(entity).useAngVelSmoothing(entity)
override fun rotationOnMouseXY
(entity: PokemonEntity,
driver: Player,
yMouse: Double,
xMouse: Double,
yMouseSmoother: SmoothDouble,
xMouseSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double ): Vec3
{
return getActiveController(entity).rotationOnMouseXY(entity,
driver,
yMouse,
xMouse,
yMouseSmoother,
xMouseSmoother,
sensitivity,
deltaTime )
}
override fun dismountOnShift(entity: PokemonEntity): Boolean = getActiveController(entity).dismountOnShift(entity)
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeString(minimumSpeed.getString())
@@ -1,223 +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.pokemon.riding.controllers
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.controller.posing.PoseOption
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.riding.states.*
import com.cobblemon.mod.common.util.adapters.RideControllerAdapter
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.getString
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.resolveInt
import com.cobblemon.mod.common.util.writeString
import net.minecraft.client.Minecraft
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
class RunUpToJetFlightCompositeController : RideController {
override val key = KEY
override val poseProvider = PoseProvider(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { it.entityData.get(PokemonEntity.MOVING) })
override val condition: (PokemonEntity) -> Boolean = { true }
var minimumSpeed: Expression = "0.5".asExpression()
private set
var minimumJump: Expression = "0.5".asExpression()
private set
var landController: RideController = GenericLandController()
private set
var flightController: RideController = JetAirController()
private set
override fun pose(entity: PokemonEntity): PoseType {
return getActiveController(entity).poseProvider.select(entity)
}
fun getActiveController(entity: PokemonEntity): RideController {
val state = getState(entity, ::RunUpToFlightCompositeState)
return state.activeController ?: let {
val controller = if (entity.getCurrentPoseType() in PoseType.FLYING_POSES) {
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
flightController
} else {
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
landController
}
state.activeController = controller
state.timeTransitioned = entity.level().gameTime
controller
}
}
//TODO: This composite controller needs to have the jet lift off at a certain speed.
//maybe just pressing shift or space? Unsure, will need to test to see what works best.
override fun speed(
entity: PokemonEntity,
driver: Player
): Float {
val state = getState(entity, ::RunUpToFlightCompositeState)
if (( state.activeController == flightController && entity.onGround() && state.timeTransitioned + 20 < entity.level().gameTime) ||
( Minecraft.getInstance().options.keySprint.isDown() && state.activeController == flightController && state.timeTransitioned + 20 < entity.level().gameTime)) {
//Pass the data to the next state
val flightState = flightController.getState( entity, ::JetAirState )
val groundState = landController.getState( entity, ::GenericLandState )
groundState.currSpeed = flightState.currSpeed
groundState.stamina = flightState.stamina
groundState.rideVel = flightState.rideVel
//Clear accumulated rotation input when transferring to ground input
flightState.currMouseXForce = 0.0
flightState.currMouseYForce = 0.0
getState(entity, ::RunUpToFlightCompositeState).let {
it.activeController = landController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
it.timeTransitioned = entity.level().gameTime
}
}
//Wow this is not fun to look at.
//These conditions should also be more graceful. As it stands though it feels better than jump and then
//having to hit the ground
else if(
state.activeController == landController && entity.speed > 0.5 &&
Minecraft.getInstance().options.keySprint.isDown() &&
state.timeTransitioned + 20 < entity.level().gameTime
)
{
//Pass the data to the next state
val flightState = flightController.getState( entity, ::JetAirState )
val groundState = landController.getState( entity, ::GenericLandState )
flightState.currSpeed = groundState.currSpeed
flightState.stamina = groundState.stamina
flightState.rideVel = groundState.rideVel
getState(entity, ::RunUpToFlightCompositeState).let {
it.activeController = flightController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
it.timeTransitioned = entity.level().gameTime
}
}
return getActiveController(entity).speed(entity, driver)
}
override fun rotation(entity: PokemonEntity, driver: LivingEntity): Vec2 {
return getActiveController(entity).rotation(entity, driver)
}
override fun velocity(
entity: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return getActiveController(entity).velocity(entity, driver, input)
}
override fun angRollVel(entity: PokemonEntity, driver: Player, deltaTime: Double): Vec3 {
return getActiveController(entity).angRollVel(entity, driver, deltaTime)
}
override fun canJump(entity: PokemonEntity, driver: Player): Boolean {
return getActiveController(entity).canJump(entity, driver)
}
override fun setRideBar(entity: PokemonEntity, driver: Player): Float {
return getActiveController(entity).setRideBar(entity, driver)
}
override fun rideFovMult(entity: PokemonEntity, driver: Player): Float {
return getActiveController(entity).rideFovMult(entity, driver)
}
override fun jumpForce(
entity: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
val controller = getActiveController(entity)
return controller.jumpForce(entity, driver, jumpStrength)
}
override fun gravity(entity: PokemonEntity, regularGravity: Double): Double? {
return getActiveController(entity).gravity(entity, regularGravity)
}
override fun inertia(entity: PokemonEntity): Double {
return getActiveController(entity).inertia(entity)
}
override fun shouldRoll(entity: PokemonEntity): Boolean = getActiveController(entity).shouldRoll(entity)
override fun useAngVelSmoothing(entity: PokemonEntity): Boolean = getActiveController(entity).useAngVelSmoothing(entity)
override fun rotationOnMouseXY(
entity: PokemonEntity,
driver: Player,
yMouse: Double,
xMouse: Double,
yMouseSmoother: SmoothDouble,
xMouseSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
return getActiveController(entity).rotationOnMouseXY(
entity,
driver,
yMouse,
xMouse,
yMouseSmoother,
xMouseSmoother,
sensitivity,
deltaTime
) }
override fun dismountOnShift(entity: PokemonEntity): Boolean = getActiveController(entity).dismountOnShift(entity)
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeString(minimumSpeed.getString())
buffer.writeString(minimumJump.getString())
landController.encode(buffer)
flightController.encode(buffer)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
minimumSpeed = buffer.readString().asExpression()
minimumJump = buffer.readString().asExpression()
landController = buffer.readResourceLocation().let { key ->
val controller = RideControllerAdapter.types[key]?.getConstructor()?.newInstance() ?: error("Unknown controller key: $key")
controller.decode(buffer)
controller
}
flightController = buffer.readResourceLocation().let { key ->
val controller = RideControllerAdapter.types[key]?.getConstructor()?.newInstance() ?: error("Unknown controller key: $key")
controller.decode(buffer)
controller
}
}
companion object {
val KEY = cobblemonResource("composite/run_up_to_jet_flight")
}
}
@@ -8,31 +8,29 @@
package com.cobblemon.mod.common.pokemon.riding.controllers
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.controller.posing.PoseOption
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.RidingOnPlayerSoundEventInstance
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.riding.states.LandVehicleState
import com.cobblemon.mod.common.util.*
import net.minecraft.client.Minecraft
import net.minecraft.client.player.LocalPlayer
import net.minecraft.client.resources.sounds.Sound
import net.minecraft.core.registries.BuiltInRegistries
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.getString
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.writeString
import kotlin.math.absoluteValue
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundEvents
import net.minecraft.util.Mth
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.*
class VehicleLandController : RideController {
companion object {
@@ -50,9 +48,6 @@ class VehicleLandController : RideController {
var reverseDriveFactor = "0.25".asExpression()
private set
var sound = "minecraft:item.elytra.flying".asExpression()
private set
var minimumSpeedToTurn = "0.1".asExpression()
private set
var rotationSpeed = "45/20".asExpression()
@@ -60,21 +55,6 @@ class VehicleLandController : RideController {
var lookYawLimit = "101".asExpression()
private set
// TODO: Tweak these stats to a workable state.
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 140.0, 20.0)".asExpression()
private set
var topSpeedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 1.0, 0.5)".asExpression()
private set
// Max accel is a whole 1.0 in 2 seconds. The conversion in the function below is to convert seconds to ticks
var accelExpr: Expression = "q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 2.0)), (1.0 / (20.0 * 6.0)))".asExpression()
private set
// Between 60 seconds and 10 seconds at the lowest when at full speed.
var staminaExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 60.0, 10.0)".asExpression()
private set
var jumpExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 300.0, 128.0)".asExpression()
private set
@Transient
override val key: ResourceLocation = KEY
@Transient
@@ -89,132 +69,35 @@ class VehicleLandController : RideController {
if (entity.isInWater || entity.isUnderWater) {
return@any false
}
/*
//This might not actually work, depending on what the yPos actually is. yPos of the middle of the entity? the feet?
if (it.y.toDouble() == (entity.position().y)) {
val blockState = entity.level().getBlockState(it.below())
return@any (!blockState.isAir && blockState.fluidState.isEmpty)
return@any !blockState.isAir && blockState.fluidState.isEmpty
}
*/
true
}
}
override fun speed(entity: PokemonEntity, driver: Player): Float {
//Retrieve Vehicle state
val state = getState(entity, ::LandVehicleState)
//retrieve stats
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr) * 2
val accel = getRuntime(entity).resolveDouble(accelExpr) * 2
//speed up and slow down based on input
if( driver.zza > 0.0 && state.currSpeed < topSpeed)
{
state.currSpeed = min( state.currSpeed + accel , topSpeed)
}
else if ( driver.zza < 0.0 && state.currSpeed > 0.0)
{
//Decelerate is currently always half of accel
state.currSpeed = max( state.currSpeed - (accel * 2) , 0.0)
}
else{
state.currSpeed = max( state.currSpeed - (accel / 2.0) , 0.0)
}
return state.currSpeed.toFloat()
return getRuntime(entity).resolveFloat(speed)
}
override fun rotation(entity: PokemonEntity, driver: LivingEntity): Vec2 {
//Retrieve Vehicle state
val state = getState(entity, ::LandVehicleState)
val rotationDegrees = driver.xxa * getRuntime(entity).resolveFloat(this.rotationSpeed)
if( Minecraft.getInstance().options.keyJump.isDown() ){
//If the current drift has not been set and the car is rotating then set drift
if( state.currDrift == 0.0 && rotationDegrees != 0.0f) {
//Set to base value which will be half.
state.currDrift = sign(rotationDegrees) * 2.5
state.turnSmoother.reset()
}
//Calculate any changes to drift
val driftChangeRate = (1.0 / (20.0 * 0.15))
//If Drift is negative already
if(state.currDrift < 0) {
state.currDrift = (state.currDrift + (sign(rotationDegrees) * driftChangeRate)).coerceIn(-5.0, -1.0)
}else{
state.currDrift = (state.currDrift + (sign(rotationDegrees) * driftChangeRate)).coerceIn(1.0, 5.0)
}
return Vec2(driver.xRot, entity.yRot - state.currDrift.toFloat())
}
//If the drift key is not held then reset the curr drift to 0 and use normal rotation
else{
//If the drift key has just been released perform boost addition and drift reset
if( state.currDrift != 0.0){
state.turnSmoother.reset()
state.currDrift = 0.0
state.driftBoost = min(state.driftBoost + 1.0, 2.0)
driver.level().playSound(
null as Player?,
driver.getX(),
driver.getY(),
driver.getZ(), SoundEvents.PLAYER_ATTACK_WEAK,
driver.getSoundSource(), 1.0f, 0.2f
)
val lp = driver as? LocalPlayer
lp?.let {
//val resourceLocation = ResourceLocation.tryParse(getRuntime(entity).resolveString(sound))
//val soundEvent = BuiltInRegistries.SOUND_EVENT.getOptional(resourceLocation).orElse(null)
val soundInstance = RidingOnPlayerSoundEventInstance(it, SoundEvents.AMBIENT_UNDERWATER_LOOP)
Minecraft.getInstance().soundManager.play(soundInstance)
}
}
//Restrict turning while boost is active
val boostRotDebuff = if( state.driftBoost != 0.0 ) 0.5f else 1.0f
return Vec2(driver.xRot, entity.yRot - (rotationDegrees * boostRotDebuff) )
//return Vec2(driver.xRot, state.turnSmoother.getNewDeltaValue((entity.yRot - rotationDegrees).toDouble(), 1.0).toFloat())
}
return Vec2(driver.xRot, entity.yRot - rotationDegrees)
}
override fun velocity(entity: PokemonEntity, driver: Player, input: Vec3): Vec3 {
//Retrieve Vehicle state
val state = getState(entity, ::LandVehicleState)
val runtime = getRuntime(entity)
val driveFactor = runtime.resolveFloat(this.driveFactor)
var g = driver.zza * driveFactor
if (g <= 0.0f) {
g *= runtime.resolveFloat(this.reverseDriveFactor)
}
val grav = -1.0
var velocity = Vec3(0.0,0.0,0.0)
//If the vehicle is currently drifting then do a "poor mans" rotation of the velocity vector to offset it
//from the front of the vehicle. This likely needs cleaned up to be
if( state.currDrift != 0.0){
//Drift speed should be slower the higher the drift is
//normalize the drift speed
val driftSpeed = (1.0 - ((abs(state.currDrift) - 1.0) / (5.0 - 1.0))) * 0.5 + 0.3
velocity = Vec3((1.0 * state.currSpeed) * (driftSpeed / 2.0) * -sign(state.currDrift) + min(state.driftBoost, 0.5) * 1.25,
grav,
(1.0 * state.currSpeed) * driftSpeed + min(state.driftBoost, 0.5) * 1.25)
}else{
velocity = Vec3(0.0 , grav, state.currSpeed + min(state.driftBoost, 0.5) * 1.25)
if (driver.xxa != 0F && g.absoluteValue < runtime.resolveFloat(this.minimumSpeedToTurn)) {
driver.xxa = 0F
}
//tick down boost
state.driftBoost = max( state.driftBoost - (1.0 / (20.0 * 2)), 0.0)
val velocity = Vec3(0.0 /* Maybe do drifting here later */, 0.0, g.toDouble())
return velocity
}
@@ -227,7 +110,7 @@ class VehicleLandController : RideController {
override fun clampPassengerRotation(entity: PokemonEntity, driver: LivingEntity) {
val f = Mth.wrapDegrees(driver.yRot - entity.yRot)
val runtime = getRuntime(entity)
val lookYawLimit = 120.0f
val lookYawLimit = runtime.resolveFloat(this.lookYawLimit)
val g = Mth.clamp(f, -lookYawLimit, lookYawLimit)
driver.yRotO += g - f
driver.yRot = driver.yRot + g - f
@@ -243,27 +126,6 @@ class VehicleLandController : RideController {
return Vec3(jumpVector[0].toDouble(), jumpVector[1].toDouble(), jumpVector[2].toDouble())
}
override fun inertia(entity: PokemonEntity ) : Double {
val state = getState(entity, ::LandVehicleState)
//val inertia = if(Minecraft.getInstance().options.keyJump.isDown()) 0.1 else 0.5
return 0.1
}
override fun rideFovMult(entity: PokemonEntity, driver: Player): Float {
val state = getState(entity, ::LandVehicleState)
//Only increase fov when actively using the boost.
val fovIncrease = (min( state.driftBoost, 0.5 ) * 0.3).toFloat()
return 1.0f + fovIncrease
}
override fun shouldRoll(entity: PokemonEntity): Boolean {
return false
}
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeString(this.speed.getString())
@@ -276,11 +138,6 @@ class VehicleLandController : RideController {
buffer.writeString(this.minimumSpeedToTurn.getString())
buffer.writeString(this.rotationSpeed.getString())
buffer.writeString(this.lookYawLimit.getString())
buffer.writeString(this.handlingExpr.getString())
buffer.writeString(this.staminaExpr.getString())
buffer.writeString(this.jumpExpr.getString())
buffer.writeString(this.topSpeedExpr.getString())
buffer.writeString(this.accelExpr.getString())
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
@@ -296,11 +153,6 @@ class VehicleLandController : RideController {
this.minimumSpeedToTurn = buffer.readString().asExpression()
this.rotationSpeed = buffer.readString().asExpression()
this.lookYawLimit = buffer.readString().asExpression()
this.handlingExpr = buffer.readString().asExpression()
this.staminaExpr = buffer.readString().asExpression()
this.jumpExpr = buffer.readString().asExpression()
this.topSpeedExpr = buffer.readString().asExpression()
this.accelExpr = buffer.readString().asExpression()
}
}
@@ -1,19 +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.pokemon.riding.states
import com.cobblemon.mod.common.api.riding.RidingState
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.world.phys.Vec3
data class BirdAirState(val entity: PokemonEntity) : RidingState {
var currSpeed: Double = 0.0
var stamina: Float = 1.0f
var rideVel: Vec3 = Vec3.ZERO
}
@@ -1,16 +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.pokemon.riding.states
import com.cobblemon.mod.common.api.riding.RidingState
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
data class DolphinState(val entity: PokemonEntity) : RidingState {
var currSpeed: Double = 0.0
}
@@ -1,19 +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.pokemon.riding.states
import com.cobblemon.mod.common.api.riding.RidingState
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.world.phys.Vec3
data class GenericLandState(val entity: PokemonEntity) : RidingState {
var currSpeed: Double = 0.0
var rideVel: Vec3 = Vec3.ZERO
var stamina: Float = 1.0f
}
@@ -1,21 +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.pokemon.riding.states
import com.cobblemon.mod.common.api.riding.RidingState
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.world.phys.Vec3
data class JetAirState(val entity: PokemonEntity) : RidingState {
var currSpeed: Double = 0.0
var stamina: Float = 1.0f
var rideVel: Vec3 = Vec3.ZERO
var currMouseXForce: Double = 0.0
var currMouseYForce: Double = 0.0
}
@@ -1,33 +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.pokemon.riding.states
import com.cobblemon.mod.common.api.riding.RidingState
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.util.SmoothDouble
import net.minecraft.world.phys.Vec3
data class LandVehicleState(val entity: PokemonEntity) : RidingState {
var currSpeed: Double = 0.0
var stamina: Float = 1.0f
var currMouseXForce: Double = 0.0
var currMouseYForce: Double = 0.0
//I am thinking this will either be a ranging from -1.0 to 1.0
//When drift is first activated while a turn is initiated it will go to (the sign of the turn)*0.5.
//Then you are able to change it between +-1.0 and 0.25 which will determine how much forced rotation
//The drift gives you. The second component is the offset of the velocity vector. Might simply be able
//to add an equal amount of x velocity as the current z velocity? Or maybe just a transformation to turn
//it to the right?? This will be the hardest part actually... Because if you add a similar one then the
//magnitude willbe increased? Maybe just half both and then that effectively slows it? idk.
var currDrift: Double = 0.0
//Determines how much boost is left.
var driftBoost: Double = 0.0
var turnSmoother: SmoothDouble = SmoothDouble()
}
@@ -1,15 +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.pokemon.riding.states
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
class RunUpToFlightCompositeState(pokemonEntity: PokemonEntity) : CompositeState(pokemonEntity) {
var currSpeed: Double = 0.0
}
@@ -36,16 +36,14 @@ object ModelTextureSupplierAdapter : JsonDeserializer<ModelTextureSupplier> {
return StaticModelTextureSupplier(ResourceLocation.parse(json.asString))
} else if (json.isJsonObject) {
val jsonObject = json as JsonObject
val loop = jsonObject.get("loop")?.asBoolean != false
val loop = jsonObject.get("loop")?.asBoolean ?: true
val fps = jsonObject.get("fps")?.asFloat ?: 1F
val frames = jsonObject.get("frames")?.asJsonArray?.map { ResourceLocation.parse(it.asString) }
?: throw IllegalArgumentException("Animated textures require a 'frames' value.")
val interpolation = jsonObject.get("interpolation")?.asBoolean == true
return AnimatedModelTextureSupplier(
loop = loop,
fps = fps,
frames = frames,
interpolation = interpolation
frames = frames
)
} else {
throw IllegalArgumentException("Invalid JSON provided for model texture, it was of type ${json::class.simpleName} instead of a String or Object.")
@@ -10,15 +10,12 @@ package com.cobblemon.mod.common.util.adapters
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.pokemon.riding.controllers.BirdAirController
import com.cobblemon.mod.common.pokemon.riding.controllers.DolphinController
import com.cobblemon.mod.common.pokemon.riding.controllers.FallToFlightCompositeController
import com.cobblemon.mod.common.pokemon.riding.controllers.GenericLandController
import com.cobblemon.mod.common.pokemon.riding.controllers.GenericLiquidController
import com.cobblemon.mod.common.pokemon.riding.controllers.GliderAirController
import com.cobblemon.mod.common.pokemon.riding.controllers.HelicopterAirController
import com.cobblemon.mod.common.pokemon.riding.controllers.JetAirController
import com.cobblemon.mod.common.pokemon.riding.controllers.RunUpToFlightCompositeController
import com.cobblemon.mod.common.pokemon.riding.controllers.RunUpToJetFlightCompositeController
import com.cobblemon.mod.common.pokemon.riding.controllers.SwimDashController
import com.cobblemon.mod.common.pokemon.riding.controllers.VehicleLandController
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
@@ -36,19 +33,15 @@ import net.minecraft.resources.ResourceLocation
*/
object RideControllerAdapter : JsonDeserializer<RideController> {
val types: MutableMap<ResourceLocation, Class<out RideController>> = mutableMapOf(
GenericLandController.KEY to GenericLandController::class.java,
GenericLiquidController.KEY to GenericLiquidController::class.java,
SwimDashController.KEY to SwimDashController::class.java,
RunUpToFlightCompositeController.KEY to RunUpToFlightCompositeController::class.java,
RunUpToJetFlightCompositeController.KEY to RunUpToJetFlightCompositeController::class.java,
BirdAirController.KEY to BirdAirController::class.java,
HelicopterAirController.KEY to HelicopterAirController::class.java,
GliderAirController.KEY to GliderAirController::class.java,
FallToFlightCompositeController.KEY to FallToFlightCompositeController::class.java,
VehicleLandController.KEY to VehicleLandController::class.java,
DolphinController.KEY to DolphinController::class.java,
JetAirController.KEY to JetAirController::class.java
GenericLandController.Companion.KEY to GenericLandController::class.java,
GenericLiquidController.Companion.KEY to GenericLiquidController::class.java,
SwimDashController.Companion.KEY to SwimDashController::class.java,
RunUpToFlightCompositeController.Companion.KEY to RunUpToFlightCompositeController::class.java,
BirdAirController.Companion.KEY to BirdAirController::class.java,
HelicopterAirController.Companion.KEY to HelicopterAirController::class.java,
GliderAirController.Companion.KEY to GliderAirController::class.java,
FallToFlightCompositeController.Companion.KEY to FallToFlightCompositeController::class.java,
VehicleLandController.Companion.KEY to VehicleLandController::class.java
)
override fun deserialize(element: JsonElement, type: Type, context: JsonDeserializationContext): RideController {
@@ -33,8 +33,8 @@ object RidingStatDefinitionAdapter : JsonDeserializer<RidingStatDefinition> {
json as JsonObject
val rangesJson = json.get("ranges")
val displayName = json.get("displayName")?.let { TextAdapter.deserialize(it, TypeToken.get(Component::class.java).type, context) }
val description = json.get("description")?.let { TextAdapter.deserialize(it, TypeToken.get(Component::class.java).type, context) }
val displayName = json.get("displayName")?.let { TextAdapter.deserialize(json, TypeToken.get(Component::class.java).type, context) }
val description = json.get("description")?.let { TextAdapter.deserialize(json, TypeToken.get(Component::class.java).type, context) }
if (rangesJson.isJsonPrimitive) {
val range = IntRangeAdapter.deserialize(rangesJson, TypeToken.get(IntRange::class.java).type, context)
@@ -1,36 +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.util.adapters
import com.google.gson.*
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import java.lang.reflect.Type
/**
* Adapter for deserializing SoundEvent types.
*
* @author Jackowes
* @since March 5th, 2025
*/
object SoundEventAdapter : JsonDeserializer<SoundEvent> {
override fun deserialize(json: JsonElement, type: Type, context: JsonDeserializationContext): SoundEvent {
val soundId = json.asString
// Attempt to grab the sound resource location. If incorrect then return null
val resourceLocation = ResourceLocation.read(soundId).result().orElse(null)
?: throw JsonParseException("Invalid sound event resource location: $soundId")
// Attempt to retrieve the SoundEvent from the registry
return BuiltInRegistries.SOUND_EVENT.getOptional(resourceLocation)
.orElseThrow { JsonParseException("Unknown sound event: $soundId") }
}
}
@@ -124,14 +124,8 @@
},
"pokeball": {
"scale": {
"0.4583": {
"pre": [0, 0, 0],
"post": [0.3, 0.3, 0.3]
},
"0.5417": {
"pre": [0.3, 0.3, 0.3],
"post": [0.3, 0.3, 0.3]
},
"0.4583": {"pre": [0, 0, 0], "post": [0.3, 0.3, 0.3]},
"0.5417": {"pre": [0.3, 0.3, 0.3], "post": [0.3, 0.3, 0.3]},
"0.7083": [1, 1, 1]
}
},
@@ -628,21 +622,12 @@
},
"pokeball": {
"scale": {
"0.9375": {
"pre": [0, 0, 0],
"post": [0.3, 0.3, 0.3]
},
"1.0": {
"pre": [0.3, 0.3, 0.3],
"post": [0.3, 0.3, 0.3]
},
"0.9375": {"pre": [0, 0, 0], "post": [0.3, 0.3, 0.3]},
"1.0": {"pre": [0.3, 0.3, 0.3], "post": [0.3, 0.3, 0.3]},
"1.1875": [1, 1, 1],
"3.0625": [1, 1, 1],
"3.25": [0.4, 0.4, 0.4],
"3.375": {
"pre": [0.4, 0.4, 0.4],
"post": [0, 0, 0]
}
"3.375": {"pre": [0.4, 0.4, 0.4], "post": [0, 0, 0]}
}
},
"leg_right": {
@@ -1011,20 +996,6 @@
"scale": 0
}
}
},
"animation.trainer_generic.ride_test": {
"loop": true,
"bones": {
"body": {
"position": [0, -8.4, 0]
},
"leg_right": {
"rotation": [-90, 5, 0]
},
"leg_left": {
"rotation": [-90, -5, 0]
}
}
}
}
}
@@ -26,10 +26,7 @@
"pivot": [0, 24.5, -1],
"cubes": [
{"origin": [-6, 18.5, -13.75], "size": [12, 12, 26], "uv": [52, 39]}
],
"locators": {
"seat_1": [0, 24, 0]
}
]
},
{
"name": "neck",
@@ -31,7 +31,6 @@ transitive-accessible field net/minecraft/client/model/geom/builders/CubeDeforma
transitive-accessible field net/minecraft/client/model/geom/builders/CubeDeformation growY F
transitive-accessible field net/minecraft/client/model/geom/builders/CubeDeformation growZ F
transitive-extendable method net/minecraft/world/entity/LivingEntity canBreatheUnderwater ()Z
accessible method net/minecraft/world/entity/Entity getInputVector (Lnet/minecraft/world/phys/Vec3;FF)Lnet/minecraft/world/phys/Vec3;
transitive-extendable class net/minecraft/world/level/block/grower/TreeGrower
accessible method net/minecraft/world/level/block/state/BlockBehaviour codec ()Lcom/mojang/serialization/MapCodec;
accessible method net/minecraft/network/protocol/game/ClientboundAddEntityPacket <init> (Lnet/minecraft/network/RegistryFriendlyByteBuf;)V
@@ -113,8 +112,6 @@ transitive-extendable method net/minecraft/world/entity/LivingEntity getDimensio
transitive-accessible field net/minecraft/world/level/material/MapColor MATERIAL_COLORS [Lnet/minecraft/world/level/material/MapColor;
transitive-accessible field net/minecraft/client/Minecraft textureManager Lnet/minecraft/client/renderer/texture/TextureManager;
transitive-accessible class net/minecraft/client/renderer/RenderStateShard$EmptyTextureStateShard
transitive-accessible field net/minecraft/world/entity/LivingEntity jumping Z
transitive-accessible field net/minecraft/world/entity/Entity FLAG_SHIFT_KEY_DOWN I
transitive-accessible field net/minecraft/world/entity/Entity DATA_SHARED_FLAGS_ID Lnet/minecraft/network/syncher/EntityDataAccessor;
transitive-accessible field net/minecraft/world/entity/Entity DATA_SHARED_FLAGS_ID Lnet/minecraft/network/syncher/EntityDataAccessor;
@@ -50,48 +50,6 @@
"fixed": false
},
"riding": {
"stats": {
"SPEED": {
"displayName": "Speed",
"description": "Its top speed",
"ranges": {
"LAND": "2-10",
"AIR": "100-101"
}
},
"ACCELERATION": {
"displayName": "Acceleration",
"description": "How fast it can speed up",
"ranges": {
"LAND": "2-10",
"AIR": "100-101"
}
},
"SKILL": {
"displayName": "Handling",
"description": "How fast it can turn",
"ranges": {
"LAND": "2-10",
"AIR": "100-101"
}
},
"JUMP": {
"displayName": "Altitude",
"description": "How high it can fly",
"ranges": {
"LAND": "2-10",
"AIR": "100-101"
}
},
"STAMINA": {
"displayName": "Fuel/Stamina",
"description": "How long it can stay off the ground",
"ranges": {
"LAND": "2-10",
"AIR": "100-101"
}
}
},
"controller": {
"key": "cobblemon:composite/run_up_to_flight",
"minimumSpeed": "0.2",
@@ -175,7 +133,39 @@
}
]
}
]
],
"stats": {
"SPEED": {
"ranges": {
"LAND": "10-20",
"AIR": "25-50"
}
},
"ACCELERATION": {
"ranges": {
"LAND": "90-100",
"AIR": "20-40"
}
},
"SKILL": {
"ranges": {
"LAND": "15-30",
"AIR": "50-100"
}
},
"JUMP": {
"ranges": {
"LAND": "10-20",
"AIR": "25-50"
}
},
"STAMINA": {
"ranges": {
"LAND": "15-30",
"AIR": "35-70"
}
}
}
},
"behaviour": {
"resting": {
@@ -88,67 +88,6 @@
}
]
},
"riding": {
"stats": {
"SPEED": {
"displayName": "Speed",
"description": "Its top speed",
"ranges": {
"LIQUID": "100-100"
}
},
"ACCELERATION": {
"displayName": "Acceleration",
"description": "How fast it can speed up",
"ranges": {
"LIQUID": "100-100"
}
},
"SKILL": {
"displayName": "Handling",
"description": "How fast it can turn",
"ranges": {
"LIQUID": "100-100"
}
},
"JUMP": {
"displayName": "Altitude",
"description": "How high it can fly",
"ranges": {
"LIQUID": "100-100"
}
},
"STAMINA": {
"displayName": "Fuel/Stamina",
"description": "How long it can stay off the ground",
"ranges": {
"LIQUID": "100-100"
}
}
},
"controller": {
"key": "cobblemon:swim/dolphin"
},
"seats": [
{
"offset": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"poseAnimations": [
{
"animations": [
{
"fileName": "trainer_generic",
"animationName": "animation.trainer_generic.ride_test"
}
]
}
]
}
]
},
"moves": [
"1:firepunch",
"1:thunderpunch",
@@ -50,87 +50,6 @@
"height": 2.3,
"fixed": false
},
"riding": {
"stats": {
"SPEED": {
"displayName": "Speed",
"description": "Its top speed",
"ranges": {
"LAND": "100-101",
"AIR": "100-101"
}
},
"ACCELERATION": {
"displayName": "Acceleration",
"description": "How fast it can speed up",
"ranges": {
"LAND": "100-101",
"AIR": "100-101"
}
},
"SKILL": {
"displayName": "Handling",
"description": "How fast it can turn",
"ranges": {
"LAND": "100-101",
"AIR": "100-101"
}
},
"JUMP": {
"displayName": "Altitude",
"description": "How high it can fly",
"ranges": {
"LAND": "100-101",
"AIR": "100-101"
}
},
"STAMINA": {
"displayName": "Fuel/Stamina",
"description": "How long it can stay off the ground",
"ranges": {
"LAND": "100-101",
"AIR": "100-101"
}
}
},
"controller": {
"key": "cobblemon:composite/run_up_to_jet_flight",
"minimumSpeed": "0.2",
"minimumJump": "80",
"landController": {
"key": "cobblemon:land/generic",
"speed": "Math.clamp(q.entity.velocity.horizontal_magnitude + 0.33, 0, 1)",
"jumpVector": [0, "0.3*q.jump_strength/50", "-1 * 0.5*q.jump_strength/50"]
},
"flightController": {
"key": "cobblemon:air/jet",
"gravity": "q.gravity * (0.1 + (q.passenger_count - 1) * 0.1)",
"speed": "Math.clamp(q.entity.velocity.horizontal_magnitude + 0.33, 0, 1)",
"jumpVector": [0, "0.3*q.jump_strength/50", 0]
}
},
"seats": [
{
"offset": {
"x": 0,
"y": 0.4,
"z": 0
},
"poseOffsets": [
{
"poseTypes": [
"FLY"
],
"offset": {
"x": 0,
"y": 0.0,
"z": 0
}
}
]
}
]
},
"behaviour": {
"moving": {
"walk": {
@@ -43,67 +43,6 @@
"eggCycles": 20,
"baseFriendship": 50,
"baseScale": 1,
"riding": {
"stats": {
"SPEED": {
"displayName": "Speed",
"description": "Its top speed",
"ranges": {
"LAND": "100-101"
}
},
"ACCELERATION": {
"displayName": "Acceleration",
"description": "How fast it can speed up",
"ranges": {
"LAND": "100-101"
}
},
"SKILL": {
"displayName": "Handling",
"description": "How fast it can turn",
"ranges": {
"LAND": "100-101"
}
},
"JUMP": {
"displayName": "Altitude",
"description": "How high it can fly",
"ranges": {
"LAND": "100-101"
}
},
"STAMINA": {
"displayName": "Fuel/Stamina",
"description": "How long it can sprint for.",
"ranges": {
"LAND": "100-101"
}
}
},
"controller": {
"key": "cobblemon:land/generic"
},
"seats": [
{
"offset": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"poseAnimations": [
{
"animations": [
{
"fileName": "trainer_generic",
"animationName": "animation.trainer_generic.ride_test"
}
]
}
]
}
]
},
"hitbox": {
"width": 1.3,
"height": 2.6,
@@ -42,68 +42,6 @@
"catchRate": 75,
"eggCycles": 20,
"baseFriendship": 50,
"riding": {
"stats": {
"SPEED": {
"displayName": "Speed",
"description": "Its top speed",
"ranges": {
"LAND": "100-101"
}
},
"ACCELERATION": {
"displayName": "Acceleration",
"description": "How fast it can speed up",
"ranges": {
"LAND": "100-101"
}
},
"SKILL": {
"displayName": "Handling",
"description": "How fast it can turn",
"ranges": {
"LAND": "100-101"
}
},
"JUMP": {
"displayName": "Altitude",
"description": "How high it can fly",
"ranges": {
"LAND": "100-101"
}
},
"STAMINA": {
"displayName": "Fuel/Stamina",
"description": "How long it can stay off the ground",
"ranges": {
"LAND": "100-101"
}
}
},
"controller": {
"key": "cobblemon:land/vehicle",
"sound": "minecraft:entity.elder_guardian.flop"
},
"seats": [
{
"offset": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"poseAnimations": [
{
"animations": [
{
"fileName": "trainer_generic",
"animationName": "animation.trainer_generic.ride_test"
}
]
}
]
}
]
},
"behaviour": {
"resting": {
"canSleep": true,
@@ -40,7 +40,6 @@
"ItemStackComponentizationFixMixin",
"LecternBlockMixin",
"LivingEntityMixin",
"LocalPlayerMixin",
"MinecraftDedicatedServerMixin",
"NbtUtilsMixin",
"PiglinBarterMixin",
@@ -16,7 +16,7 @@ internal class MoveSetTest {
@Test
fun `it should swap two moves properly`() {
val moveset = MoveSet()
val move1 = Move(mockk(), 11, 2)
val move1 = Move( mockk(), 11, 2,)
val move2 = Move(mockk(), 10, 4)
moveset.setMove(0, move1)
moveset.setMove(1, move2)