Compare commits

...
Author SHA1 Message Date
JacksonB 308c9d61c6 Practically an overhaul of bird. Also everything but altitude implemented. 2025-03-14 21:11:02 -05:00
JacksonB 07ad22608d Migrate bird logic over to rideVectors and add lateral and vertical movement to bird 2025-03-14 12:58:02 -05:00
JacksonB b50f924ce0 Jet fully transferred to velocity based calculations rather than speed. A few tweaks to generic land as well. 2025-03-13 23:43:04 -05:00
JacksonB c0b55ebb6f Further dirty this branch before cleaning :( 2025-03-13 20:18:05 -05:00
JacksonB cdc0a790fb Simplification of data driven function to control rideControllers base stat interpolation. Min and max are now inside the function 2025-03-03 22:51:49 -06:00
JacksonB 13e8084f0d merge and fixes to merge of controller work 2025-03-02 12:39:06 -06:00
JacksonB 3daa3d31a3 initial fixes 2025-03-02 12:02:29 -06:00
JacksonB 979d6f02a6 Fix: Whole json object being passed to deserialize rather than the single JsonElement. 2025-03-02 11:32:38 -06:00
Polymeta 8fdb32273d Merge branch 'nuggets-of-nuggets' into 'main'
Fling

See merge request cable-mc/cobblemon!1548
2025-03-02 13:11:56 +00:00
dhi_holo d5c9d8d5b1 Fling
Blocks of Gold now can be flung for 150 BP
2025-03-02 20:57:37 +10:00
Hiroku b7b6185ada Merge branch 'jackowes/ride_cleanup' into 'main'
WIP jet, bird, and helicopter controller

See merge request cable-mc/cobblemon!1543
2025-03-02 09:31:46 +00:00
Jackson Bowes 211b886bf3 WIP jet, bird, and helicopter controller 2025-03-02 09:31:45 +00:00
Paul 36d1008f29 Minor code cleanup and added interpolate property to changelog 2025-03-02 15:39:02 +10:00
Hiroku 1441625a6b Merge branch 'animated-texures-interpolation' into 'main'
Animated Texture Interpolation for Cobblemon

See merge request cable-mc/cobblemon!1545
2025-03-02 05:28:00 +00:00
JacksonB 585371bb08 initial commit 2 2025-03-01 22:35:38 -06:00
JacksonB aee97ff96e initial commit 2025-03-01 22:35:20 -06:00
Gertimoshka 6ca16a0161 Resolve Conflicts 2025-03-01 03:46:18 +00:00
BuildTools 2f31db2232 Insane Person Presents: INTERPOLATION 2025-02-27 18:04:48 +02:00
JacksonB fbbd450d0d Address MR comments. Also implement a seperate jet flight composite controller to handle transition from jetstate data to groundstate data. 2025-02-25 19:24:23 -06:00
JacksonB 83612b50fc Initial cleanup before MR 2025-02-22 19:07:51 -06:00
JacksonB 434faea210 Merge branch 'jackowes/ride_controllers' into jackowes/ride_cleanup 2025-02-22 17:40:24 -06:00
JacksonB 6e24ee6b4e last commit before cleanup and MR 2025-02-22 17:29:18 -06:00
JacksonB 3d6a3fbfec Fix Jet sensitivity issues 2025-02-20 20:12:54 -06:00
JacksonB 708b810e6b Set up begginings of stamina using vanilla jump bar and change jet turning to accumulated input method. 2025-02-20 00:25:02 -06:00
JacksonB 509016e133 Changes to how ride handling is handled and jet controls 2025-02-19 15:07:19 -06:00
JacksonB 690183b994 Squashed commit of the following:
commit 9497175627
Author: JacksonB <78700961+jackowes@users.noreply.github.com>
Date:   Thu Feb 13 00:05:03 2025 -0600

    mk2 bird flight controller

commit 0defd2614f
Author: JacksonB <78700961+jackowes@users.noreply.github.com>
Date:   Wed Feb 12 20:01:57 2025 -0600

    Initial bird controller changes and refactoring of how riding controllers control rotation mouse+rotVel

commit 181af6847d
Author: JacksonB <78700961+jackowes@users.noreply.github.com>
Date:   Wed Feb 12 12:29:35 2025 -0600

    Add updated version of Helicopter ride controller. Tie in some static pokemonentity functions to be riding configurable. Modify the cameramixins, currently there is a temp change to them on this branch.

commit ac1cdb0ba6
Author: JacksonB <78700961+jackowes@users.noreply.github.com>
Date:   Sat Feb 8 03:02:35 2025 -0600

    Add angular velocity constant modifier (roll can now influence yaw so the dolphin can turn). Spruce up dolphin swim controller.

commit 59190209a2
Author: JacksonB <78700961+jackowes@users.noreply.github.com>
Date:   Wed Feb 5 19:30:21 2025 -0600

    initial commit with a bunch of poking around code
2025-02-18 18:20:31 -06:00
JacksonB 33d56124f5 Controller fixes due to some refactoring and velocity handling changes 2025-02-17 18:11:51 -06:00
JacksonB a5a65abe2b Changes to how velocity is handled and some fixes to jet and bird. 2025-02-17 02:01:20 -06:00
JacksonB f73ab3b001 Jet implementation and intermediate velocity and speed handling changes( this commit has broken ride controllers due to this) 2025-02-16 13:14:56 -06:00
JacksonB 9a571984db Rebase off of main transport branch. Make some tweaks to bird and helicopter controls as well as adding a WIP jet controller. 2025-02-14 16:08:13 -06:00
65 changed files with 2741 additions and 212 deletions

No files matched your search

+2
View File
@@ -13,6 +13,7 @@
### 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)
@@ -54,6 +55,7 @@
- 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,6 +9,9 @@
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;
@@ -117,8 +120,24 @@ 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();
}
@@ -0,0 +1,38 @@
/*
* 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,6 +38,7 @@ 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;
@@ -47,6 +48,7 @@ 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;
@@ -55,6 +57,7 @@ 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;
@@ -271,6 +274,7 @@ 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) {
@@ -296,6 +300,60 @@ 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,8 +9,10 @@
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;
@@ -30,5 +32,15 @@ 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());
}
}
}
@@ -14,11 +14,13 @@ 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;
@@ -33,8 +35,11 @@ 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;
@@ -44,6 +49,8 @@ public class MouseHandlerMixin {
@Shadow private double accumulatedDY;
@Shadow private double accumulatedScrollX;
@Inject(
method = "onScroll",
at = @At(
@@ -95,7 +102,13 @@ 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) {
public boolean cobblemon$modifyRotation(
LocalPlayer player,
double cursorDeltaX,
double cursorDeltaY,
@Local(argsOnly = true) double d,
@Local(argsOnly = true) double movementTime
) {
PokedexUsageContext usageContext = CobblemonClient.INSTANCE.getPokedexUsageContext();
if (usageContext.getScanningGuiOpen()) {
this.smoothTurnY.reset();
@@ -109,16 +122,66 @@ 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 pitch = pitchSmoother.getNewDeltaValue(cursorDeltaY * 0.15f, d);
var roll = rollSmoother.getNewDeltaValue(cursorDeltaX * 0.15f, d);
rollable.rotate(0.0F, (float)pitch, (float)roll);
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)
);
}
}
return false;
}
@Inject(method = "handleAccumulatedMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MouseHandler;isMouseGrabbed()Z", ordinal = 0))
@@ -1278,6 +1278,7 @@ 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.values()) {
for (orientation in Orientation.entries) {
if(!options.containsKey(orientation)) {
return orientation
} else {
@@ -39,6 +39,8 @@ 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,6 +61,7 @@ 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
@@ -108,6 +109,7 @@ 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,15 +10,23 @@ 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
@@ -31,6 +39,17 @@ 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)
}
}
}
@@ -58,8 +77,17 @@ 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 {
@@ -91,6 +119,16 @@ 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)
@@ -101,11 +139,23 @@ 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)
@@ -121,8 +171,50 @@ data class RidingManager(val entity: PokemonEntity) {
return controller.shouldRotatePokemonHead()
}
//FIXME: Make this not just be false for all controllers lol
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)
}
fun dismountOnShift(): Boolean {
return false
val controller = getController(entity) ?: return false
return controller.dismountOnShift(entity)
}
}
@@ -0,0 +1,50 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding
import net.minecraft.client.player.LocalPlayer
import net.minecraft.client.resources.sounds.AbstractTickableSoundInstance
import net.minecraft.client.resources.sounds.SoundInstance
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundSource
/**
* put what it is here
*
* @author Jackowes
* @since March 4th, 2025
*/
class RidingOnPlayerSoundEventInstance(
private val player: LocalPlayer,
soundEvent: SoundEvent
) : AbstractTickableSoundInstance(soundEvent, SoundSource.PLAYERS, SoundInstance.createUnseededRandom()) {
init {
this.looping = true
this.delay = 0
this.volume = 0.1f
}
fun setPitch( pitch: Float ){
this.pitch = pitch
}
fun setVolume( volume: Float){
this.volume = volume
}
override fun tick() {
//TODO: implement effectively
//For now it should just play the sound?
setPitch(1.0f)
println("ticking")
}
}
@@ -8,6 +8,7 @@
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
@@ -17,6 +18,7 @@ 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
@@ -71,6 +73,14 @@ 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) {
@@ -84,13 +94,44 @@ 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,8 +17,12 @@ 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
@@ -196,14 +200,78 @@ class StaticModelTextureSupplier(val texture: ResourceLocation): ModelTextureSup
class AnimatedModelTextureSupplier(
val loop: Boolean,
val fps: Float,
val frames: List<ResourceLocation>
val frames: List<ResourceLocation>,
val interpolation: Boolean
): ModelTextureSupplier {
override fun invoke(state: PosableState): ResourceLocation {
val frameIndex = floor(state.animationSeconds * fps).toInt()
if (frameIndex >= frames.size && !loop) {
return frames.last()
return if (frameIndex >= frames.size && !loop) {
frames.last()
} else {
frames[frameIndex % frames.size]
}
return frames[frameIndex % frames.size]
}
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
}
}
@@ -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,6 +18,7 @@ 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
@@ -41,6 +42,7 @@ 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
@@ -50,6 +52,7 @@ 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
@@ -388,6 +391,13 @@ 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,
@@ -422,8 +432,16 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
val provider = bufferProvider
if (provider != null) {
for (layer in currentLayers) {
val texture = layer.texture?.invoke(currentState ?: FloatingState()) ?: continue
val renderLayer = getLayer(texture, layer.emissive, layer.translucent)
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 consumer = provider.getBuffer(renderLayer)
val tint = layer.tint
val tintRed = (tint.x * r2 * 255).toInt()
@@ -447,7 +465,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: ResourceLocation, emissive: Boolean, translucent: Boolean): RenderType {
fun makeLayer(texture: RenderStateShard.EmptyTextureStateShard, emissive: Boolean, translucent: Boolean): RenderType {
val multiPhaseParameters: RenderType.CompositeState = RenderType.CompositeState.builder()
.setShaderState(
when {
@@ -457,7 +475,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(RenderStateShard.TextureStateShard(texture, false, false))
.setTextureState(texture)
.setTransparencyState(if (translucent) RenderStateShard.TRANSLUCENT_TRANSPARENCY else RenderStateShard.NO_TRANSPARENCY)
.setCullState(RenderStateShard.CULL)
.setWriteMaskState(RenderStateShard.COLOR_DEPTH_WRITE)
@@ -482,7 +500,7 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
} else if (!emissive) {
RenderType.entityTranslucent(texture)
} else {
makeLayer(texture, emissive = emissive, translucent = translucent)
makeLayer(RenderStateShard.TextureStateShard(texture, false, false), 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
)
@@ -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,8 +28,7 @@ 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,6 +26,7 @@ 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,10 +36,9 @@ 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.Rideable
import com.cobblemon.mod.common.api.riding.RidingManager
import com.cobblemon.mod.common.api.riding.Seat
import com.cobblemon.mod.common.api.riding.*
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
@@ -121,15 +120,7 @@ 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.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.*
import net.minecraft.world.entity.ai.attributes.AttributeSupplier
import net.minecraft.world.entity.ai.attributes.Attributes
import net.minecraft.world.entity.ai.control.MoveControl
@@ -238,7 +229,8 @@ 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)
@@ -1410,10 +1402,46 @@ 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) {
@@ -1637,6 +1665,15 @@ 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
}
@@ -1655,6 +1692,22 @@ 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) {
@@ -1807,8 +1860,42 @@ 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 true
return false
}
override fun getDefaultGravity(): Double {
@@ -1819,6 +1906,16 @@ 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,6 +593,7 @@ 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)
@@ -625,7 +626,7 @@ object CobblemonItemGroups {
}
private fun blocksInjections(injector: Injector) {
if(Cobblemon.implementation.isModInstalled("adorn")) { //adorn modifies vanilla block tab and if we dont follow suit we crash the game
if(Cobblemon.implementation.isModInstalled("adorn")) { //adorn modifies vanilla block tab and if we don't 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,45 +9,79 @@
package com.cobblemon.mod.common.pokemon.riding.controllers
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.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.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 com.cobblemon.mod.common.pokemon.riding.states.BirdAirState
import com.cobblemon.mod.common.util.*
import net.minecraft.client.Minecraft
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.Mth
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
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) { it.deltaMovement.horizontalDistance() > 0.1 })
.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
})
*/
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 speed: Expression = "1.0".asExpression()
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()
private set
override fun speed(entity: PokemonEntity, driver: Player): Float {
return getRuntime(entity).resolveFloat(speed)
//Retrieve the current bird controller state and stats
val state = getState(entity, ::BirdAirState)
return state.rideVel.length().toFloat()
}
override fun rotation(entity: PokemonEntity, driver: LivingEntity): Vec2 {
@@ -55,33 +89,128 @@ class BirdAirController : RideController {
}
override fun velocity(entity: PokemonEntity, driver: Player, input: Vec3): Vec3 {
val f = driver.xxa * 0.2f
var g = driver.zza
if (g <= 0.0f) {
g *= 0.25f
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
}
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)
//TODO: implement altitude limit
//Bring the ride out of the sky when stamina is depleted.
if (state.stamina <= 0.0) {
upForce -= 0.3
}
val velocity = Vec3(f.toDouble(), 0.0, g.toDouble())
val velocity = Vec3(state.rideVel.x , upForce, forwardForce)
return velocity
}
override fun angRollVel(entity: PokemonEntity, driver: Player, deltaTime: Double): Vec3 {
val rollable = driver as? Rollable
val state = getState(entity, ::BirdAirState)
//TODO: Tie in handling
val handling = getRuntime(entity).resolveDouble(handlingExpr)
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
val rotationChangeRate = 10.0
if (rollable != null) {
var yawForce = rotationChangeRate * sin(Math.toRadians(rollable.roll.toDouble()))
//for a bit of correction on the rolls limit it to a quarter the amount
var pitchForce = -0.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 true
return false
}
override fun setRideBar(entity: PokemonEntity, driver: Player): Float {
//Retrieve stamina from state
val state = getState(entity, ::BirdAirState)
return (state.stamina / 1.0f)
}
override fun jumpForce(
@@ -92,32 +221,185 @@ class BirdAirController : RideController {
return Vec3.ZERO
}
override fun gravity(entity: PokemonEntity, regularGravity: Double): Double? {
return getRuntime(entity)
.withQueryValue("gravity", DoubleValue(regularGravity))
.resolveFloat(gravity)
.toDouble()
override fun gravity(entity: PokemonEntity, regularGravity: Double): Double {
return 0.0
}
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(speed.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())
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
gravity = buffer.readString().asExpression()
horizontalAcceleration = buffer.readString().asExpression()
verticalAcceleration = buffer.readString().asExpression()
speed = 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()
}
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)))
}
}
@@ -0,0 +1,197 @@
/*
* 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.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 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 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,6 +50,20 @@ 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
@@ -74,7 +88,8 @@ class GenericLandController : RideController {
}
override fun speed(entity: PokemonEntity, driver: Player): Float {
return getRuntime(entity).resolveFloat(speed)
val state = getState(entity, ::GenericLandState)
return state.rideVel.length().toFloat()
}
override fun rotation(entity: PokemonEntity, driver: LivingEntity): Vec2 {
@@ -82,6 +97,8 @@ 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)
@@ -90,13 +107,64 @@ class GenericLandController : RideController {
if (g <= 0.0f) {
g *= runtime.resolveFloat(this.reverseDriveFactor)
}
val gravity = -1.0
val velocity = Vec3(f.toDouble(), 0.0, g.toDouble())
val state = getState(entity, ::GenericLandState)
return velocity
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
}
override fun canJump(entity: PokemonEntity, driver: Player) = getRuntime(entity).resolveBoolean(canJump)
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 jumpForce(entity: PokemonEntity, driver: Player, jumpStrength: Int): Vec3 {
val runtime = getRuntime(entity)
@@ -105,6 +173,12 @@ 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())
@@ -129,4 +203,50 @@ 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,9 +9,11 @@
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
@@ -54,6 +56,7 @@ 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,35 +9,48 @@
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.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 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 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
* @author Apion, Jackowes
* @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 })
override val condition: (PokemonEntity) -> Boolean = { true }
//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
}
}
var gravity: Expression = "1.0".asExpression()
private set
@@ -48,8 +61,12 @@ class HelicopterAirController : RideController {
var speed: Expression = "1.0".asExpression()
private set
override fun speed(entity: PokemonEntity, driver: Player): Float {
return getRuntime(entity).resolveFloat(speed)
//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
}
override fun rotation(entity: PokemonEntity, driver: LivingEntity): Vec2 {
@@ -58,21 +75,83 @@ 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)
else if (driver.isShiftKeyDown) -runtime.resolveDouble(verticalVelocity)
else (0.0)
yVel *= 0.25
var xVel = 0.0
var zVel = 0.0
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()))
}
val yVel = if (driver.jumping) runtime.resolveDouble(verticalVelocity)
else if (driver.isShiftKeyDown) -runtime.resolveDouble(verticalVelocity)
else (entity.gravity)
val velocity = Vec3(f.toDouble(), yVel, g.toDouble())
return velocity
return Vec3(xVel, yVel, zVel)
}
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
@@ -88,10 +167,32 @@ class HelicopterAirController : RideController {
return Vec3.ZERO
}
override fun gravity(entity: PokemonEntity, regularGravity: Double): Double? {
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 {
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())
@@ -110,5 +211,6 @@ class HelicopterAirController : RideController {
companion object {
val KEY = cobblemonResource("air/helicopter")
val ROTATION_LIMIT = 30.0f
}
}
@@ -0,0 +1,345 @@
/*
* 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.CompositeState
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
@@ -23,7 +23,9 @@ 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
@@ -50,7 +52,7 @@ class RunUpToFlightCompositeController : RideController {
}
fun getActiveController(entity: PokemonEntity): RideController {
val state = getState(entity, ::CompositeState)
val state = getState(entity, ::RunUpToFlightCompositeState)
return state.activeController ?: let {
val controller = if (entity.getCurrentPoseType() in PoseType.FLYING_POSES) {
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
@@ -69,10 +71,36 @@ class RunUpToFlightCompositeController : RideController {
entity: PokemonEntity,
driver: Player
): Float {
val state = getState(entity, ::CompositeState)
val state = getState(entity, ::RunUpToFlightCompositeState)
if (state.activeController == flightController && entity.onGround() && state.timeTransitioned + 20 < entity.level().gameTime) {
getState(entity, ::CompositeState).activeController = landController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
//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
}
}
return getActiveController(entity).speed(entity, driver)
@@ -90,7 +118,21 @@ class RunUpToFlightCompositeController : RideController {
return getActiveController(entity).velocity(entity, driver, input)
}
override fun canJump(entity: PokemonEntity, driver: Player) = true
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 jumpForce(
entity: PokemonEntity,
@@ -98,13 +140,9 @@ class RunUpToFlightCompositeController : RideController {
jumpStrength: Int
): Vec3 {
val controller = getActiveController(entity)
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
}
}
val state = getState(entity, ::CompositeState)
val isJumping = Minecraft.getInstance().options.keyJump.isDown
return controller.jumpForce(entity, driver, jumpStrength)
}
@@ -112,8 +150,36 @@ 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())
@@ -0,0 +1,223 @@
/*
* 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,29 +8,31 @@
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.util.asExpression
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.getString
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.writeString
import kotlin.math.absoluteValue
import com.cobblemon.mod.common.pokemon.riding.states.LandVehicleState
import com.cobblemon.mod.common.util.*
import net.minecraft.client.Minecraft
import net.minecraft.client.player.LocalPlayer
import net.minecraft.client.resources.sounds.Sound
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundEvents
import net.minecraft.util.Mth
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.*
class VehicleLandController : RideController {
companion object {
@@ -48,6 +50,9 @@ class VehicleLandController : RideController {
var reverseDriveFactor = "0.25".asExpression()
private set
var sound = "minecraft:item.elytra.flying".asExpression()
private set
var minimumSpeedToTurn = "0.1".asExpression()
private set
var rotationSpeed = "45/20".asExpression()
@@ -55,6 +60,21 @@ 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
@@ -69,35 +89,132 @@ class VehicleLandController : RideController {
if (entity.isInWater || entity.isUnderWater) {
return@any false
}
/*
//This might not actually work, depending on what the yPos actually is. yPos of the middle of the entity? the feet?
if (it.y.toDouble() == (entity.position().y)) {
val blockState = entity.level().getBlockState(it.below())
return@any !blockState.isAir && blockState.fluidState.isEmpty
return@any (!blockState.isAir && blockState.fluidState.isEmpty)
}
*/
true
}
}
override fun speed(entity: PokemonEntity, driver: Player): Float {
return getRuntime(entity).resolveFloat(speed)
//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()
}
override fun rotation(entity: PokemonEntity, driver: LivingEntity): Vec2 {
//Retrieve Vehicle state
val state = getState(entity, ::LandVehicleState)
val rotationDegrees = driver.xxa * getRuntime(entity).resolveFloat(this.rotationSpeed)
return Vec2(driver.xRot, entity.yRot - rotationDegrees)
if( Minecraft.getInstance().options.keyJump.isDown() ){
//If the current drift has not been set and the car is rotating then set drift
if( state.currDrift == 0.0 && rotationDegrees != 0.0f) {
//Set to base value which will be half.
state.currDrift = sign(rotationDegrees) * 2.5
state.turnSmoother.reset()
}
//Calculate any changes to drift
val driftChangeRate = (1.0 / (20.0 * 0.15))
//If Drift is negative already
if(state.currDrift < 0) {
state.currDrift = (state.currDrift + (sign(rotationDegrees) * driftChangeRate)).coerceIn(-5.0, -1.0)
}else{
state.currDrift = (state.currDrift + (sign(rotationDegrees) * driftChangeRate)).coerceIn(1.0, 5.0)
}
return Vec2(driver.xRot, entity.yRot - state.currDrift.toFloat())
}
//If the drift key is not held then reset the curr drift to 0 and use normal rotation
else{
//If the drift key has just been released perform boost addition and drift reset
if( state.currDrift != 0.0){
state.turnSmoother.reset()
state.currDrift = 0.0
state.driftBoost = min(state.driftBoost + 1.0, 2.0)
driver.level().playSound(
null as Player?,
driver.getX(),
driver.getY(),
driver.getZ(), SoundEvents.PLAYER_ATTACK_WEAK,
driver.getSoundSource(), 1.0f, 0.2f
)
val lp = driver as? LocalPlayer
lp?.let {
//val resourceLocation = ResourceLocation.tryParse(getRuntime(entity).resolveString(sound))
//val soundEvent = BuiltInRegistries.SOUND_EVENT.getOptional(resourceLocation).orElse(null)
val soundInstance = RidingOnPlayerSoundEventInstance(it, SoundEvents.AMBIENT_UNDERWATER_LOOP)
Minecraft.getInstance().soundManager.play(soundInstance)
}
}
//Restrict turning while boost is active
val boostRotDebuff = if( state.driftBoost != 0.0 ) 0.5f else 1.0f
return Vec2(driver.xRot, entity.yRot - (rotationDegrees * boostRotDebuff) )
//return Vec2(driver.xRot, state.turnSmoother.getNewDeltaValue((entity.yRot - rotationDegrees).toDouble(), 1.0).toFloat())
}
}
override fun velocity(entity: PokemonEntity, driver: Player, input: Vec3): Vec3 {
//Retrieve Vehicle state
val state = getState(entity, ::LandVehicleState)
val runtime = getRuntime(entity)
val driveFactor = runtime.resolveFloat(this.driveFactor)
var g = driver.zza * driveFactor
if (g <= 0.0f) {
g *= runtime.resolveFloat(this.reverseDriveFactor)
}
if (driver.xxa != 0F && g.absoluteValue < runtime.resolveFloat(this.minimumSpeedToTurn)) {
driver.xxa = 0F
val grav = -1.0
var velocity = Vec3(0.0,0.0,0.0)
//If the vehicle is currently drifting then do a "poor mans" rotation of the velocity vector to offset it
//from the front of the vehicle. This likely needs cleaned up to be
if( state.currDrift != 0.0){
//Drift speed should be slower the higher the drift is
//normalize the drift speed
val driftSpeed = (1.0 - ((abs(state.currDrift) - 1.0) / (5.0 - 1.0))) * 0.5 + 0.3
velocity = Vec3((1.0 * state.currSpeed) * (driftSpeed / 2.0) * -sign(state.currDrift) + min(state.driftBoost, 0.5) * 1.25,
grav,
(1.0 * state.currSpeed) * driftSpeed + min(state.driftBoost, 0.5) * 1.25)
}else{
velocity = Vec3(0.0 , grav, state.currSpeed + min(state.driftBoost, 0.5) * 1.25)
}
val velocity = Vec3(0.0 /* Maybe do drifting here later */, 0.0, g.toDouble())
//tick down boost
state.driftBoost = max( state.driftBoost - (1.0 / (20.0 * 2)), 0.0)
return velocity
}
@@ -110,7 +227,7 @@ class VehicleLandController : RideController {
override fun clampPassengerRotation(entity: PokemonEntity, driver: LivingEntity) {
val f = Mth.wrapDegrees(driver.yRot - entity.yRot)
val runtime = getRuntime(entity)
val lookYawLimit = runtime.resolveFloat(this.lookYawLimit)
val lookYawLimit = 120.0f
val g = Mth.clamp(f, -lookYawLimit, lookYawLimit)
driver.yRotO += g - f
driver.yRot = driver.yRot + g - f
@@ -126,6 +243,27 @@ class VehicleLandController : RideController {
return Vec3(jumpVector[0].toDouble(), jumpVector[1].toDouble(), jumpVector[2].toDouble())
}
override fun inertia(entity: PokemonEntity ) : Double {
val state = getState(entity, ::LandVehicleState)
//val inertia = if(Minecraft.getInstance().options.keyJump.isDown()) 0.1 else 0.5
return 0.1
}
override fun rideFovMult(entity: PokemonEntity, driver: Player): Float {
val state = getState(entity, ::LandVehicleState)
//Only increase fov when actively using the boost.
val fovIncrease = (min( state.driftBoost, 0.5 ) * 0.3).toFloat()
return 1.0f + fovIncrease
}
override fun shouldRoll(entity: PokemonEntity): Boolean {
return false
}
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeString(this.speed.getString())
@@ -138,6 +276,11 @@ class VehicleLandController : RideController {
buffer.writeString(this.minimumSpeedToTurn.getString())
buffer.writeString(this.rotationSpeed.getString())
buffer.writeString(this.lookYawLimit.getString())
buffer.writeString(this.handlingExpr.getString())
buffer.writeString(this.staminaExpr.getString())
buffer.writeString(this.jumpExpr.getString())
buffer.writeString(this.topSpeedExpr.getString())
buffer.writeString(this.accelExpr.getString())
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
@@ -153,6 +296,11 @@ class VehicleLandController : RideController {
this.minimumSpeedToTurn = buffer.readString().asExpression()
this.rotationSpeed = buffer.readString().asExpression()
this.lookYawLimit = buffer.readString().asExpression()
this.handlingExpr = buffer.readString().asExpression()
this.staminaExpr = buffer.readString().asExpression()
this.jumpExpr = buffer.readString().asExpression()
this.topSpeedExpr = buffer.readString().asExpression()
this.accelExpr = buffer.readString().asExpression()
}
}
@@ -0,0 +1,19 @@
/*
* 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
}
@@ -0,0 +1,16 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.pokemon.riding.states
import com.cobblemon.mod.common.api.riding.RidingState
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
data class DolphinState(val entity: PokemonEntity) : RidingState {
var currSpeed: Double = 0.0
}
@@ -0,0 +1,19 @@
/*
* 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
}
@@ -0,0 +1,21 @@
/*
* 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
}
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.pokemon.riding.states
import com.cobblemon.mod.common.api.riding.RidingState
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.util.SmoothDouble
import net.minecraft.world.phys.Vec3
data class LandVehicleState(val entity: PokemonEntity) : RidingState {
var currSpeed: Double = 0.0
var stamina: Float = 1.0f
var currMouseXForce: Double = 0.0
var currMouseYForce: Double = 0.0
//I am thinking this will either be a ranging from -1.0 to 1.0
//When drift is first activated while a turn is initiated it will go to (the sign of the turn)*0.5.
//Then you are able to change it between +-1.0 and 0.25 which will determine how much forced rotation
//The drift gives you. The second component is the offset of the velocity vector. Might simply be able
//to add an equal amount of x velocity as the current z velocity? Or maybe just a transformation to turn
//it to the right?? This will be the hardest part actually... Because if you add a similar one then the
//magnitude willbe increased? Maybe just half both and then that effectively slows it? idk.
var currDrift: Double = 0.0
//Determines how much boost is left.
var driftBoost: Double = 0.0
var turnSmoother: SmoothDouble = SmoothDouble()
}
@@ -0,0 +1,15 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.pokemon.riding.states
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
class RunUpToFlightCompositeState(pokemonEntity: PokemonEntity) : CompositeState(pokemonEntity) {
var currSpeed: Double = 0.0
}
@@ -36,14 +36,16 @@ 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 ?: true
val loop = jsonObject.get("loop")?.asBoolean != false
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
frames = frames,
interpolation = interpolation
)
} else {
throw IllegalArgumentException("Invalid JSON provided for model texture, it was of type ${json::class.simpleName} instead of a String or Object.")
@@ -10,12 +10,15 @@ 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
@@ -33,15 +36,19 @@ import net.minecraft.resources.ResourceLocation
*/
object RideControllerAdapter : JsonDeserializer<RideController> {
val types: MutableMap<ResourceLocation, Class<out RideController>> = mutableMapOf(
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
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
)
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(json, TypeToken.get(Component::class.java).type, context) }
val description = json.get("description")?.let { TextAdapter.deserialize(json, TypeToken.get(Component::class.java).type, context) }
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) }
if (rangesJson.isJsonPrimitive) {
val range = IntRangeAdapter.deserialize(rangesJson, TypeToken.get(IntRange::class.java).type, context)
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.util.adapters
import com.google.gson.*
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import java.lang.reflect.Type
/**
* Adapter for deserializing SoundEvent types.
*
* @author Jackowes
* @since March 5th, 2025
*/
object SoundEventAdapter : JsonDeserializer<SoundEvent> {
override fun deserialize(json: JsonElement, type: Type, context: JsonDeserializationContext): SoundEvent {
val soundId = json.asString
// Attempt to grab the sound resource location. If incorrect then return null
val resourceLocation = ResourceLocation.read(soundId).result().orElse(null)
?: throw JsonParseException("Invalid sound event resource location: $soundId")
// Attempt to retrieve the SoundEvent from the registry
return BuiltInRegistries.SOUND_EVENT.getOptional(resourceLocation)
.orElseThrow { JsonParseException("Unknown sound event: $soundId") }
}
}
@@ -124,8 +124,14 @@
},
"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]
}
},
@@ -622,12 +628,21 @@
},
"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": {
@@ -996,6 +1011,20 @@
"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,7 +26,10 @@
"pivot": [0, 24.5, -1],
"cubes": [
{"origin": [-6, 18.5, -13.75], "size": [12, 12, 26], "uv": [52, 39]}
]
],
"locators": {
"seat_1": [0, 24, 0]
}
},
{
"name": "neck",
@@ -31,6 +31,7 @@ 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
@@ -112,6 +113,8 @@ 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,6 +50,48 @@
"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",
@@ -133,39 +175,7 @@
}
]
}
],
"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,6 +88,67 @@
}
]
},
"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,6 +50,87 @@
"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,6 +43,67 @@
"eggCycles": 20,
"baseFriendship": 50,
"baseScale": 1,
"riding": {
"stats": {
"SPEED": {
"displayName": "Speed",
"description": "Its top speed",
"ranges": {
"LAND": "100-101"
}
},
"ACCELERATION": {
"displayName": "Acceleration",
"description": "How fast it can speed up",
"ranges": {
"LAND": "100-101"
}
},
"SKILL": {
"displayName": "Handling",
"description": "How fast it can turn",
"ranges": {
"LAND": "100-101"
}
},
"JUMP": {
"displayName": "Altitude",
"description": "How high it can fly",
"ranges": {
"LAND": "100-101"
}
},
"STAMINA": {
"displayName": "Fuel/Stamina",
"description": "How long it can sprint for.",
"ranges": {
"LAND": "100-101"
}
}
},
"controller": {
"key": "cobblemon:land/generic"
},
"seats": [
{
"offset": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"poseAnimations": [
{
"animations": [
{
"fileName": "trainer_generic",
"animationName": "animation.trainer_generic.ride_test"
}
]
}
]
}
]
},
"hitbox": {
"width": 1.3,
"height": 2.6,
@@ -42,6 +42,68 @@
"catchRate": 75,
"eggCycles": 20,
"baseFriendship": 50,
"riding": {
"stats": {
"SPEED": {
"displayName": "Speed",
"description": "Its top speed",
"ranges": {
"LAND": "100-101"
}
},
"ACCELERATION": {
"displayName": "Acceleration",
"description": "How fast it can speed up",
"ranges": {
"LAND": "100-101"
}
},
"SKILL": {
"displayName": "Handling",
"description": "How fast it can turn",
"ranges": {
"LAND": "100-101"
}
},
"JUMP": {
"displayName": "Altitude",
"description": "How high it can fly",
"ranges": {
"LAND": "100-101"
}
},
"STAMINA": {
"displayName": "Fuel/Stamina",
"description": "How long it can stay off the ground",
"ranges": {
"LAND": "100-101"
}
}
},
"controller": {
"key": "cobblemon:land/vehicle",
"sound": "minecraft:entity.elder_guardian.flop"
},
"seats": [
{
"offset": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"poseAnimations": [
{
"animations": [
{
"fileName": "trainer_generic",
"animationName": "animation.trainer_generic.ride_test"
}
]
}
]
}
]
},
"behaviour": {
"resting": {
"canSleep": true,
@@ -40,6 +40,7 @@
"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)