Compare commits

..
Author SHA1 Message Date
jackowes 09eb3395c0 <temp and broken> add beginnings of recurring sound function 2025-08-18 21:42:26 -05:00
jackowes 48d289279a add char tests 2025-08-14 20:42:50 -05:00
Polymeta d141e6b46d Merge branch 'riding-controllers-expanded' into 'main'
Controller additions as well as many controller fixes and polish

See merge request cable-mc/cobblemon!1762
2025-08-12 15:13:04 +00:00
jackowes e683dedc2a rename rotateheadFunctions to unbreak 2025-08-12 09:25:16 -05:00
jackowes c7d6107cea Merge branch 'main' into riding-controllers-expanded 2025-08-11 22:52:55 -05:00
jackowes ef7e76dc07 move driverInput over to Player and check if input has changed before sending driverInput to the server. 2025-08-11 22:50:53 -05:00
jackowes 5697fa53f0 move driverInput over to ServerPlayer 2025-08-11 21:35:53 -05:00
Polymeta 2e87894dac Merge branch 'molang-commands' into 'main'
Add command to run MoLang expressions as well as add `entity` to RunMolangScriptCommand.kt.

See merge request cable-mc/cobblemon!1743
2025-08-11 13:46:05 +00:00
JordanandPolymeta 2dd4373890 Add command to run MoLang expressions as well as add entity to RunMolangScriptCommand.kt. 2025-08-11 13:46:05 +00:00
Test e33587fefc pc walls 2025-08-10 23:50:02 -04:00
Polymeta 1089fb2571 various fixes for suspicious stew (specifically pep up flower ingredient too) interactions with mooshtanks
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-08-11 00:57:22 +01:00
Polymeta db777381e7 Merge branch 'josh/locator-flip-fix' into 'main'
Only flip locators Z axis if model is attached to an entity

See merge request cable-mc/cobblemon!1767
2025-08-10 21:48:32 +00:00
Polymeta 8fee43c0b8 Merge branch 'orientable-entities' into 'main'
Orientation now works for Living Entities

See merge request cable-mc/cobblemon!1601
2025-08-10 21:37:48 +00:00
JordanandPolymeta d7160948cd Orientation now works for Living Entities 2025-08-10 21:37:48 +00:00
Polymeta 868b8ae662 Merge branch 'neva-enough-molang' into 'main'
Neva enough molang

See merge request cable-mc/cobblemon!1755
2025-08-10 21:31:29 +00:00
JordanandPolymeta d65884de5b Neva enough molang 2025-08-10 21:31:29 +00:00
Josh 7714d60b2e Only flip locators Z axis if model is attached to an entity 2025-08-10 16:52:08 -04:00
jackowes f4f6cbee38 Address MR comments 2025-08-10 02:02:49 -05:00
Hiroku 09883fd239 Merge branch 'pitch-tilting' into 'main'
Added pitch tilting animation for fishies and birds, implemented for magikarp and pidgey

See merge request cable-mc/cobblemon!1766
2025-08-09 05:25:38 +00:00
Paul 50cea69dfb Added delta_movement query function to entities 2025-08-09 15:12:08 +10:00
Paul 583892364d Added pitch tilting animation for fishies and birds, implemented for magikarp and pidgey 2025-08-09 13:02:34 +10:00
jackowes 1664b917f8 Dolphin time 2025-08-08 16:48:07 -05:00
Test 55587908a2 update state icons/ride controls overlay 2025-08-07 22:05:09 -04:00
jackowes e1ed483d67 Remove altitude limits on rides 2025-08-07 11:49:59 -05:00
jackowes c460bac177 Accessibility rides for jet and bird. Need hooked up to cobble settings/config and auto changed out if accessibility rides are enabled. Further refinement needed but waiting for the rolling counterparts to be polished first. 2025-08-06 20:29:08 -05:00
jackowes ad6771f8f4 minekart improvements 2025-08-05 21:34:52 -05:00
jackowes ad1a68cefe Add base of minekart behaviour and fix player head movement flag for a few rides 2025-08-05 16:19:18 -05:00
jackowes e8ab8fda71 Fix sound attenuation, inverse input_right() animation data, and properly destroy sounds after dismounting a pokmeon. 2025-08-05 10:09:00 -05:00
jackowes 7def27e437 dupe function from merge 2025-08-04 20:37:24 -05:00
jackowes f3db0ed61d hover anim tests 2025-08-04 20:29:10 -05:00
jackowes fdcff83a2d Merge branch 'main' into riding-controllers-expanded
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/land/VehicleBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/liquid/BoatBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
#	common/src/main/resources/assets/cobblemon/sounds.json
#	common/src/main/resources/data/cobblemon/species/generation1/charizard.json
2025-08-04 20:11:56 -05:00
jackowes 8815758bc0 Adjust all ride params after double tick error. Mirror deltamovement scale to ridevel to fix the catching on terrain issue. Scale smoothly between sprinting turn rate and walking turn rate on horse. 2025-08-04 20:00:34 -05:00
jackowes 1aaea961fb Fix cursed golurk jsons 2025-08-02 02:59:52 -05:00
jackowes 8253b96a67 Add multiplayer support for driver input tracking 2025-08-02 01:42:15 -05:00
jackowes 6593fcc250 fix jittery mons 2025-08-01 00:59:09 -05:00
jackowes f51376f9c3 quick fixes 2025-07-31 21:58:23 -05:00
jackowes 23ecc3dd05 Merge branch 'main' into riding-controllers-expanded
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/RidingBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/RidingController.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/GenericSwimBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/air/BirdBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/air/GliderBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/air/JetBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/land/HorseBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/land/VehicleBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/liquid/BurstBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
#	common/src/main/resources/assets/cobblemon/bedrock/pokemon/animations/0967_cyclizar/cyclizar.animation.json
#	common/src/main/resources/assets/cobblemon/bedrock/pokemon/models/0967_cyclizar/cyclizar.geo.json
#	common/src/main/resources/assets/cobblemon/bedrock/pokemon/posers/0967_cyclizar/cyclizar.json
#	common/src/main/resources/data/cobblemon/species/generation1/charizard.json
#	common/src/main/resources/data/cobblemon/species/generation9/cyclizar.json
2025-07-31 21:25:01 -05:00
jackowes 9e6f27162e quick tweak 2025-07-31 21:13:06 -05:00
jackowes 6c279e0986 vehicle tweaks 2025-07-31 21:13:06 -05:00
jackowes 76d1291180 vehicle v3 becoming 2025-07-31 21:13:06 -05:00
jackowes 627f09010b Start of vehicle v3 2025-07-31 21:13:03 -05:00
jackowes 9afd0e6fc3 nope 2025-07-31 21:11:28 -05:00
jackowes 1ea361cce9 I cannot for fine the culprit of this increase in vertical impulse when moving as opposed to standing still. :(. Also rocket improvements. 2025-07-31 21:11:28 -05:00
jackowes 41bf22c43a More rocket improvements
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/GenericSwimBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/RocketBehaviour.kt
2025-07-31 21:11:24 -05:00
jackowes 71989292d3 Various rocket improvements including: lower boost turning speed, turn momentum, behaviour switch time decrease, temp golurk flight animation tweaks for fluidity (need to check with gingle), also tweaked the feel and air resistance of the ride as a whole 2025-07-31 21:10:35 -05:00
jackowes f46b13da6a Quick fix to velocity zeroing for dimensions that horse and rocket don't care about. 2025-07-31 21:10:35 -05:00
JacksonB ab1f169fcc add forgotten rocket loop 2025-05-20 22:35:59 -05:00
JacksonB ce5928e783 Add rocket sounds 2025-05-20 22:35:15 -05:00
JacksonB e3e40627e8 Beginnings of rocket controller (for real this time) 2025-05-16 21:41:20 -05:00
JacksonB 140158f5bb Default riding sounds to only be heard by passengers and default sounds to not muffle or reduce in volume when behind a block. Also move the attenuation logic out of RideLoopSound and have openAL handle all attenuation models. 2025-05-11 19:33:00 -05:00
JacksonB aeec991e7b add license 2025-04-27 22:44:06 -05:00
JacksonB 7751b13611 merge main 2025-04-27 18:50:35 -05:00
JacksonB e0cbc0bdc3 Riding Sound refactor to support and arbitrary number of sounds per behaviour per species. Implemented a fade in and out for any rideSound instance and added support for a handful of configurations when specifying the sound. 2025-04-27 16:52:04 -05:00
JacksonB 2f64ab1018 merge main 2025-04-26 17:33:09 -05:00
JacksonB 6c74d30ce8 Fix some filter issues and add saucer noise for magnezone 2025-04-26 17:26:35 -05:00
JacksonB 25956d5f23 temp commit for sound fixes 2025-04-25 16:16:15 -05:00
JacksonB f6832cd44c temp commit for cone raycasting 2025-04-25 11:46:33 -05:00
JacksonB 313e9dadb6 Add soundOcclusion, doppler effect, and custom non linear sound attenuation. 2025-04-25 09:05:43 -05:00
JacksonB 1d171268ed Behaviours can specify a looping ride sound and control its volume and pitch dynamically. Still need to allow multiple sound instances and also clean up my wip code 2025-04-23 00:34:13 -05:00
133 changed files with 13579 additions and 2055 deletions
+8 -14
View File
@@ -40,7 +40,9 @@
- Ground type Pokémon are immune to lightning damage
- Added functionality to Everstone when held by a Pokémon; suppresses evolution notification and hides evolve button in summary interface.
- Added new optional property `attachment_options` for most EmitterShapes to be attached to the locator/entities scale, rotation, and/or position. Position is true by default.
- Galarica Nut bushes now generate on beaches
- Galarica Nut bushes now generate on beaches.
- Some Pokémon now pitch their bodies in the direction they're moving.
- Added `/runmolang <molang> [<npc>|<player>|<pokemon>]` command that executes a MoLang expression with the provided options as environment variables, as well as the entity (as `q.entity`) that executed the command.
### Pokémon Added
@@ -194,19 +196,6 @@
### Changes
- Changed pokemon caught and seen count to update based on the current pokedex being looked
- Renamed `chargeGainedPerTick` config to `secondsToChargeHealingMachine`.
- Added config setting `maxPokedexScanningDetectionRange` to control from what distance the player can scan Pokémon using the Pokédex.
- Added the `is_standing_on_blocks(depth, blocks...)` MoLang function, which returns whether the specified entity is standing on a specific block or set of blocks. Example usage: `q.is_standing_on_blocks(2, minecraft:sand)`
- Added config setting `hideUnimplementedPokemonInThePokedex` which hides unimplemented Pokémon from the Pokédex when set to true.
- Added molang particle queries for getting distance to targeted entities
- Added debug renderer for poseable entity locators
- Added time zone as a config option
- Added optional boolean to use time zone for MoLang function "date_local_time" and "date_of"
- Added "irlDateRange" and "irlTimeRange" as a condition for spawning
- Added an optional MoValue to either use system timezone when set as true, or use the supplied string as a timezone for MoLang functions `date_local_time` and `date_of`. If the timezone supplied fails to parse this function now returns `0.0`.
### Additions
- Added time zone as a config option
- Added optional boolean to use time zone for MoLang function "date_local_time" and "date_of"
- Made Blocks of Gold count as Big Nuggets when held by a Pokémon (for Fling functionality)
- Players can now eat Sweet and Tart Apples, Whipped Dreams, and the Alcremie Sweets.
- Updated Sweet and Tart Apple sprites
@@ -253,6 +242,11 @@
- Offset in EmitterShape now ignores scale to be more like Blockbench by default. You can get this behaviour back by adding `"scale": true` in the `attachment_options` property in most EmitterShapes.
- Not specifying a dex in `/pokedex printcalculations {player} {dex}` will now print the National Dex statistics instead of showing all dexes. `/pokedex printcalculations {player} all` is how to view all dex statistics in one command.
- Removed Braised Vivichoke
- Updated Pokémon state icons shown in party interfaces
- MoLang `face` function can now be run on any `PosableEntity` (Like Pokémon!) instead of just NPCs.
- MoLang `run_action_effect` now works on Pokémon.
- Changed MoLang entity function `is_standing_on` to allow for block tags in the list.
- Added entity (as `q.entity`) that executed the command to the `executemolangscript` command.
### Fixes
- Fixed game crashing when removing national pokedex using datapacks
@@ -9,15 +9,6 @@
package com.cobblemon.mod.common;
import com.cobblemon.mod.common.api.orientation.OrientationController;
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;
import java.util.function.Function;
public interface OrientationControllable {
OrientationController getOrientationController();
@@ -0,0 +1,18 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.duck;
import org.joml.Vector3f;
public interface PlayerDuck {
void setDriverInput(Vector3f driverInput);
Vector3f getDriverInput();
void setLastSentDriverInput(Vector3f driverInput);
Vector3f getLastSentDriverInput();
}
@@ -8,17 +8,44 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffectRegistry;
import com.cobblemon.mod.common.api.riding.Seat;
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate;
import com.cobblemon.mod.common.client.render.MatrixWrapper;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import org.spongepowered.asm.mixin.Mixin;
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.callback.CallbackInfo;
@Mixin(LivingEntity.class)
public abstract class LivingEntityMixin {
public abstract class LivingEntityMixin extends Entity implements OrientationControllable {
public LivingEntityMixin(EntityType<?> entityType, Level level) {
super(entityType, level);
}
@Unique
private final OrientationController cobblemon$orientationController = new OrientationController((LivingEntity) (Object) this);
public OrientationController getOrientationController() {
return cobblemon$orientationController;
}
@Inject(method = "onEffectRemoved", at = @At(value = "TAIL"))
private void cobblemon$onEffectRemoved(MobEffectInstance effect, CallbackInfo ci) {
@@ -28,4 +55,67 @@ public abstract class LivingEntityMixin {
}
}
@Override
public @NotNull HitResult pick(double hitDistance, float partialTicks, boolean hitFluids) {
Entity vehicle = this.getVehicle();
if (vehicle instanceof PokemonEntity pokemonEntity) {
int seatIndex = pokemonEntity.getPassengers().indexOf(this);
Seat seat = pokemonEntity.getSeats().get(seatIndex);
PokemonClientDelegate delegate = (PokemonClientDelegate) pokemonEntity.getDelegate();
MatrixWrapper locator = delegate.getLocatorStates().get(seat.getLocator());
Vec3 locatorOffset = new Vec3(locator.getMatrix().getTranslation(new Vector3f()));
Vec3 eyePosition = cobblemon$getEyePosition(partialTicks, pokemonEntity, locatorOffset);
Vec3 viewVector = this.getViewVector(partialTicks);
Vec3 viewDistanceVector = eyePosition.add(viewVector.x * hitDistance, viewVector.y * hitDistance, viewVector.z * hitDistance);
return this.level()
.clip(
new ClipContext(
eyePosition, viewDistanceVector, ClipContext.Block.OUTLINE, hitFluids ? net.minecraft.world.level.ClipContext.Fluid.ANY : net.minecraft.world.level.ClipContext.Fluid.NONE, this
)
);
}
return super.pick(hitDistance, partialTicks, hitFluids);
}
@Unique
private Vec3 cobblemon$getEyePosition(float partialTicks, PokemonEntity pokemonEntity, Vec3 locatorOffset) {
OrientationController controller = this.getOrientationController();
float currEyeHeight = this.getEyeHeight();
Vec3 offset = locatorOffset.add(pokemonEntity.position());
if (controller.isActive() && controller.getOrientation() != null) {
Quaternionf orientation = controller.getRenderOrientation(partialTicks);
Vec3 rotatedEyeHeight = new Vec3(orientation.transform(new Vector3f(0f, currEyeHeight - (this.getBbHeight() / 2), 0f)));
offset.add(rotatedEyeHeight);
}
return offset;
}
@Inject(method = "tick", at = @At("HEAD"))
private void cobblemon$updateRenderOrientation(CallbackInfo ci) {
this.cobblemon$orientationController.tick();
}
@Override
public void absMoveTo(double x, double y, double z, float yaw, float pitch) {
if (cobblemon$orientationController.getActive()) {
this.absMoveTo(x, y, z);
this.setYRot(yaw % 360.0f);
this.setXRot(pitch % 360.0f);
this.yRotO = this.getYRot();
this.xRotO = this.getXRot();
} else {
this.absMoveTo(x, y, z);
this.absRotateTo(yaw, pitch);
}
}
}
@@ -18,6 +18,7 @@ import com.cobblemon.mod.common.api.pokemon.PokemonSpecies;
import com.cobblemon.mod.common.api.storage.party.PartyStore;
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore;
import com.cobblemon.mod.common.api.tags.CobblemonItemTags;
import com.cobblemon.mod.common.duck.PlayerDuck;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.pokedex.scanner.PokedexEntityData;
import com.cobblemon.mod.common.pokedex.scanner.ScannableEntity;
@@ -47,6 +48,7 @@ import net.minecraft.world.food.FoodProperties;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector3f;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@@ -61,7 +63,12 @@ import java.util.*;
import java.util.stream.Collectors;
@Mixin(Player.class)
public abstract class PlayerMixin extends LivingEntity implements ScannableEntity, OrientationControllable {
public abstract class PlayerMixin extends LivingEntity implements ScannableEntity, OrientationControllable, PlayerDuck {
@Unique
private Vector3f cobblemon$driverInput;
@Unique
private Vector3f cobblemon$lastSentDriverInput;
@Shadow public abstract CompoundTag getShoulderEntityLeft();
@@ -279,4 +286,25 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
public OrientationController getOrientationController() {
return cobblemon$orientationController;
}
@Override
public void setDriverInput(Vector3f driverInput) {
cobblemon$driverInput = driverInput;
}
@Override
public Vector3f getDriverInput() {
return cobblemon$driverInput;
}
@Override
public void setLastSentDriverInput(Vector3f lastSentDriverInput) {
cobblemon$lastSentDriverInput = lastSentDriverInput;
}
@Override
public Vector3f getLastSentDriverInput() {
return cobblemon$lastSentDriverInput;
}
}
@@ -12,9 +12,11 @@ import com.cobblemon.mod.common.CobblemonNetwork;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.api.net.NetworkPacket;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.duck.PlayerDuck;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.mixin.accessor.ChunkMapAccessor;
import com.cobblemon.mod.common.mixin.accessor.TrackedEntityAccessor;
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateDriverInputPacket;
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateOrientationPacket;
import com.cobblemon.mod.common.net.messages.client.pokemon.update.ClientboundUpdateRidingStatePacket;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
@@ -26,6 +28,7 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerPlayerConnection;
import net.minecraft.world.entity.Entity;
import org.joml.Matrix3f;
import org.joml.Vector3f;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -55,6 +58,7 @@ public abstract class ServerEntityMixin {
private void cobblemon$sendChanges(CallbackInfo ci) {
cobblemon$sendOrientationChanges();
cobblemon$sendRidingStateChanges();
cobblemon$sendDriverInput();
}
private void cobblemon$sendRidingStateChanges() {
@@ -82,6 +86,19 @@ public abstract class ServerEntityMixin {
cobblemon$broadcast(new ClientboundUpdateOrientationPacket(currOrientation, currActive, entity.getId()));
}
private void cobblemon$sendDriverInput() {
if (!(this.entity instanceof ServerPlayer serverPlayer)) return;
if(!(serverPlayer.getVehicle() instanceof PokemonEntity)) return;
Vector3f driverInput = ((PlayerDuck)serverPlayer).getDriverInput();
if(driverInput == null) return;
// If no change in input is detected then don't send a new packet
Vector3f lastSentDriverInput = ((PlayerDuck)serverPlayer).getLastSentDriverInput();
if (driverInput.equals(lastSentDriverInput)) return;
((PlayerDuck)serverPlayer).setLastSentDriverInput(driverInput);
cobblemon$broadcast(new ClientboundUpdateDriverInputPacket(driverInput, entity.getId()));
}
private void cobblemon$broadcast(NetworkPacket<?> packet) {
if (!(entity.level() instanceof ServerLevel level)) return;
ChunkMap chunkMap = level.getChunkSource().chunkMap;
@@ -97,5 +114,4 @@ public abstract class ServerEntityMixin {
}
}
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.mixin.accessor;
import net.minecraft.server.level.ServerEntity;
import net.minecraft.server.network.ServerPlayerConnection;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@@ -18,4 +19,7 @@ import java.util.Set;
public interface TrackedEntityAccessor {
@Accessor("seenBy")
Set<ServerPlayerConnection> getSeenBy();
@Accessor("serverEntity")
ServerEntity getServerEntity();
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.client.render.player.MountedPlayerRenderer;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.client.model.HumanoidModel;
@@ -59,9 +60,9 @@ public class HumanoidModelMixin {
}
Entity vehicle = entity.getVehicle();
if (vehicle instanceof PokemonEntity pokemonEntity && entity instanceof AbstractClientPlayer) {
if (vehicle instanceof PokemonEntity pokemonEntity && entity instanceof OrientationControllable) {
var shouldRotatePlayerHead = pokemonEntity.ifRidingAvailableSupply(false, (behaviour, settings, state) -> {
return behaviour.shouldRotatePlayerHead(settings, state, pokemonEntity);
return behaviour.shouldRotateRiderHead(settings, state, pokemonEntity);
});
if (!shouldRotatePlayerHead) {
@@ -73,7 +74,9 @@ public class HumanoidModelMixin {
}
}
MountedPlayerRenderer.INSTANCE.animate(pokemonEntity, (AbstractClientPlayer) entity, relevantPartsByName, netHeadYaw, headPitch, ageInTicks, limbSwing, limbSwingAmount);
if (!(entity instanceof AbstractClientPlayer player)) return;
MountedPlayerRenderer.INSTANCE.animate(pokemonEntity, player, relevantPartsByName, netHeadYaw, headPitch, ageInTicks, limbSwing, limbSwingAmount);
}
}
}
@@ -0,0 +1,37 @@
/*
* 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.client;
import com.cobblemon.mod.common.client.render.entity.MountedLivingEntityRenderer;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.model.EntityModel;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraft.world.entity.LivingEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LivingEntityRenderer.class)
public class LivingEntityRendererMixin<T extends LivingEntity, M extends EntityModel<T>> {
@Inject(
method = "setupRotations",
at = @At("HEAD")
)
private void cobblemon$modifyRoll(LivingEntity entity, PoseStack poseStack, float bob, float yBodyRot, float partialTick, float scale, CallbackInfo ci) {
if (entity.isPassenger()) {
var vehicle = entity.getVehicle();
if (vehicle instanceof PokemonEntity pokemonEntity) {
MountedLivingEntityRenderer.INSTANCE.render(entity, pokemonEntity, poseStack, bob, yBodyRot, partialTick, scale);
}
}
}
}
@@ -17,8 +17,10 @@ import com.cobblemon.mod.common.client.render.MatrixWrapper;
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState;
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel;
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository;
import com.cobblemon.mod.common.duck.PlayerDuck;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.net.messages.server.orientation.ServerboundUpdateOrientationPacket;
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateDriverInputPacket;
import com.mojang.authlib.GameProfile;
import net.minecraft.client.CameraType;
import net.minecraft.client.Minecraft;
@@ -28,6 +30,7 @@ import net.minecraft.client.player.LocalPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.HitResult;
@@ -83,6 +86,23 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
controllable.getOrientationController().setActive(shouldUseCustomOrientation);
}
@Inject(method = "rideTick", at = @At("HEAD"))
private void cobblemon$updateDriverInputRideTick(CallbackInfo ci) {
if (Minecraft.getInstance().player != (Object)this) return;
if (!(this.getVehicle() instanceof PokemonEntity pokemonEntity)) return;
// Gather driver Input
float vertInput = this.jumping ? 1.0f : (((Player)this).isShiftKeyDown() ? -1.0f : 0.0f);
Vector3f driverInput = new Vector3f(Math.signum(this.xxa), vertInput, Math.signum(this.zza));
// Check if player input has changed. If it has then update the last sent and send it
Vector3f lastSentDriverInput = ((PlayerDuck)this).getLastSentDriverInput();
if (driverInput.equals(lastSentDriverInput)) return;
((PlayerDuck)this).setLastSentDriverInput(driverInput);
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateDriverInputPacket(driverInput));
}
@Unique
private boolean cobblemon$shouldUseCustomOrientation(LocalPlayer player) {
var playerVehicle = player.getVehicle();
@@ -127,7 +147,7 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
return super.pick(hitDistance, partialTicks, hitFluids);
}
Vec3 locatorOffset = new Vec3(locator.getMatrix().getTranslation(new Vector3f()));
Vec3 locatorOffset = locator != null ? new Vec3(locator.getMatrix().getTranslation(new Vector3f())) : Vec3.ZERO;
OrientationController controller = this.getOrientationController();
@@ -9,12 +9,10 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.client.render.ClientPlayerIcon;
import com.cobblemon.mod.common.client.render.player.MountedPlayerRenderer;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.model.EntityModel;
import net.minecraft.client.model.PlayerModel;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.renderer.MultiBufferSource;
@@ -55,17 +53,4 @@ public abstract class PlayerRendererMixin extends LivingEntityRenderer<AbstractC
playerModel.crouching = false;
}
}
@Inject(
method = "setupRotations(Lnet/minecraft/client/player/AbstractClientPlayer;Lcom/mojang/blaze3d/vertex/PoseStack;FFFF)V",
at = @At("HEAD")
)
private void cobblemon$modifyRoll(AbstractClientPlayer player, PoseStack poseStack, float a, float b, float partialTicks, float i, CallbackInfo ci) {
if (player.isPassenger()) {
var vehicle = player.getVehicle();
if (vehicle instanceof PokemonEntity entity) {
MountedPlayerRenderer.INSTANCE.render(player, entity, poseStack, a, b, partialTicks, i);
}
}
}
}
@@ -59,6 +59,7 @@ object CobblemonCommands {
ApplyPlayerTextureCommand.register(dispatcher)
RenameBoxCommand.register(dispatcher)
ChangeBoxWallpaperCommand.register(dispatcher)
RunMolangCommand.register(dispatcher)
RunMolangScriptCommand.register(dispatcher)
SpectateBattleCommand.register(dispatcher)
CobblemonConfigCommand.register(dispatcher)
@@ -41,6 +41,7 @@ import com.cobblemon.mod.common.client.net.pasture.UpdatePastureConflictFlagHand
import com.cobblemon.mod.common.client.net.pokedex.ServerConfirmedRegisterHandler
import com.cobblemon.mod.common.client.net.pokemon.update.ClientboundUpdateRidingStateHandler
import com.cobblemon.mod.common.client.net.pokemon.update.PokemonUpdatePacketHandler
import com.cobblemon.mod.common.client.net.riding.ClientboundUpdateDriverInputHandler
import com.cobblemon.mod.common.client.net.settings.OpenCobblemonConfigEditorHandler
import com.cobblemon.mod.common.client.net.settings.ServerSettingsPacketHandler
import com.cobblemon.mod.common.client.net.sound.UnvalidatedPlaySoundS2CPacketHandler
@@ -83,6 +84,7 @@ import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityP
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticlePacket
import com.cobblemon.mod.common.net.messages.client.npc.CloseNPCEditorPacket
import com.cobblemon.mod.common.net.messages.client.npc.OpenNPCEditorPacket
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateDriverInputPacket
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateOrientationPacket
import com.cobblemon.mod.common.net.messages.client.pasture.ClosePasturePacket
import com.cobblemon.mod.common.net.messages.client.pasture.OpenPasturePacket
@@ -173,6 +175,7 @@ import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetMarkingsPa
import com.cobblemon.mod.common.net.messages.server.pokemon.update.ServerboundUpdateRidingStatePacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.evolution.AcceptEvolutionPacket
import com.cobblemon.mod.common.net.messages.server.riding.DismountPokemonPacket
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateDriverInputPacket
import com.cobblemon.mod.common.net.messages.server.starter.RequestStarterScreenPacket
import com.cobblemon.mod.common.net.messages.server.storage.SwapPCPartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.party.MovePartyPokemonPacket
@@ -223,6 +226,7 @@ import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetActiveMarkH
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetMarkingsHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.ServerboundUpdateRidingStateHandler
import com.cobblemon.mod.common.net.serverhandling.riding.DismountPokemonPacketHandler
import com.cobblemon.mod.common.net.serverhandling.riding.DriverInputPacketHandler
import com.cobblemon.mod.common.net.serverhandling.starter.RequestStarterScreenHandler
import com.cobblemon.mod.common.net.serverhandling.starter.SelectStarterPacketHandler
import com.cobblemon.mod.common.net.serverhandling.storage.BenchMoveHandler
@@ -465,6 +469,7 @@ object CobblemonNetwork {
// Riding
list.add(PacketRegisterInfo(ClientboundUpdateRidingStatePacket.ID, ClientboundUpdateRidingStatePacket::decode, ClientboundUpdateRidingStateHandler))
list.add(PacketRegisterInfo(ClientboundUpdateDriverInputPacket.ID, ClientboundUpdateDriverInputPacket::decode, ClientboundUpdateDriverInputHandler))
return list
}
@@ -571,6 +576,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(ServerboundUpdateRidingStatsPacket.ID, ServerboundUpdateRidingStatsPacket::decode, ServerboundUpdateRidingStatsHandler))
list.add(PacketRegisterInfo(ServerboundUpdateRidingStatRangePacket.ID, ServerboundUpdateRidingStatRangePacket::decode, ServerboundUpdateRidingStatRangeHandler))
list.add(PacketRegisterInfo(DismountPokemonPacket.ID, DismountPokemonPacket::decode, DismountPokemonPacketHandler))
list.add(PacketRegisterInfo(ServerboundUpdateDriverInputPacket.ID, ServerboundUpdateDriverInputPacket::decode, DriverInputPacketHandler))
// Cooking
list.add(PacketRegisterInfo(ToggleCookingPotLidPacket.ID, ToggleCookingPotLidPacket::decode, ToggleCookingPotLidHandler))
@@ -38,6 +38,7 @@ import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.animations.NPCProvider
import com.cobblemon.mod.common.api.moves.animations.TargetsProvider
import com.cobblemon.mod.common.api.npc.NPCClasses
import com.cobblemon.mod.common.api.npc.configuration.interaction.DialogueNPCInteractionConfiguration
import com.cobblemon.mod.common.api.npc.configuration.interaction.ScriptNPCInteractionConfiguration
@@ -143,12 +144,6 @@ import net.minecraft.world.level.levelgen.Heightmap
import net.minecraft.world.level.pathfinder.PathType
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import java.util.*
/**
* Holds a bunch of useful MoLang trickery that can be used or extended in API
@@ -295,44 +290,16 @@ object MoLangFunctions {
DoubleValue(System.currentTimeMillis())
},
// the rest of the world use dd/MM/yyyy grow up america (this comment was generated by copilot)
"date_local_time" to java.util.function.Function { params ->
val useTimeZone = params.get<MoValue>(0)
var timezone: ZoneId? = null
if (useTimeZone is DoubleValue){
timezone = if (useTimeZone.value == 1.0) Cobblemon.config.timeZoneId else null
} else if (useTimeZone is StringValue){
try {
timezone = ZoneId.of(useTimeZone.value)
} catch (_: Exception) {
Cobblemon.LOGGER.error("Error parsing timezone: ${useTimeZone.value}")
return@Function DoubleValue.ZERO
}
}
val date: LocalDateTime = if (timezone != null) LocalDateTime.now(timezone) else LocalDateTime.now()
val formatted = DateTimeFormatter.ofPattern("dd/MM/uuuu").format(date)
"date_local_time" to java.util.function.Function { _ ->
val time = System.currentTimeMillis()
val date = java.util.Date(time)
val formatted = java.text.SimpleDateFormat("DD/MM/YYYY").format(date)
StringValue(formatted)
},
"date_of" to java.util.function.Function { params ->
val time = params.getDouble(0).toLong()
val useTimeZone = params.get<MoValue>(1)
var timezone: ZoneId? = null
if (useTimeZone is DoubleValue){
timezone = if (useTimeZone.value == 1.0) Cobblemon.config.timeZoneId else null
} else if (useTimeZone is StringValue){
try {
timezone = ZoneId.of(useTimeZone.value)
} catch (_: Exception) {
Cobblemon.LOGGER.error("Error parsing timezone: ${useTimeZone.value}")
return@Function DoubleValue.ZERO
}
}
val date = java.util.Date(time)
val instant: Instant = date.toInstant()
val formatted = DateTimeFormatter.ofPattern("dd/MM/uuuu").format(LocalDateTime.ofInstant(instant,
timezone ?: ZoneOffset.systemDefault()
))
val formatted = java.text.SimpleDateFormat("DD/MM/YYYY").format(date)
StringValue(formatted)
},
"date_is_after" to java.util.function.Function { params ->
@@ -694,7 +661,7 @@ object MoLangFunctions {
map
}
)
val itemStackFunctions = mutableListOf<(ItemStack, RegistryAccess) -> java.util.HashMap<String, java.util.function.Function<MoParams, Any>>>(
val itemStackFunctions = mutableListOf<(ItemStack, RegistryAccess) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ stack, registryAccess ->
val itemRegistry = registryAccess.registryOrThrow(Registries.ITEM)
val holder = itemRegistry.wrapAsHolder(stack.item)
@@ -713,7 +680,7 @@ object MoLangFunctions {
}
)
val entityFunctions: MutableList<(Entity) -> java.util.HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
val entityFunctions: MutableList<(Entity) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
{ entity ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("uuid") { _ -> StringValue(entity.uuid.toString()) }
@@ -741,6 +708,9 @@ object MoLangFunctions {
val owner = entity.owner
return@put owner?.asMostSpecificMoLangValue() ?: DoubleValue.ZERO
}
map.put("delta_movement") {
return@put listOf(entity.deltaMovement.x, entity.deltaMovement.y, entity.deltaMovement.z).asArrayValue(::DoubleValue)
}
if (entity is MoLangScriptingEntity) {
map.put("add_callback") { params ->
@@ -885,14 +855,12 @@ object MoLangFunctions {
}
map.put("is_standing_on_blocks") { params ->
val depth = params.getDouble(0).toInt()
val blocks: MutableSet<Block> = mutableSetOf()
val blockStrings: MutableSet<String> = mutableSetOf()
for (blockIndex in 1..<params.params.size) {
val blockString = params.getString(blockIndex)
val block = BuiltInRegistries.BLOCK.get(blockString.asIdentifierDefaultingNamespace("minecraft"))
blocks.add(block)
blockStrings.add(params.getString(blockIndex))
}
return@put if (entity.isStandingOn(blocks, depth)) DoubleValue.ONE else DoubleValue.ZERO
return@put if (entity.isStandingOn(blockStrings, depth)) DoubleValue.ONE else DoubleValue.ZERO
}
if (entity is PosableEntity) {
map.put("play_animation") { params ->
@@ -918,6 +886,10 @@ object MoLangFunctions {
return@put DoubleValue.ONE
}
}
map.put("face") { params -> ObjectValue(ReferenceDialogueFaceProvider(entity.id, params.getBooleanOrNull(0) != false))
}
}
if (entity is Schedulable) {
map.put("run_molang_after") { params ->
@@ -951,7 +923,7 @@ object MoLangFunctions {
}
)
val livingEntityFunctions: MutableList<(LivingEntity) -> java.util.HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf<(LivingEntity) -> java.util.HashMap<String, java.util.function.Function<MoParams, Any>>>(
val livingEntityFunctions: MutableList<(LivingEntity) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf<(LivingEntity) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ entity ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("is_living_entity") { DoubleValue.ONE }
@@ -1087,7 +1059,6 @@ object MoLangFunctions {
map.put("name") { StringValue(npc.name.string) }
map.put("level") { DoubleValue(npc.level) }
map.put("has_aspect") { params -> DoubleValue(npc.aspects.contains(params.getString(0))) }
map.put("face") { params -> ObjectValue(ReferenceDialogueFaceProvider(npc.id, params.getBooleanOrNull(0) != false)) }
map.put("in_battle") { DoubleValue(npc.isInBattle()) }
map.put("battles") { ArrayStruct(npc.battleIds.mapNotNull { BattleRegistry.getBattle(it)?.struct }.mapIndexed { index, value -> "$index" to value }.toMap()) }
map.put("stop_battles") { _ -> npc.battleIds.forEach { BattleRegistry.getBattle(it)?.stop() } }
@@ -1693,6 +1664,8 @@ object MoLangFunctions {
map.put("get_alt_pose") { StringValue(pokemonEntity.getAltPose()) }
map.put("is_gliding") { DoubleValue(pokemonEntity.isUsingAltPose(cobblemonResource("gliding"))) }
map.put("is_sprinting") { DoubleValue(pokemonEntity.isUsingAltPose(cobblemonResource("sprinting"))) }
map.put("is_drifting") { DoubleValue(pokemonEntity.isUsingAltPose(cobblemonResource("drifting"))) }
map.put("is_powered_drifting") { DoubleValue(pokemonEntity.isUsingAltPose(cobblemonResource("powered_drifting"))) }
map.put("in_air") { DoubleValue(pokemonEntity.isUsingAltPose(cobblemonResource("in_air"))) }
map.put("is_wild") { DoubleValue(pokemonEntity.ownerUUID == null) }
map.put("is_in_party") { DoubleValue(pokemonEntity.pokemon.storeCoordinates.get()?.store is PartyStore) }
@@ -1710,6 +1683,34 @@ object MoLangFunctions {
map.put("pasture_conflict_enabled") {
DoubleValue(pokemonEntity.getBehaviourFlag(PokemonBehaviourFlag.PASTURE_CONFLICT))
}
map.put("run_action_effect") { params ->
val runtime = MoLangRuntime().setup()
runtime.environment.cloneFrom(params.environment)
runtime.withQueryValue("entity", pokemonEntity.struct)
val actionEffect = ActionEffects.actionEffects[params.getString(0).asIdentifierDefaultingNamespace()]
if (actionEffect != null) {
val context = ActionEffectContext(
actionEffect = actionEffect,
providers = mutableListOf(TargetsProvider(pokemonEntity)),
runtime = runtime,
level = pokemonEntity.level()
)
// This was taken from NPC run_action_effect but this variable isn't used, so I just commented it out.
// pokemonEntity.actionEffect = context
pokemonEntity.brain.setMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT, context)
pokemonEntity.brain.setActiveActivityIfPossible(CobblemonActivities.ACTION_EFFECT)
actionEffect.run(context).thenRun {
val pokemonActionEffect = pokemonEntity.brain.getMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT).orElse(null)
if (pokemonActionEffect == context && pokemonEntity.brain.isActive(CobblemonActivities.ACTION_EFFECT)) {
pokemonEntity.brain.eraseMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT)
//pokemonEntity.actionEffect = null
}
}
return@put DoubleValue.ONE
}
return@put DoubleValue.ZERO
}
map
}
)
@@ -2020,7 +2021,7 @@ object MoLangFunctions {
}
)
val pokemonPropertiesFunctions: MutableList<(PokemonProperties) -> java.util.HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
val pokemonPropertiesFunctions: MutableList<(PokemonProperties) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
{ props ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
@@ -2078,7 +2079,7 @@ object MoLangFunctions {
}
)
val evolutionFunctions: MutableList<(Evolution) -> java.util.HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
val evolutionFunctions: MutableList<(Evolution) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
{ evolution ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
@@ -10,14 +10,12 @@ package com.cobblemon.mod.common.api.orientation
import com.cobblemon.mod.common.util.math.geometry.toDegrees
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
import net.minecraft.world.entity.LivingEntity
import org.joml.Matrix3f
import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.asin
import kotlin.math.sign
open class OrientationController(val entity: LivingEntity) {
@@ -132,6 +132,8 @@ object CobblemonPermissions {
@JvmStatic
val ABANDON_MULTITEAM = this.create("${COMMAND_PREFIX}abandonmultiteam", PermissionLevel.NONE)
@JvmStatic
val RUN_MOLANG = this.create("${COMMAND_PREFIX}runmolang", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val RUN_MOLANG_SCRIPT = this.create("${COMMAND_PREFIX}runmolangscript", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val COBBLEMON_CONFIG_RELOAD = this.create("${COMMAND_PREFIX}cobblemonconfig.reload", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@@ -87,7 +87,7 @@ interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBeha
fun shouldRotatePokemonHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun shouldRotatePlayerHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun shouldRotateRiderHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun getRideSounds(settings: Settings, state: State, vehicle: PokemonEntity): RideSoundSettingsList
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.api.riding.behaviour.types.air.*
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.MinekartBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BoatBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BurstBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.DolphinBehaviour
@@ -22,15 +23,19 @@ object RidingBehaviours {
init {
register(BirdBehaviour.KEY, BirdBehaviour())
register(BirdNoRollBehaviour.KEY, BirdNoRollBehaviour())
register(DolphinBehaviour.KEY, DolphinBehaviour())
register(HorseBehaviour.KEY, HorseBehaviour())
register(BoatBehaviour.KEY, BoatBehaviour())
register(GliderBehaviour.KEY, GliderBehaviour())
register(HelicopterBehaviour.KEY, HelicopterBehaviour())
register(JetBehaviour.KEY, JetBehaviour())
register(JetNoRollBehaviour.KEY, JetNoRollBehaviour())
register(BurstBehaviour.KEY, BurstBehaviour())
register(VehicleBehaviour.KEY, VehicleBehaviour())
register(MinekartBehaviour.KEY, MinekartBehaviour())
register(HoverBehaviour.KEY, HoverBehaviour())
register(RocketBehaviour.KEY, RocketBehaviour())
register(CompositeBehaviour.KEY, CompositeBehaviour())
}
@@ -120,9 +120,9 @@ class RidingController<Settings : RidingBehaviourSettings, State : RidingBehavio
return behaviour.canJump(settings, state, vehicle, driver)
}
override fun shouldRotatePlayerHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
override fun shouldRotateRiderHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.shouldRotatePlayerHead(settings, state, vehicle)
return behaviour.shouldRotateRiderHead(settings, state, vehicle)
}
override fun shouldRotatePokemonHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
@@ -16,7 +16,6 @@ import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideLoopSound
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -25,7 +24,6 @@ import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
@@ -107,14 +105,6 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
upForce -= 0.3
}
val altitudeLimit = vehicle.runtime.resolveDouble(settings.jumpExpr)
//Only limit altitude if altitude is not infinite
if (!vehicle.runtime.resolveBoolean(settings.infiniteAltitude)) {
//Provide a hard limit on altitude
upForce = if (vehicle.y >= altitudeLimit && upForce > 0) 0.0 else upForce
}
val velocity = Vec3(state.rideVelocity.get().x , upForce, forwardForce)
return velocity
@@ -129,12 +119,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
val glideTopSpeed = vehicle.runtime.resolveDouble(settings.glidespeedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val staminaStat = vehicle.runtime.resolveDouble(settings.staminaExpr)
var glideSpeedChange = 0.0
val currSpeed = state.rideVelocity.get().length()
//Flag for determining if player is actively inputting
var activeInput = false
var newVelocity = Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, state.rideVelocity.get().z)
@@ -464,12 +449,12 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
return false
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity
): Boolean {
return true
return false
}
override fun getRideSounds(
@@ -0,0 +1,518 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import org.joml.Vector3f
import kotlin.math.*
class BirdNoRollBehaviour : RidingBehaviour<BirdNoRollSettings, BirdNoRollState> {
companion object {
val KEY = cobblemonResource("air/birdnoroll")
}
override val key: ResourceLocation = KEY
override fun getRidingStyle(settings: BirdNoRollSettings, state: BirdNoRollState): RidingStyle {
return RidingStyle.AIR
}
val poseProvider = PoseProvider<BirdNoRollSettings, BirdNoRollState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > 0.2 })
override fun isActive(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity): Boolean {
return true
}
override fun pose(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player
): Float {
return state.rideVelocity.get().length().toFloat()
}
override fun rotation(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
var yVel = 0.0
var leftForce = 0.0
var upForce = 0.0
var forwardForce = 0.0
//Perform ride velocity update
calculateRideSpaceVel(settings, state, vehicle, driver)
//Translate ride space velocity to world space velocity.
val controller = (driver as? OrientationControllable)?.orientationController
if (controller != null) {
upForce += -1.0 * sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().z
forwardForce += cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().z
upForce += cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().y
forwardForce += sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().y
}
//Bring the ride out of the sky when stamina is depleted.
if (state.stamina.get() <= 0.0) {
upForce -= 0.3
}
val velocity = Vec3(state.rideVelocity.get().x , upForce, forwardForce)
return velocity
}
/*
* Calculates the change in the ride space vector due to player input and ride state
*/
fun calculateRideSpaceVel(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity, driver: Player) {
//retrieve stats
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val glideTopSpeed = vehicle.runtime.resolveDouble(settings.glidespeedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val staminaStat = vehicle.runtime.resolveDouble(settings.staminaExpr)
var glideSpeedChange = 0.0
val currSpeed = state.rideVelocity.get().length()
//Flag for determining if player is actively inputting
var activeInput = false
var newVelocity = Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, state.rideVelocity.get().z)
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0) {
//make sure it can't exceed top speed
val forwardInput = when {
driver.zza > 0 && newVelocity.z > topSpeed -> 0.0
driver.zza < 0 && newVelocity.z < (-topSpeed / 3.0) -> 0.0
else -> driver.zza.sign
}
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble())))
activeInput = true
}
val controller = (driver as? OrientationControllable)?.orientationController
if (controller != null) {
//Base glide speed change on current pitch of the ride.
glideSpeedChange = sin(Math.toRadians(controller.pitch.toDouble()))
glideSpeedChange = glideSpeedChange * 0.25
//TODO: Possibly create a deadzone around parallel where glide doesn't affect speed?
if (glideSpeedChange <= 0.0) {
//Ensures that a propelling force is still able to be applied when
//climbing in height
if (driver.zza <= 0) {
//speed decrease should be 2x speed increase?
//state.currSpeed = max(state.currSpeed + (0.0166) * glideSpeedChange, 0.0 )
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
lerp( newVelocity.z, 0.0,glideSpeedChange * -0.0166 * 2 )
)
}
} else {
// only add to the speed if it hasn't exceeded the current
//glide angles maximum amount of speed that it can give.
//state.currSpeed = min(state.currSpeed + ((0.0166 * 2) * glideSpeedChange), maxGlideSpeed)
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
min(newVelocity.z + ((0.0166 * 2) * glideSpeedChange), glideTopSpeed)
)
}
}
//Vertical movement based on driver input.
val vertTopSpeed = topSpeed / 2.0
val vertInput = when {
driver.jumping && state.rideVelocity.get().length() < 0.3 -> 1.0
driver.isShiftKeyDown && state.rideVelocity.get().length() < 0.3 -> -1.0
else -> 0.0
}
if (vertInput != 0.0 && state.stamina.get() > 0.0) {
newVelocity = Vec3(
newVelocity.x,
(newVelocity.y + (accel * vertInput)).coerceIn(-vertTopSpeed, vertTopSpeed),
newVelocity.z)
activeInput = true
}
else {
newVelocity = Vec3(
newVelocity.x,
lerp(newVelocity.y, 0.0, vertTopSpeed / 20.0),
newVelocity.z)
}
//Check if the ride should be gliding
if (driver.isLocalPlayer) {
if (activeInput && state.stamina.get() > 0.0) {
state.gliding.set(false)
} else {
state.gliding.set(true)
}
}
//air resistance
if( abs(newVelocity.z) > 0) {
newVelocity = newVelocity.subtract(0.0, 0.0, min(0.0015 * newVelocity.z.sign, newVelocity.z))
}
state.rideVelocity.set(newVelocity)
}
override fun angRollVel(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
//limit rolling based on handling and current speed.
//modulated by speed so that when flapping idle in air you are ont wobbling around to look around
//TODO: Tie handling into yaw rate
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//This rotation limit will be used to know if the ride has exceeded it and its needs correcting.
val rotMin = 15.0
val rotLimit = max(handling * sqrt(normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed)), rotMin)
var yawDeltaDeg = deltaTime * handling * sin(Math.toRadians(controller.roll.toDouble())) //sin(Math.toRadians(controller.roll.toDouble())) * abs(cos(Math.toRadians(controller.pitch.toDouble())))
val trueYawDelt = yawDeltaDeg * abs(cos(Math.toRadians(controller.pitch.toDouble())))
//val pitchedYawDelt = yawDeltaDeg - trueYawDelt
controller.applyGlobalYaw(trueYawDelt.toFloat())
//Calculate correcting roll force.
val correctionRate = 5.0
if(abs(controller.roll) > rotLimit && state.rideVelocity.get().length() < 0.2) {
state.currRollCorrectionForce.set(
state.currRollCorrectionForce.get() + ((abs(controller.roll) - rotLimit) / 180 - rotLimit) * controller.roll.sign * correctionRate * deltaTime * 0.01
)
} else {
state.currRollCorrectionForce.set(
//reduce rollCorrection gradually
lerp(state.currRollCorrectionForce.get(), 0.0, deltaTime * 0.98)
)
}
//yaw, pitch, roll
return Vec3(0.0, 0.0, state.currRollCorrectionForce.get())
}
override fun rotationOnMouseXY(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
//TODO: hook up handling in parity with normal bird
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//Smooth out mouse input.
val smoothingSpeed = 4.0
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
val mouseXc = (mouseX).coerceIn(-60.0, 60.0)
val mouseYc = (mouseY).coerceIn(-60.0, 60.0)
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1 * invertRoll, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1 * invertPitch, deltaTime * smoothingSpeed);
//Give the ability to yaw with x mouse input when at low speeds.
val yawForce = xInput * ( 1.0 - min(sqrt(normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed)), 0.5))
//Apply yaw globally as we don't want roll or pitch changes due to local yaw when looking up or down.
controller.applyGlobalYaw(yawForce.toFloat())
var pitchRot = yInput
// Pitch up globally
if (abs(controller.pitch + pitchRot) >= 89.5 ) {
pitchRot = 0.0
mouseYSmoother.reset()
} else {
controller.applyGlobalPitch(pitchRot.toFloat() * -1.0f)
}
//yaw, pitch, roll
return Vec3.ZERO
}
/*
* Normalizes the given value between a min and a max.
* The result is clamped between 0.0 and 1.0, where 0.0 represents x is at or below min
* and 1.0 represents x is at or above it.
*/
private fun normalizeVal(x: Double, min: Double, max: Double): Double {
require(max > min) { "max must be greater than min" }
return ((x - min) / (max - min)).coerceIn(0.0, 1.0)
}
override fun canJump(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player
): Float {
return (state.stamina.get() / 1.0f)
}
override fun jumpForce(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val glideTopSpeed = vehicle.runtime.resolveDouble(settings.glidespeedExpr)
//Must I ensure that topspeed is greater than minimum?
val normalizedGlideSpeed = normalizeVal(state.rideVelocity.get().length(), topSpeed, glideTopSpeed)
//Only ever want the fov change to be a max of 0.2 and for it to have non linear scaling.
return 1.0f + normalizedGlideSpeed.pow(2).toFloat() * 0.2f
}
override fun useAngVelSmoothing(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun useRidingAltPose(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
if (state.gliding.get()) {
return cobblemonResource("gliding")
}
return cobblemonResource("no_pose")
}
override fun inertia(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity): Double {
return 0.5
}
override fun shouldRoll(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity): Boolean {
return true
}
override fun turnOffOnGround(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotateRiderHead(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun getRideSounds(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: BirdNoRollSettings) = BirdNoRollState()
}
class BirdNoRollSettings : RidingBehaviourSettings {
override val key = BirdNoRollBehaviour.KEY
var infiniteAltitude: Expression = "false".asExpression()
private set
var infiniteStamina: Expression = "false".asExpression()
private set
//max y level for the ride
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 200.0, 128.0)".asExpression()
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 135.0, 45.0)".asExpression()
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 1.0, 0.35)".asExpression()
var accelerationExpr: Expression = "q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 3.0)), (1.0 / (20.0 * 8.0)))".asExpression()
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 120.0, 20.0)".asExpression()
var glidespeedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 2.0, 1.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(infiniteAltitude)
buffer.writeExpression(infiniteStamina)
buffer.writeExpression(glidespeedExpr)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteAltitude = buffer.readExpression()
infiniteStamina = buffer.readExpression()
glidespeedExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
}
}
class BirdNoRollState : RidingBehaviourState() {
var gliding = ridingState(false, Side.CLIENT)
var lastGlide = ridingState(-100L, Side.CLIENT)
var currRollCorrectionForce = ridingState(0.0, Side.CLIENT)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeFloat(stamina.get())
buffer.writeBoolean(gliding.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
gliding.set(buffer.readBoolean(), forced = true)
}
override fun reset() {
super.reset()
gliding.set(false, forced = true)
lastGlide.set(-100L, forced = true)
currRollCorrectionForce.set(0.0, forced = true)
}
override fun toString(): String {
return "BirdNoRollState(rideVelocity=${rideVelocity.get()}, stamina=${stamina.get()}, gliding=${gliding.get()})"
}
override fun copy() = BirdNoRollState().also {
it.rideVelocity.set(this.rideVelocity.get(), forced = true)
it.stamina.set(this.stamina.get(), forced = true)
it.gliding.set(this.gliding.get(), forced = true)
it.lastGlide.set(this.lastGlide.get(), forced = true)
it.currRollCorrectionForce.set(this.currRollCorrectionForce.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is BirdNoRollState) return false
if (previous.gliding.get() != gliding.get()) return true
return super.shouldSync(previous)
}
}
@@ -184,7 +184,7 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
return false
}
override fun shouldRotatePlayerHead(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun shouldRotateRiderHead(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
@@ -12,9 +12,9 @@ import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
@@ -269,7 +269,11 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
return false
}
override fun shouldRotatePlayerHead(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun shouldRotateRiderHead(
settings: HelicopterSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity
): Boolean {
return false
}
@@ -13,7 +13,6 @@ import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideLoopSound
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -452,7 +451,7 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
return false
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity
@@ -21,7 +21,6 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
@@ -138,14 +137,6 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
//If stamina has run out then initiate forced glide down.
upForce = if (state.stamina.get() > 0.0) upForce else -0.7
val altitudeLimit = vehicle.runtime.resolveDouble(settings.jumpExpr)
//Only limit altitude if altitude is not infinite
if (!vehicle.runtime.resolveBoolean(settings.infiniteAltitude)) {
//Provide a hard limit on altitude
upForce = if (vehicle.y >= altitudeLimit && upForce > 0) 0.0 else upForce
}
val velocity = Vec3(0.0, upForce, forwardForce)
return velocity
@@ -162,17 +153,11 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
) {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val altitudeLimit = vehicle.runtime.resolveDouble(settings.jumpExpr)
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
val speed = state.rideVelocity.get().length()
//Give no altitude limit if at max jump stat.
val pushingHeightLimit = if (vehicle.runtime.resolveBoolean(settings.infiniteStamina)) false
else (vehicle.y >= altitudeLimit && vehicle.xRot <= 0)
//speed up and slow down based on input
if (driver.zza > 0.0 && speed < topSpeed && state.stamina.get() > 0.0f && !pushingHeightLimit) {
if (driver.zza > 0.0 && speed < topSpeed && state.stamina.get() > 0.0f) {
//modify acceleration to be slower when at closer speeds to top speed
val accelMod = max(-(normalizeSpeed(speed, minSpeed, topSpeed)) + 1, 0.0)
state.rideVelocity.set(
@@ -182,7 +167,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
min(state.rideVelocity.get().z + (accel * accelMod), topSpeed)
)
)
} else if (driver.zza >= 0.0 && (state.stamina.get() == 0.0f || pushingHeightLimit)) {
} else if (driver.zza >= 0.0 && (state.stamina.get() == 0.0f)) {
state.rideVelocity.set(
Vec3(
state.rideVelocity.get().x,
@@ -382,7 +367,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
return false
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: JetSettings,
state: JetState,
vehicle: PokemonEntity
@@ -427,9 +412,6 @@ class JetSettings : RidingBehaviourSettings {
var infiniteStamina: Expression = "false".asExpression()
private set
var infiniteAltitude: Expression = "false".asExpression()
private set
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 300.0, 128.0)".asExpression()
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 140.0, 20.0)".asExpression()
@@ -451,7 +433,6 @@ class JetSettings : RidingBehaviourSettings {
buffer.writeExpression(minSpeed)
buffer.writeExpression(handlingYawExpr)
buffer.writeExpression(infiniteStamina)
buffer.writeExpression(infiniteAltitude)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(speedExpr)
@@ -465,7 +446,6 @@ class JetSettings : RidingBehaviourSettings {
minSpeed = buffer.readExpression()
handlingYawExpr = buffer.readExpression()
infiniteStamina = buffer.readExpression()
infiniteAltitude = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
speedExpr = buffer.readExpression()
@@ -501,8 +481,6 @@ class JetState : RidingBehaviourState() {
it.currSpeed.set(currSpeed.get(), forced = true)
it.stamina.set(stamina.get(), forced = true)
it.rideVelocity.set(rideVelocity.get(), forced = true)
// it.currMouseXForce.set(currMouseXForce.get(), forced = true)
// it.currMouseYForce.set(currMouseYForce.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
@@ -0,0 +1,477 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.*
class JetNoRollBehaviour : RidingBehaviour<JetNoRollSettings, JetNoRollState> {
companion object {
val KEY = cobblemonResource("air/jetnoroll")
}
override val key = KEY
override fun getRidingStyle(settings: JetNoRollSettings, state: JetNoRollState): RidingStyle {
return RidingStyle.AIR
}
val poseProvider = PoseProvider<JetNoRollSettings, JetNoRollState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > 0.1 })
override fun isActive(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
//Need to check other fluids
if (vehicle.isInWater || vehicle.isUnderWater) {
return@any false
}
//This might not actually work, depending on what the yPos actually is. yPos of the middle of the entity? the feet?
if (it.y.toDouble() == (vehicle.position().y)) {
val blockState = vehicle.level().getBlockState(it.below())
return@any !(!blockState.isAir && blockState.fluidState.isEmpty)
}
true
}
}
override fun pose(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity, driver: Player): Float {
//retrieve stats
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val staminaStat = vehicle.runtime.resolveDouble(settings.staminaExpr)
//retrieve minSpeed
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
//TODO: Reintroduce stamina drain once stats start to be polished and tweaked
state.stamina.set(1.0f)
return state.rideVelocity.get().length().toFloat()
}
//TODO: Move these functions to a riding util class.
/*
* Normalizes the current speed between minSpeed and maxSpeed.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minSpeed and 1.0 represents maxSpeed.
*/
private fun normalizeSpeed(currSpeed: Double, minSpeed: Double, maxSpeed: Double): Double {
require(maxSpeed > minSpeed) { "maxSpeed must be greater than minSpeed" }
return ((currSpeed - minSpeed) / (maxSpeed - minSpeed)).coerceIn(0.0, 1.0)
}
override fun rotation(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
var upForce = 0.0
var forwardForce = 0.0
val controller = (driver as? OrientationControllable)?.orientationController
//Calculate ride space velocity
calculateRideSpaceVel(settings, state, vehicle, driver)
//Translate ride space velocity to world space velocity.
if (controller != null) {
upForce = -1.0 * sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().z
forwardForce = cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().z
}
//If stamina has run out then initiate forced glide down.
upForce = if (state.stamina.get() > 0.0) upForce else -0.7
val velocity = Vec3(0.0, upForce, forwardForce)
return velocity
}
/*
* Calculates the change in the ride space vector due to player input and ride state
*/
private fun calculateRideSpaceVel(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player
) {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
val speed = state.rideVelocity.get().length()
//speed up and slow down based on input
if (driver.zza > 0.0 && speed < topSpeed && state.stamina.get() > 0.0f) {
//modify acceleration to be slower when at closer speeds to top speed
val accelMod = max(-(normalizeSpeed(speed, minSpeed, topSpeed)) + 1, 0.0)
state.rideVelocity.set(
Vec3(
state.rideVelocity.get().x,
state.rideVelocity.get().y,
min(state.rideVelocity.get().z + (accel * accelMod), topSpeed)
)
)
} else if (driver.zza >= 0.0 && (state.stamina.get() == 0.0f)) {
state.rideVelocity.set(
Vec3(
state.rideVelocity.get().x,
state.rideVelocity.get().y,
max(state.rideVelocity.get().z - ((accel) / 4), minSpeed)
)
)
} else if (driver.zza < 0.0 && speed > minSpeed) {
//modify deccel to be slower when at closer speeds to minimum speed
val deccelMod = max((normalizeSpeed(speed, minSpeed, topSpeed) - 1).pow(2) * 4, 0.1)
//Decelerate currently always a constant half of max acceleration.
state.rideVelocity.set(
Vec3(
state.rideVelocity.get().x,
state.rideVelocity.get().y,
max(state.rideVelocity.get().z - ((accel * deccelMod) / 2), minSpeed)
)
)
}
}
override fun angRollVel(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
// //Cap at a rate of 5fps so frame skips dont lead to huge jumps
// val cappedDeltaTime = min(deltaTime, 0.2)
//
// //Get handling in degrees per second
// val yawRotRate = vehicle.runtime.resolveDouble(settings.handlingYawExpr)
//
// //Base the change off of deltatime.
// var handlingYaw = yawRotRate * (cappedDeltaTime)
//
// //apply stamina debuff if applicable
// handlingYaw *= if (state.stamina.get() > 0.0) 1.0 else 0.5
//
// //A+D to yaw
// val yawForce = driver.xxa * handlingYaw * -1
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//TODO: figure out a cleaner solution to this issue of large jumps when skipping frames or lagging
//Cap at a rate of 5fps so frame skips dont lead to huge jumps
val cappedDeltaTime = min(deltaTime, 0.2)
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
// Accumulate the mouse input
state.currMouseXForce.set((state.currMouseXForce.get() + (0.0015 * mouseX * invertRoll)).coerceIn(-1.0, 1.0))
state.currMouseYForce.set((state.currMouseYForce.get() + (0.0015 * mouseY * invertPitch)).coerceIn(-1.0, 1.0))
//Get handling in degrees per second
var handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
//convert it to delta time
handling *= (cappedDeltaTime)
//apply stamina debuff if applicable
handling *= if (state.stamina.get() > 0.0) 1.0 else 0.5
var pitchRot = handling * state.currMouseYForce.get()
// Yaw
val yawRot = handling * 0.5 * state.currMouseXForce.get()
val controller = (driver as OrientationControllable).orientationController
controller.applyGlobalYaw(yawRot.toFloat())
if (abs(controller.pitch + pitchRot) >= 89.5 ) {
pitchRot = 0.0
state.currMouseYForce.set(0.0)
mouseYSmoother.reset()
} else {
controller.applyGlobalPitch(pitchRot.toFloat() * -1.0f)
}
val mouseRotation = Vec3(0.0, pitchRot, 0.0)
// Have accumulated input begin decay when no input detected
if (abs(mouseX) == 0.0) {
// Have decay on roll be much stronger.
state.currMouseXForce.set(lerp(state.currMouseXForce.get(), 0.0, 0.02))
}
if (mouseY == 0.0) {
state.currMouseYForce.set(lerp(state.currMouseYForce.get(), 0.0, 0.005))
}
//yaw, pitch, roll
return Vec3.ZERO
}
override fun canJump(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player
): Float {
return (state.stamina.get() / 1.0f)
}
override fun jumpForce(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
//Must I ensure that topspeed is greater than minimum?
val normalizedSpeed = normalizeSpeed(state.rideVelocity.get().length(), minSpeed, topSpeed)
//TODO: Determine if this should be based on max possible speed instead of top speed.
//Only ever want the fov change to be a max of 0.2 and for it to have non linear scaling.
return 1.0f + normalizedSpeed.pow(2).toFloat() * 0.2f
}
override fun useAngVelSmoothing(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): Boolean {
return true
}
override fun useRidingAltPose(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
return cobblemonResource("no_pose")
}
override fun inertia(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): Double {
return 1.0
}
override fun shouldRoll(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): Boolean {
return true
}
override fun turnOffOnGround(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotateRiderHead(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun getRideSounds(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: JetNoRollSettings) = JetNoRollState()
override fun damageOnCollision(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
impactVec: Vec3
): Boolean {
val impactSpeed = impactVec.horizontalDistance().toFloat() * 10f
return vehicle.causeFallDamage(impactSpeed, 1f, vehicle.damageSources().flyIntoWall())
}
}
class JetNoRollSettings : RidingBehaviourSettings {
override val key = JetNoRollBehaviour.KEY
var gravity: Expression = "0".asExpression()
private set
var minSpeed: Expression = "1.2".asExpression()
private set
var handlingYawExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 50.0, 25.0)".asExpression()
private set
// Make configurable by json
var infiniteStamina: Expression = "false".asExpression()
private set
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 300.0, 128.0)".asExpression()
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 140.0, 20.0)".asExpression()
private set
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 1.8, 1.0)".asExpression()
private set
var accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 1.0)), (1.0 / (20.0 * 5.0)))".asExpression()
private set
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 60.0, 10.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(gravity)
buffer.writeExpression(minSpeed)
buffer.writeExpression(handlingYawExpr)
buffer.writeExpression(infiniteStamina)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
gravity = buffer.readExpression()
minSpeed = buffer.readExpression()
handlingYawExpr = buffer.readExpression()
infiniteStamina = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
}
}
class JetNoRollState : RidingBehaviourState() {
var currSpeed = ridingState(0.0, Side.CLIENT)
var currMouseXForce = ridingState(0.0, Side.CLIENT)
var currMouseYForce = ridingState(0.0, Side.CLIENT)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeDouble(currSpeed.get())
buffer.writeFloat(stamina.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
currSpeed.set(buffer.readDouble(), forced = true)
}
override fun reset() {
super.reset()
currSpeed.set(0.0, forced = true)
currMouseXForce.set(0.0, forced = true)
currMouseYForce.set(0.0, forced = true)
}
override fun copy() = JetNoRollState().also {
it.currSpeed.set(currSpeed.get(), forced = true)
it.stamina.set(stamina.get(), forced = true)
it.rideVelocity.set(rideVelocity.get(), forced = true)
// it.currMouseXForce.set(currMouseXForce.get(), forced = true)
// it.currMouseYForce.set(currMouseYForce.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is JetNoRollState) return false
if (previous.currSpeed != currSpeed) return true
return super.shouldSync(previous)
}
}
@@ -0,0 +1,566 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.client.Minecraft
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import org.joml.Matrix3f
import kotlin.math.*
class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
companion object {
val KEY = cobblemonResource("air/rocket")
}
override val key = KEY
override fun getRidingStyle(settings: RocketSettings, state: RocketState): RidingStyle {
return RidingStyle.AIR
}
val poseProvider = PoseProvider<RocketSettings, RocketState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ ->
return@PoseOption state.boosting.get()
})
override fun isActive(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
//Need to check other fluids
if (vehicle.isInWater || vehicle.isUnderWater) {
return@any false
}
//This might not actually work, depending on what the yPos actually is. yPos of the middle of the entity? the feet?
if (it.y.toDouble() == (vehicle.position().y)) {
val blockState = vehicle.level().getBlockState(it.below())
return@any !blockState.isAir && blockState.fluidState.isEmpty
}
true
}
}
override fun pose(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): PoseType {
return this.poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
): Float {
// Use this as a "tick" function and check to see if the driver is "boosting"
if(vehicle.level().isClientSide) {
//If the forward key is not held then it cannot be boosting
if(Minecraft.getInstance().options.keyUp.isDown()) {
val boostKeyPressed = Minecraft.getInstance().options.keySprint.isDown()
//If on the previous tick the boost key was held then don't change if the ride is boosting
if(state.boostIsToggleable.get() && boostKeyPressed) {
//flip the boosting state if boost key is pressed
state.boosting.set(!state.boosting.get())
}
//If the boost key is not held then next tick boosting is toggleable
state.boostIsToggleable.set(!boostKeyPressed)
} else {
//Turn off boost and reset boost params
state.boostIsToggleable.set(true)
state.boosting.set(false)
state.canSpeedBurst.set(true)
}
}
return state.rideVelocity.get().length().toFloat()
}
override fun updatePassengerRotation(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: LivingEntity
) {
return
}
override fun clampPassengerRotation(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: LivingEntity
) {
val f = Mth.wrapDegrees(driver.yRot - vehicle.yRot)
val lookYawLimit = 90.0f
val g = Mth.clamp(f, -lookYawLimit, lookYawLimit)
val ticksBeforeCorrection = 40
if(state.prevCamYRot.get() == driver.yRot){
state.ticksNoInput.set(state.ticksNoInput.get() + 1)
} else {
state.ticksNoInput.set(0)
}
val correctionForce = if(state.ticksNoInput.get() >= ticksBeforeCorrection && f != 0.0f && state.boosting.get()) 1f * min(sqrt(lookYawLimit / (abs(f))), 10.0f) else 0.0f
driver.yRotO += g - f
driver.yRot = driver.yRot + g - f //- ( clamp(f.sign * correctionForce, -abs(f), abs(f)) )
//driver.yRot = driver.yRot + g - f - ( min )
state.prevCamYRot.set(driver.yRot)
driver.setYHeadRot(driver.yRot)
}
override fun rotation(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
var newMomentum = state.turnMomentum.get().toDouble()
// Grab turningAcceleration and divide 20 twice to get
val turningAcceleration = (vehicle.runtime.resolveDouble(settings.handlingExpr) / 20.0f) / 20.0f * 2
val turnInput = (driver.xxa *-1.0f) * turningAcceleration
val maxTurnMomentum = 80.0f / 20.0f
// Base boost stats off of normal turning stats
val boostMaxTurnMomentum = maxTurnMomentum * 0.1f
val boostTurnInput = turnInput * 0.15f
if(state.boosting.get()) {
if(driver.xxa != 0.0f && abs(newMomentum + turnInput) < (boostMaxTurnMomentum)) { //If max momentum will not be exceeded then modulate
newMomentum += boostTurnInput
} else {
newMomentum = lerp(newMomentum, 0.0, 0.05)
}
} else {
if(driver.xxa == 0.0f) { //If no turning input then lerp to 0
newMomentum = lerp(newMomentum, 0.0, 0.05)
} else if(abs(newMomentum + turnInput) < maxTurnMomentum) { //If max momentum will not be exceeded then modulate
newMomentum += turnInput
}
}
state.turnMomentum.set(newMomentum.toFloat())
driver.yRot += newMomentum.toFloat()
return Vec2(driver.xRot, vehicle.yRot + newMomentum.toFloat())
}
override fun velocity(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val retVel = calculateRideSpaceVel(settings, state, vehicle, driver)
state.rideVelocity.set(retVel)
return retVel
}
private fun calculateRideSpaceVel(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
): Vec3 {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) * 2
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr) * 0.5 * 2
var newVelocity = vehicle.deltaMovement
// Align the direction of movement with the world coordinate space so as not to modify it through turning
val yawAligned = Matrix3f().rotateY(vehicle.yRot.toRadians())
newVelocity = (newVelocity.toVector3f().mul(yawAligned)).toVec3d()
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0 && !state.boosting.get()) {
//make sure it can't exceed top speed
val forwardInput = when {
vehicle.deltaMovement.horizontalDistance() > topSpeed && (driver.zza.sign == newVelocity.z.sign.toFloat()) -> 0.0
else -> driver.zza.sign
}
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble())))
} else if ((state.stamina.get() > 0.0 && state.boosting.get())) {
val boostSpeed = topSpeed * 5
val boostAccel = if(newVelocity.length() < boostSpeed) accel * 3 else 0.0
val forwardInput = 1.0f
var burst = 0.0f
if(state.canSpeedBurst.get()) {
burst = 0.25f
state.canSpeedBurst.set(false)
}
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
newVelocity.z + (boostAccel * forwardInput.toDouble()) + burst)
}
//Vertical movement based on driver input.
val maxVertSpeed = 0.5f
var vertInput = 0.0
//If boosting then don't lose altitude
//otherwise propel up or fall
if (state.boosting.get() && !driver.jumping && vehicle.deltaMovement.y > (-maxVertSpeed * 0.25)) {
vertInput = -0.7
newVelocity = Vec3(
newVelocity.x,
newVelocity.y + accel * vertInput,
newVelocity.z)
} else if(state.boosting.get() && vehicle.deltaMovement.y < maxVertSpeed) {
vertInput = when {
driver.jumping -> 2.0
else -> 0.0
}
newVelocity = Vec3(
newVelocity.x,
(newVelocity.y + (accel * 0.5 * vertInput)),
newVelocity.z)
} else {
if (driver.jumping && !(vehicle.deltaMovement.y > maxVertSpeed && (newVelocity.y.sign.toFloat() > 0.0))) {
vertInput = 1.0
// Reset falldistance if upward motion is detected
vehicle.resetFallDistance()
} else {
vertInput = -2.5
}
if (state.stamina.get() > 0.0) {
newVelocity = Vec3(
newVelocity.x,
(newVelocity.y + (accel * vertInput)),
newVelocity.z)
}
}
return newVelocity
}
/*
* Normalizes the current speed between minSpeed and maxSpeed.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minSpeed and 1.0 represents maxSpeed.
*/
private fun normalizeVal(currSpeed: Double, minSpeed: Double, maxSpeed: Double): Double {
require(maxSpeed > minSpeed) { "maxSpeed must be greater than minSpeed" }
return ((currSpeed - minSpeed) / (maxSpeed - minSpeed)).coerceIn(0.0, 1.0)
}
override fun angRollVel(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
//Smooth out mouse input.
val smoothingSpeed = 4
val xInput = mouseXSmoother.getNewDeltaValue(mouseX * 0.1, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseY * 0.1, deltaTime * smoothingSpeed);
//yaw, pitch, roll
return Vec3(0.0, yInput, xInput)
}
override fun canJump(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
): Float {
//Retrieve stamina from state and use it to set the "stamina bar"
return (state.stamina.get() / 1.0f)
}
override fun jumpForce(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
): Float {
if (state.boosting.get()) {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//val exceededSpeed = max(vehicle.deltaMovement.length() - topSpeed, 0.0)
//TODO: Remove this magic number and get a better comparison for boost top speed
val normalizedBoostSpeed = normalizeVal(state.rideVelocity.get().length(), topSpeed, topSpeed * 3)
return 1.0f + normalizedBoostSpeed.pow(2).toFloat() * 0.2f
} else {
return 1.0f
}
}
override fun useAngVelSmoothing(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun useRidingAltPose(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
return cobblemonResource("no_pose")
}
override fun inertia(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Double {
return 1.0
}
override fun shouldRoll(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun turnOffOnGround(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun dismountOnShift(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotateRiderHead(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun getRideSounds(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: RocketSettings) = RocketState()
}
class RocketSettings : RidingBehaviourSettings {
override val key = RocketBehaviour.KEY
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 0.65, 0.3) * 0.25".asExpression()
private set
// Max accel is a whole 1.0 in 1 second. The conversion in the function below is to convert seconds to ticks
var accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 1.5)), (1.0 / (20.0 * 3.0))) * 0.5".asExpression()
private set
// Between 30 seconds and 10 seconds at the lowest when at full speed.
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 30.0, 10.0)".asExpression()
private set
//Between a one block jump and a ten block jump
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 10.0, 1.0)".asExpression()
private set
// Controls the acceleration of the turning speed since the max turning speed is locked at 60 degrees/second
// So this is a range of 60 degrees/second per second to 20. Meaning at max stats when holding a direction it
// takes a full second to be turning at max speed.
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 60.0, 20.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
}
}
class RocketState : RidingBehaviourState() {
var boosting = ridingState(false, Side.BOTH)
var boostIsToggleable = ridingState(false, Side.BOTH)
var canSpeedBurst = ridingState(false, Side.BOTH)
var prevCamYRot: SidedRidingState<Float> = ridingState(0.0f, Side.CLIENT)
var ticksNoInput: SidedRidingState<Int> = ridingState(0, Side.CLIENT)
val turnMomentum: SidedRidingState<Float> = ridingState(0.0f, Side.CLIENT)
override fun reset() {
super.reset()
boosting.set(false, forced = true)
boostIsToggleable.set(false, forced = true)
canSpeedBurst.set(true, forced = true)
prevCamYRot.set(0.0f, forced = true)
ticksNoInput.set(0, forced = true)
turnMomentum.set(0.0f, forced = true)
}
override fun copy() = RocketState().also {
it.rideVelocity.set(this.rideVelocity.get(), forced = true)
it.stamina.set(this.stamina.get(), forced = true)
it.boosting.set(this.boosting.get(), forced = true)
it.boostIsToggleable.set(this.boosting.get(), forced = true)
it.canSpeedBurst.set(this.canSpeedBurst.get(), forced = true)
it.prevCamYRot.set(this.prevCamYRot.get(), forced = true)
it.ticksNoInput.set(this.ticksNoInput.get(), forced = true)
it.turnMomentum.set(this.turnMomentum.get(), forced = true)
}
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeBoolean(boosting.get())
buffer.writeBoolean(boostIsToggleable.get())
buffer.writeBoolean(canSpeedBurst.get())
buffer.writeFloat(prevCamYRot.get())
buffer.writeInt(ticksNoInput.get())
buffer.writeFloat(turnMomentum.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
boosting.set(buffer.readBoolean(), forced = true)
boostIsToggleable.set(buffer.readBoolean(), forced = true)
canSpeedBurst.set(buffer.readBoolean(), forced = true)
prevCamYRot.set(buffer.readFloat(), forced = true)
ticksNoInput.set(buffer.readInt(), forced = true)
turnMomentum.set(buffer.readFloat(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is RocketState) return false
if (previous.boosting.get() != boosting.get()) return true
if (previous.boostIsToggleable.get() != boostIsToggleable.get()) return true
if (previous.canSpeedBurst.get() != canSpeedBurst.get()) return true
if (previous.prevCamYRot.get() != prevCamYRot.get()) return true
if (previous.ticksNoInput.get() != ticksNoInput.get()) return true
if (previous.turnMomentum.get() != turnMomentum.get()) return true
return super.shouldSync(previous)
}
}
@@ -107,13 +107,13 @@ class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
}
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.shouldRotatePlayerHead(behaviourSettings, behaviourState, vehicle)
behaviour.shouldRotateRiderHead(behaviourSettings, behaviourState, vehicle)
}
}
@@ -45,7 +45,7 @@ object JumpStrategy : CompositeRidingStrategy<CompositeSettings> {
): Boolean {
if (state.activeBehaviour.get() == defaultSettings.key) return false
if (!vehicle.onGround()) return false
if (state.lastTransition.get() + 20 >= vehicle.level().gameTime) return false
if (state.lastTransition.get() + 5 >= vehicle.level().gameTime) return false
val defaultBehaviour = RidingBehaviours.get(defaultSettings.key)
return defaultBehaviour.isActive(defaultSettings, state.defaultBehaviourState, vehicle)
}
@@ -58,7 +58,7 @@ object JumpStrategy : CompositeRidingStrategy<CompositeSettings> {
): Boolean {
if (state.activeBehaviour.get() == alternativeSettings.key) return false
if (!driver.jumping) return false
if (state.lastTransition.get() + 20 >= vehicle.level().gameTime) return false
if (state.lastTransition.get() + 5 >= vehicle.level().gameTime) return false
val alternativeBehaviour = RidingBehaviours.get(alternativeSettings.key)
return alternativeBehaviour.isActive(alternativeSettings, state.alternateBehaviourState, vehicle)
}
@@ -83,7 +83,15 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
// Use this as a "tick" function and calculate sprinting and inAir state here
state.sprinting.set(driver.isSprinting)
inAirCheck(state, vehicle)
return state.rideVelocity.get().length().toFloat()
}
fun inAirCheck(
state: HorseState,
vehicle: PokemonEntity,
) {
// Check both vertical movement and if there are blocks below.
val posBelow = vehicle.blockPosition().below()
val blockStateBelow = vehicle.level().getBlockState(posBelow)
@@ -92,14 +100,10 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
val canSupportEntity = blockStateBelow.isFaceSturdy(vehicle.level(), posBelow, Direction.UP)
val standingOnSolid = canSupportEntity && !isAirOrLiquid
// val level = vehicle.level()
// val toesBox = vehicle.boundingBox.move(0.0, -0.1, 0.0)
// inAir if not on the ground
val inAir = !(vehicle.deltaMovement.y == 0.0 || standingOnSolid)
state.inAir.set(inAir)
return state.rideVelocity.get().length().toFloat()
}
override fun updatePassengerRotation(
@@ -107,26 +111,7 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
state: HorseState,
vehicle: PokemonEntity,
driver: LivingEntity
) {
//Take the inverse so that it cancels out how
//much the entity rotates.
val turnAmount = calcRotAmount(settings, state, vehicle, driver)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val maxYawDiff = 90.0f
//Normalize the current rotation diff
val rotMod = Mth.wrapDegrees(driver.yRot - vehicle.yRot) / maxYawDiff
val rotAmount = 10.0f
//Take the inverse so that you turn more at higher speeds
val normSpeed = 1.0f - 0.5f*normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed).toFloat()
//driver.yRot += (entity.riding.deltaRotation.y - turnAmount)
//driver.setYHeadRot(driver.yHeadRot + (entity.riding.deltaRotation.y) - turnAmount)
}
) { return }
override fun clampPassengerRotation(
settings: HorseSettings,
@@ -149,9 +134,9 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
val turnAmount = calcRotAmount(settings, state, vehicle, driver)
var turnAmount = calcRotAmount(settings, state, vehicle, driver)
return Vec2(vehicle.xRot, vehicle.yRot + turnAmount )
return Vec2(vehicle.xRot, vehicle.yRot + turnAmount * 0.5f )
}
@@ -169,9 +154,9 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//In degrees per second
val walkHandling = 140.0 * 2
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr) * 2
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr) * 3
val walkHandlingBoost = 5
val walkHandling = handling * walkHandlingBoost
val maxYawDiff = 90.0f
@@ -185,13 +170,14 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
val rotDiffMod = (sqrt(abs(rotDiffNorm)) * rotDiffNorm.sign)
//Take the inverse so that you turn less at higher speeds
val normSpeed = 1.0f // = 1.0f - 0.5f*normalizeVal(state.rideVelocityocity.length(), 0.0, topSpeed).toFloat()
val normSpeed = 1.0f
// TurnRate should always be quick if not sprinting
val turnRate = if(state.sprinting.get()) (handling.toFloat() / 20.0f) else (walkHandling.toFloat() / 20.0f)
val invRelSpeed = (inverseLerp(vehicle.deltaMovement.horizontalDistance(), 0.0, topSpeed ).pow(0.5) - 1.0f) * -1.0f
val turnRate = ((handling.toFloat() / 20.0f) * max(walkHandlingBoost * invRelSpeed, 1.0)).toFloat()
//Ensure you only ever rotate as much difference as there is between the angles.
val turnSpeed = turnRate * rotDiffMod * normSpeed
val turnSpeed = turnRate * rotDiffMod * normSpeed
val rotAmount = turnSpeed.coerceIn(-abs(rotDiff), abs(rotDiff))
return rotAmount
@@ -218,16 +204,16 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
// Check to see if the ride should be walking or sprinting
val walkSpeed = getWalkSpeed(vehicle)
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) * 1.3
val topSpeed = if(state.sprinting.get()) rideTopSpeed else walkSpeed
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr) * 2
//Flag for determining if player is actively inputting
var activeInput = false
var newVelocity = Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, state.rideVelocity.get().z)
var newVelocity = Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, state.rideVelocity.get().z).normalize().scale(vehicle.deltaMovement.length())
//var newVelocity = vehicle.deltaMovement.normalize()
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0) {
@@ -246,11 +232,14 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
activeInput = true
}
//Gravity logic
// Gravity logic
if (vehicle.onGround()) {
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
} else {
val gravity = (9.8 / ( 20.0)) * 0.2
//TODO: Should we just go back to standard minecraft gravity or do the lerp modifications prevent that?
//I think minecrafts gravity logic is also too harsh and isn't gamefied enough for mounts maybe? Need
//to do some testing and get other's opinions
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.8
val terminalVel = 2.0
val fallingForce = gravity - ( newVelocity.z.sign *gravity *(abs(newVelocity.z) / 2.0))
@@ -262,27 +251,34 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
newVelocity = newVelocity.subtract(0.0, 0.0, min(0.03 * newVelocity.z.sign, newVelocity.z))
}
//TODO: Change this so its tied to a jumping stat and representative of the amount of jumps
val canJump = vehicle.runtime.resolveBoolean(settings.canJump)
//Jump the thang!
if (driver.jumping && vehicle.onGround()) {
if (driver.jumping && vehicle.onGround() && canJump) {
val jumpForce = 1.0
val horz = state.rideVelocity.get().horizontalDistance()
val velMag = newVelocity.length()
newVelocity = newVelocity.add(0.0, jumpForce, 0.0)
val test = 0.0
//Ensure this doesn't add unwanted forward velocity
//val mag = if(newVelocity.length() < rideTopSpeed) newVelocity.length() else rideTopSpeed
//newVelocity = newVelocity.normalize().scale(mag)
}
//Zero out lateral velocity possibly picked up from a controller transition
newVelocity = Vec3(0.0, newVelocity.y, newVelocity.z)
return newVelocity
}
/*
* Normalizes the current speed between minSpeed and maxSpeed.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minSpeed and 1.0 represents maxSpeed.
* lerps the current val between minVal and maxVal.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minVal and 1.0 represents maxVal.
*/
private fun normalizeVal(currSpeed: Double, minSpeed: Double, maxSpeed: Double): Double {
require(maxSpeed > minSpeed) { "maxSpeed must be greater than minSpeed" }
return ((currSpeed - minSpeed) / (maxSpeed - minSpeed)).coerceIn(0.0, 1.0)
private fun inverseLerp(currVal: Double, minVal: Double, maxVal: Double): Double {
require(maxVal > minVal) { "minVal must be greater than maxVal" }
return ((currVal - minVal) / (maxVal - minVal)).coerceIn(0.0, 1.0)
}
private fun getWalkSpeed( vehicle: PokemonEntity ): Double {
@@ -432,7 +428,7 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
return false
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: HorseSettings,
state: HorseState,
vehicle: PokemonEntity
@@ -496,7 +492,6 @@ class HorseSettings : RidingBehaviourSettings {
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
}
}
class HorseState : RidingBehaviourState() {
@@ -0,0 +1,563 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.land
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import org.joml.Matrix3f
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sign
class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
companion object {
val KEY = cobblemonResource("land/minekart")
}
override val key = KEY
override fun getRidingStyle(settings: MinekartSettings, state: MinekartState): RidingStyle {
return RidingStyle.LAND
}
val poseProvider = PoseProvider<MinekartSettings, MinekartState>(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { _, state, _ ->
return@PoseOption abs(state.rideVelocity.get().horizontalDistance()) > 0.0
})
// val poseProvider = PoseProvider<MinekartSettings, MinekartState>(PoseType.STAND)
// .with(PoseOption(PoseType.WALK) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: MinekartSettings, state: MinekartState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
//Need to check other fluids
if (vehicle.isInWater || vehicle.isUnderWater) {
return@any false
}
//This might not actually work, depending on what the yPos actually is. yPos of the middle of the entity? the feet?
if (it.y.toDouble() == (vehicle.position().y)) {
val blockState = vehicle.level().getBlockState(it.below())
return@any !(!blockState.isAir && blockState.fluidState.isEmpty)
}
true
}
}
override fun pose(settings: MinekartSettings, state: MinekartState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
): Float {
val boostMax = vehicle.runtime.resolveFloat(settings.boostLimit)
//TODO: should this be by how much turn has accumulated or simply time turning... or both?
val boostPerSecond = 20.0f
val boostLoss = 40.0f
if (driver.jumping && driver.zza > 0.0f) {
if (!state.drifting.get()) {
if (driver.xxa != 0.0f) {
state.clockwiseDrift.set(driver.xxa.sign > 0.0)
state.turnMomentum.set(0.0f)
state.drifting.set(true)
state.boost.set(0.0f)
} else {
//TODO: Add jumping logic
}
} else {
state.boost.set((state.boost.get() + boostPerSecond / 20.0f).coerceIn(0.0f, boostMax))
}
} else {
if(driver.zza <= 0.0 && state.boost.get() != 0.0f){
state.boost.set(0.0f)
} else if( state.boost.get() > 0.0f ) {
state.boost.set(max(state.boost.get() - boostLoss, 0.0f))
}
state.drifting.set(false)
}
inAirCheck(state, vehicle)
return driver.deltaMovement.length().toFloat()
}
fun inAirCheck(
state: MinekartState,
vehicle: PokemonEntity,
) {
// Check both vertical movement and if there are blocks below.
val posBelow = vehicle.blockPosition().below()
val blockStateBelow = vehicle.level().getBlockState(posBelow)
val isAirOrLiquid = blockStateBelow.isAir || !blockStateBelow.fluidState.isEmpty
val canSupportEntity = blockStateBelow.isFaceSturdy(vehicle.level(), posBelow, Direction.UP)
val standingOnSolid = canSupportEntity && !isAirOrLiquid
// inAir if not on the ground
val inAir = !(vehicle.deltaMovement.y == 0.0 || standingOnSolid)
state.inAir.set(inAir)
}
override fun rotation(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
var newMomentum = state.turnMomentum.get().toDouble()
//TODO: maybe tie this to max turnspeeds rather than the turn acceleration
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val turningAcceleration = (vehicle.runtime.resolveDouble(settings.handlingExpr) * 1.5 / 20.0f) / 20.0f * 4.0
val turnInput = (driver.xxa *-1.0f) * turningAcceleration
val driftInput = turnInput * 0.25f
val lowSpeedTurnBoost = (inverseLerp(vehicle.deltaMovement.horizontalDistance(), 0.0, topSpeed ).pow(0.5) - 2.0f) * -1.0f
val forcedDriftTurnMomentum = (80.0f / 20.0f) * lowSpeedTurnBoost
val maxTurnMomentum = (60.0f / 20.0f) * lowSpeedTurnBoost
val maxDriftMomentum = forcedDriftTurnMomentum * 0.6f
if (!state.drifting.get()){
// perform non drift turning
if(abs(newMomentum) <= maxTurnMomentum && turnInput != 0.0 ) {
newMomentum += turnInput
newMomentum = min(max(newMomentum, maxTurnMomentum * -1.0), maxTurnMomentum.toDouble())
} else {
newMomentum = lerp(newMomentum, 0.0, 0.15)
}
state.turnMomentum.set(newMomentum.toFloat())
driver.yRot += newMomentum.toFloat()
return Vec2(driver.xRot, vehicle.yRot + newMomentum.toFloat())
} else {
// perform drift turning
if(abs(newMomentum) <= maxDriftMomentum && driftInput != 0.0 ) {
newMomentum += driftInput
newMomentum = min(max(newMomentum, maxDriftMomentum * -1.0), maxDriftMomentum.toDouble())
} else {
newMomentum = lerp(newMomentum, 0.0, 0.15)
}
val signedDriftTurn = if(state.clockwiseDrift.get()) forcedDriftTurnMomentum * -1.0f else forcedDriftTurnMomentum
state.turnMomentum.set(newMomentum.toFloat())
driver.yRot += newMomentum.toFloat() + signedDriftTurn.toFloat()
return Vec2(driver.xRot, vehicle.yRot + newMomentum.toFloat() + signedDriftTurn.toFloat())
}
}
override fun velocity(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
state.rideVelocity.set(calculateRideSpaceVel(settings, state, vehicle, driver))
return state.rideVelocity.get()
}
private fun calculateRideSpaceVel(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
): Vec3 {
// Check to see if the ride should be walking or sprinting
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) * 0.6 * 2.0
val topSpeed = if (state.drifting.get()) rideTopSpeed * 0.7 else rideTopSpeed
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr) * 0.3 * 3 * 2.0
val driftAngle = 20.0f
//Align the direction of this vector with the vehicles current heading.
var newVelocity = vehicle.deltaMovement
newVelocity = newVelocity.yRot(vehicle.yRot.toRadians())
// rotate the angle of force if drifting
if (state.drifting.get()) {
val signedDriftAngle = if(state.clockwiseDrift.get()) driftAngle else driftAngle * -1.0f
newVelocity = newVelocity.yRot(signedDriftAngle.toRadians())
}
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0 && !state.inAir.get()) {
//make sure it can't exceed top speed
var forwardInput = when {
driver.zza > 0 && newVelocity.z > topSpeed -> 0.0
driver.zza < 0 && newVelocity.z <= 0.0 -> 0.0
else -> driver.zza.sign
}
if (!state.drifting.get() && state.boost.get() != 0.0f) {
forwardInput = 2.0;
}
// If drifting we need more acceleration due to the constant turn
val driftAccelBoost = if (state.drifting.get()) 1.5 else 1.0
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble() * driftAccelBoost)))
}
// Gravity logic
if (vehicle.onGround()) {
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
} else {
//TODO: Should we just go back to standard minecraft gravity or do the lerp modifications prevent that?
//I think minecrafts gravity logic is also too harsh and isn't gamefied enough for mounts maybe? Need
//to do some testing and get other's opinions
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.25 * 3.0
val terminalVel = 2.0
val fallingForce = gravity - ( newVelocity.z.sign *gravity *(abs(newVelocity.z) / 2.0))
newVelocity = Vec3(newVelocity.x, max(newVelocity.y - fallingForce, -terminalVel), newVelocity.z)
}
//ground Friction
var friction = 0.003 * 2.0
if (state.drifting.get()) {friction *= 0.5}
if (!state.inAir.get()) {
newVelocity = newVelocity.subtract(
min(friction , abs(newVelocity.x)) * newVelocity.x.sign,
0.0,
min(friction , abs(newVelocity.z)) * newVelocity.z.sign
)
// TEST FOR LATERAL SLIP OR WHATEVER
//TODO: calc lateral slip and friction better!
if (!state.drifting.get()) {
newVelocity = newVelocity.subtract(
min(friction * 10, abs(newVelocity.x)) * newVelocity.x.sign,
0.0,
0.0,
)
}
}
//TODO: Change this so its tied to a jumping stat and representative of the amount of jumps
// val canJump = vehicle.runtime.resolveBoolean(settings.canJump)
// //Jump the thang!
// if (driver.jumping && vehicle.onGround() && canJump) {
// val jumpForce = 0.5
//
// newVelocity = newVelocity.add(0.0, jumpForce, 0.0)
//
// }
// unrotate the angle of force if drifting
if (state.drifting.get()) {
val inverseSignedDriftAngle = if(state.clockwiseDrift.get()) driftAngle * -1.0f else driftAngle
newVelocity = newVelocity.yRot(inverseSignedDriftAngle.toRadians())
}
return newVelocity
}
/*
* lerps the current val between minVal and maxVal.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minVal and 1.0 represents maxVal.
*/
private fun inverseLerp(currVal: Double, minVal: Double, maxVal: Double): Double {
require(maxVal > minVal) { "minVal must be greater than maxVal" }
return ((currVal - minVal) / (maxVal - minVal)).coerceIn(0.0, 1.0)
}
override fun angRollVel(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//Might need to add the smoothing here for default.
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
return Vec3(0.0, mouseY * invertPitch, mouseX * invertRoll)
}
override fun canJump(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return vehicle.runtime.resolveBoolean(settings.canJump)
}
override fun setRideBar(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 0.0f
}
override fun jumpForce(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
): Float {
var fov = 1.0f
if(!state.drifting.get() && state.boost.get() != 0.0f) {
//TODO: is this too abrupt? Possibly caluclate differently?
fov = 1.2f
}
return fov
}
override fun useAngVelSmoothing(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun useRidingAltPose(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
when {
state.drifting.get() -> return cobblemonResource("drifting")
}
return cobblemonResource("no_pose")
}
override fun inertia(settings: MinekartSettings, state: MinekartState, vehicle: PokemonEntity): Double {
return 1.0
}
override fun shouldRoll(settings: MinekartSettings, state: MinekartState, vehicle: PokemonEntity): Boolean {
return false
}
override fun turnOffOnGround(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun dismountOnShift(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotateRiderHead(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): Boolean {
return true
}
override fun getRideSounds(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: MinekartSettings) = MinekartState()
}
class MinekartSettings : RidingBehaviourSettings {
override val key = MinekartBehaviour.KEY
var canJump = "true".asExpression()
private set
var boostLimit = "100.0f".asExpression()
private set
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'LAND', 1.0, 0.3)".asExpression()
private set
// Max accel is a whole 1.0 in 1 second. The conversion in the function below is to convert seconds to ticks
var accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'LAND', (1.0 / (20.0 * 1.5)), (1.0 / (20.0 * 5.0)))".asExpression()
private set
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'LAND', 30.0, 10.0)".asExpression()
private set
//Between a one block jump and a ten block jump
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'LAND', 10.0, 1.0)".asExpression()
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 140.0, 20.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(canJump)
buffer.writeExpression(boostLimit)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
canJump = buffer.readExpression()
boostLimit = buffer.readExpression()
}
}
class MinekartState : RidingBehaviourState() {
var currSpeed = ridingState(0.0, Side.BOTH)
var deltaRotation = ridingState(Vec2.ZERO, Side.BOTH)
var drifting = ridingState(false, Side.CLIENT)
var clockwiseDrift = ridingState(false, Side.CLIENT)
val boost: SidedRidingState<Float> = ridingState(0.0f, Side.CLIENT)
val turnMomentum: SidedRidingState<Float> = ridingState(0.0f, Side.CLIENT)
var inAir = ridingState(false, Side.CLIENT)
override fun reset() {
super.reset()
currSpeed.set(0.0, forced = true)
deltaRotation.set(Vec2.ZERO, forced = true)
drifting.set(false, forced = true)
clockwiseDrift.set(false, forced = true)
boost.set(0.0f, forced = true)
turnMomentum.set(0.0f, forced = true)
inAir.set(false, forced = true)
}
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeBoolean(drifting.get())
buffer.writeBoolean(clockwiseDrift.get())
buffer.writeFloat(boost.get())
buffer.writeFloat(turnMomentum.get())
buffer.writeBoolean(inAir.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
drifting.set(buffer.readBoolean(), forced = true)
clockwiseDrift.set(buffer.readBoolean(), forced = true)
boost.set(buffer.readFloat(), forced = true)
turnMomentum.set(buffer.readFloat(), forced = true)
inAir.set(buffer.readBoolean(), forced = true)
}
override fun toString(): String {
return "MinekartState(currSpeed=${currSpeed.get()}, deltaRotation=${deltaRotation.get()})"
}
override fun copy() = MinekartState().also {
it.rideVelocity.set(rideVelocity.get(), forced = true)
it.stamina.set(stamina.get(), forced = true)
it.currSpeed.set(currSpeed.get(), forced = true)
it.deltaRotation.set(deltaRotation.get(), forced = true)
it.drifting.set(drifting.get(), forced = true)
it.clockwiseDrift.set(clockwiseDrift.get(), forced = true)
it.boost.set(boost.get(), forced = true)
it.turnMomentum.set(turnMomentum.get(), forced = true)
it.inAir.set(inAir.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is MinekartState) return false
if (previous.drifting.get() != drifting.get()) return true
if (previous.clockwiseDrift.get() != clockwiseDrift.get()) return true
if (previous.boost.get() != boost.get()) return true
if (previous.turnMomentum.get() != turnMomentum.get()) return true
if (previous.inAir.get() != inAir.get()) return true
return super.shouldSync(previous)
}
}
@@ -8,7 +8,8 @@
package com.cobblemon.mod.common.api.riding.behaviour.types.land
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
@@ -16,10 +17,12 @@ import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
@@ -28,29 +31,15 @@ import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.absoluteValue
import org.joml.Matrix3f
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sign
class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
companion object {
val KEY = cobblemonResource("land/vehicle")
val MAX_TOP_SPEED = 1.0 // 20 bl/s
val MIN_TOP_SPEED = 0.35 // 7 bl/s
//val MIN_SPEED = 0.25 // 5 bl/s
//Accel will lie between 1.0 second and 5.0 seconds
val MAX_ACCEL = (MAX_TOP_SPEED) / (20*3) //3.0 second to max speed
val MIN_ACCEL = (MAX_TOP_SPEED) / (20*8) // 8 seconds to max speed
//Can rotate 90 degrees
val MAX_HANDLING = 90.0
val MIN_HANDLING = 30.0
val MAX_YAW_HANDLING = 16.0
val MIN_YAW_HANDLING = 8.0
}
override val key = KEY
@@ -60,7 +49,12 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
}
val poseProvider = PoseProvider<VehicleSettings, VehicleState>(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
.with(PoseOption(PoseType.WALK) { _, state, _ ->
return@PoseOption abs(state.rideVelocity.get().horizontalDistance()) > 0.0
})
// val poseProvider = PoseProvider<VehicleSettings, VehicleState>(PoseType.STAND)
// .with(PoseOption(PoseType.WALK) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: VehicleSettings, state: VehicleState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
@@ -87,18 +81,38 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
vehicle: PokemonEntity,
driver: Player
): Float {
val topSpeed = vehicle.getRideStat(RidingStat.SPEED, RidingStyle.AIR, MIN_TOP_SPEED, MAX_TOP_SPEED)
val accel = vehicle.getRideStat(RidingStat.ACCELERATION, RidingStyle.AIR, MIN_ACCEL, MAX_ACCEL)
//speed up and slow down based on input
if (driver.zza > 0.0 && state.currSpeed.get() < topSpeed) {
state.currSpeed.set(min(state.currSpeed.get() + accel , topSpeed))
} else if (driver.zza < 0.0 && state.currSpeed.get() > 0.0) {
//Decelerate is now always a constant half of max acceleration.
state.currSpeed.set(max(state.currSpeed.get() - (MAX_ACCEL / 2), 0.0))
if (driver.isShiftKeyDown) {
state.drifting.set(true)
if(driver.zza != 0.0f) {
state.poweredDrifting.set(true)
} else {
state.poweredDrifting.set(false)
}
} else {
state.drifting.set(false)
state.poweredDrifting.set(false)
}
return state.currSpeed.get().toFloat()
inAirCheck(state, vehicle)
return driver.deltaMovement.length().toFloat()
}
fun inAirCheck(
state: VehicleState,
vehicle: PokemonEntity,
) {
// Check both vertical movement and if there are blocks below.
val posBelow = vehicle.blockPosition().below()
val blockStateBelow = vehicle.level().getBlockState(posBelow)
val isAirOrLiquid = blockStateBelow.isAir || !blockStateBelow.fluidState.isEmpty
val canSupportEntity = blockStateBelow.isFaceSturdy(vehicle.level(), posBelow, Direction.UP)
val standingOnSolid = canSupportEntity && !isAirOrLiquid
// inAir if not on the ground
val inAir = !(vehicle.deltaMovement.y == 0.0 || standingOnSolid)
state.inAir.set(inAir)
}
override fun rotation(
@@ -107,10 +121,38 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
val rotationDegrees = driver.xxa * vehicle.runtime.resolveFloat(settings.rotationSpeed)
val rotation = Vec2(driver.xRot, vehicle.yRot - rotationDegrees)
state.deltaRotation.set(Vec2(rotation.x - driver.rotationVector.x, rotation.y - vehicle.rotationVector.y))
return rotation
var newMomentum = state.turnMomentum.get().toDouble()
// Grab turningAcceleration and divide 20 twice to get
val turningAcceleration = (vehicle.runtime.resolveDouble(settings.handlingExpr) * 1.5 / 20.0f) / 20.0f * 4.0
val turnInput = (driver.xxa *-1.0f) * turningAcceleration
val maxTurnMomentum = 80.0f / 20.0f
// Base boost stats off of normal turning stats
val driftMaxTurnMomentum = maxTurnMomentum * 3.0f
var driftTurnInput = turnInput * 0.4f
if(state.drifting.get() || state.inAir.get()) {
if( newMomentum < driftMaxTurnMomentum) {
driftTurnInput = driftTurnInput * (vehicle.deltaMovement.horizontalDistance() / vehicle.runtime.resolveDouble(settings.speedExpr))
newMomentum += driftTurnInput
}
newMomentum = lerp(newMomentum, 0.0, 0.03)
if( abs(newMomentum) < 0.05 ) {newMomentum = 0.0}
} else {
if(abs(newMomentum) <= maxTurnMomentum && turnInput != 0.0 ) {
newMomentum += turnInput
newMomentum.coerceIn(-maxTurnMomentum.toDouble(), maxTurnMomentum.toDouble())
} else {
newMomentum = lerp(newMomentum, 0.0, 0.15)
}
}
state.turnMomentum.set(newMomentum.toFloat())
driver.yRot += newMomentum.toFloat()
return Vec2(driver.xRot, vehicle.yRot + newMomentum.toFloat())
}
override fun velocity(
@@ -120,18 +162,106 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
driver: Player,
input: Vec3
): Vec3 {
val runtime = vehicle.runtime
val driveFactor = runtime.resolveFloat(settings.driveFactor)
var g = driver.zza * driveFactor
if (g <= 0.0f) {
g *= runtime.resolveFloat(settings.reverseDriveFactor)
}
if (driver.xxa != 0F && g.absoluteValue < runtime.resolveFloat(settings.minimumSpeedToTurn)) {
driver.xxa = 0F
}
val velocity = Vec3(0.0 /* Maybe do drifting here later */, 0.0, g.toDouble() * state.currSpeed.get())
state.rideVelocity.set(calculateRideSpaceVel(settings, state, vehicle, driver))
return state.rideVelocity.get()
}
return velocity
private fun calculateRideSpaceVel(
settings: VehicleSettings,
state: VehicleState,
vehicle: PokemonEntity,
driver: Player
): Vec3 {
// Check to see if the ride should be walking or sprinting
//val walkSpeed = getWalkSpeed(vehicle)
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) * 0.6 * 2.0
val topSpeed = if (state.drifting.get()) rideTopSpeed * 0.5 else rideTopSpeed
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr) * 0.3 * 3 * 2.0
//Flag for determining if player is actively inputting
var activeInput = false
var newVelocity = Vec3.ZERO
if (!state.drifting.get()) {
newVelocity = vehicle.deltaMovement
newVelocity = newVelocity.yRot(vehicle.yRot.toRadians())
} else {
state
newVelocity = vehicle.deltaMovement
// Align the direction of movement with the world coordinate space so as not to modify it through turning
val yawAligned = Matrix3f().rotateY(vehicle.yRot.toRadians())
newVelocity = (newVelocity.toVector3f().mul(yawAligned)).toVec3d()
}
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0 && !state.inAir.get()) {
//make sure it can't exceed top speed
val forwardInput = when {
driver.zza > 0 && newVelocity.z > topSpeed -> 0.0
driver.zza < 0 && newVelocity.z <= 0.0 -> 0.0
else -> driver.zza.sign
}
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble())))
activeInput = true
}
// Gravity logic
if (vehicle.onGround()) {
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
} else {
//TODO: Should we just go back to standard minecraft gravity or do the lerp modifications prevent that?
//I think minecrafts gravity logic is also too harsh and isn't gamefied enough for mounts maybe? Need
//to do some testing and get other's opinions
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.25 * 3.0
val terminalVel = 2.0
val fallingForce = gravity - ( newVelocity.z.sign *gravity *(abs(newVelocity.z) / 2.0))
newVelocity = Vec3(newVelocity.x, max(newVelocity.y - fallingForce, -terminalVel), newVelocity.z)
}
//ground Friction
var friction = 0.003 * 2.0
if (state.drifting.get()) {friction *= 0.1}
if (!state.inAir.get()) {
newVelocity = newVelocity.subtract(
min(friction , abs(newVelocity.x)) * newVelocity.x.sign,
0.0,
min(friction , abs(newVelocity.z)) * newVelocity.z.sign
)
// TEST FOR LATERAL SLIP OR WHATEVER
//TODO: calc lateral slip and friction better!
if (!state.drifting.get()) {
newVelocity = newVelocity.subtract(
min(friction * 10, abs(newVelocity.x)) * newVelocity.x.sign,
0.0,
0.0,
)
}
}
//TODO: Change this so its tied to a jumping stat and representative of the amount of jumps
val canJump = vehicle.runtime.resolveBoolean(settings.canJump)
//Jump the thang!
if (driver.jumping && vehicle.onGround() && canJump) {
val jumpForce = 0.5
newVelocity = newVelocity.add(0.0, jumpForce, 0.0)
}
return newVelocity
}
override fun angRollVel(
@@ -189,10 +319,7 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
driver: Player,
jumpStrength: Int
): Vec3 {
val runtime = vehicle.runtime
runtime.environment.query.addFunction("jump_strength") { DoubleValue(jumpStrength.toDouble()) }
val jumpVector = settings.jumpVector.map { runtime.resolveFloat(it) }
return Vec3(jumpVector[0].toDouble(), jumpVector[1].toDouble(), jumpVector[2].toDouble())
return Vec3.ZERO
}
override fun gravity(
@@ -201,7 +328,7 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return regularGravity
return 0.0
}
override fun rideFovMultiplier(
@@ -227,11 +354,15 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
when {
state.poweredDrifting.get() -> return cobblemonResource("powered_drifting")
state.drifting.get() -> return cobblemonResource("drifting")
}
return cobblemonResource("no_pose")
}
override fun inertia(settings: VehicleSettings, state: VehicleState, vehicle: PokemonEntity): Double {
return 0.5
return 1.0
}
override fun shouldRoll(settings: VehicleSettings, state: VehicleState, vehicle: PokemonEntity): Boolean {
@@ -262,12 +393,12 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
return false
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: VehicleSettings,
state: VehicleState,
vehicle: PokemonEntity
): Boolean {
return false
return true
}
override fun getRideSounds(
@@ -287,9 +418,6 @@ class VehicleSettings : RidingBehaviourSettings {
var canJump = "true".asExpression()
private set
var jumpVector = listOf("0".asExpression(), "0.3".asExpression(), "0".asExpression())
private set
var speed = "0.3".asExpression()
private set
@@ -299,28 +427,40 @@ class VehicleSettings : RidingBehaviourSettings {
var reverseDriveFactor = "0.25".asExpression()
private set
var minimumSpeedToTurn = "0.1".asExpression()
private set
var rotationSpeed = "45/20".asExpression()
private set
var lookYawLimit = "101".asExpression()
private set
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'LAND', 1.0, 0.3)".asExpression()
private set
// Max accel is a whole 1.0 in 1 second. The conversion in the function below is to convert seconds to ticks
var accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'LAND', (1.0 / (20.0 * 1.5)), (1.0 / (20.0 * 5.0)))".asExpression()
private set
// Between 30 seconds and 10 seconds at the lowest when at full speed.
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'LAND', 30.0, 10.0)".asExpression()
private set
//Between a one block jump and a ten block jump
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'LAND', 10.0, 1.0)".asExpression()
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 140.0, 20.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(canJump)
buffer.writeExpression(jumpVector[0])
buffer.writeExpression(jumpVector[1])
buffer.writeExpression(jumpVector[2])
buffer.writeExpression(speed)
buffer.writeExpression(driveFactor)
buffer.writeExpression(reverseDriveFactor)
buffer.writeExpression(minimumSpeedToTurn)
buffer.writeExpression(rotationSpeed)
buffer.writeExpression(lookYawLimit)
}
@@ -328,15 +468,9 @@ class VehicleSettings : RidingBehaviourSettings {
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
canJump = buffer.readExpression()
jumpVector = listOf(
buffer.readExpression(),
buffer.readExpression(),
buffer.readExpression()
)
speed = buffer.readExpression()
driveFactor = buffer.readExpression()
reverseDriveFactor = buffer.readExpression()
minimumSpeedToTurn = buffer.readExpression()
rotationSpeed = buffer.readExpression()
lookYawLimit = buffer.readExpression()
}
@@ -345,11 +479,36 @@ class VehicleSettings : RidingBehaviourSettings {
class VehicleState : RidingBehaviourState() {
var currSpeed = ridingState(0.0, Side.BOTH)
var deltaRotation = ridingState(Vec2.ZERO, Side.BOTH)
var drifting = ridingState(false, Side.CLIENT)
var poweredDrifting = ridingState(false, Side.CLIENT)
val turnMomentum: SidedRidingState<Float> = ridingState(0.0f, Side.CLIENT)
var inAir = ridingState(false, Side.CLIENT)
override fun reset() {
super.reset()
currSpeed.set(0.0, forced = true)
deltaRotation.set(Vec2.ZERO, forced = true)
drifting.set(false, forced = true)
poweredDrifting.set(false, forced = true)
turnMomentum.set(0.0f, forced = true)
inAir.set(false, forced = true)
}
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeBoolean(drifting.get())
buffer.writeBoolean(poweredDrifting.get())
buffer.writeFloat(turnMomentum.get())
buffer.writeBoolean(inAir.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
drifting.set(buffer.readBoolean(), forced = true)
poweredDrifting.set(buffer.readBoolean(), forced = true)
turnMomentum.set(buffer.readFloat(), forced = true)
inAir.set(buffer.readBoolean(), forced = true)
}
override fun toString(): String {
@@ -361,5 +520,18 @@ class VehicleState : RidingBehaviourState() {
it.stamina.set(stamina.get(), forced = true)
it.currSpeed.set(currSpeed.get(), forced = true)
it.deltaRotation.set(deltaRotation.get(), forced = true)
it.drifting.set(drifting.get(), forced = true)
it.poweredDrifting.set(poweredDrifting.get(), forced = true)
it.turnMomentum.set(turnMomentum.get(), forced = true)
it.inAir.set(inAir.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is VehicleState) return false
if (previous.drifting.get() != drifting.get()) return true
if (previous.poweredDrifting.get() != poweredDrifting.get()) return true
if (previous.turnMomentum.get() != turnMomentum.get()) return true
if (previous.inAir.get() != inAir.get()) return true
return super.shouldSync(previous)
}
}
@@ -330,7 +330,7 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
return vehicle.runtime.resolveBoolean(settings.rotatePokemonHead)
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity
@@ -215,7 +215,7 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
return false
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: BurstSettings,
state: BurstState,
vehicle: PokemonEntity
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.riding.behaviour.types.liquid
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
@@ -19,6 +20,7 @@ import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
@@ -29,7 +31,9 @@ import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.min
import kotlin.math.sin
import kotlin.math.sqrt
class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
companion object {
@@ -43,7 +47,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
}
val poseProvider = PoseProvider<DolphinSettings, DolphinState>(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.deltaMovement.length() > 0.1 })
override fun isActive(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
@@ -161,11 +165,59 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
//Might need to add the smoothing here for default.
//TODO:hook up handling
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//Interpret mouse input
val smoothingSpeed = 4.0
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
return Vec3(0.0, mouseY * invertPitch, mouseX * invertRoll)
val mouseXc = (mouseX).coerceIn(-60.0, 60.0)
val mouseYc = (mouseY).coerceIn(-60.0, 60.0)
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1 * invertRoll, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1 * invertPitch, deltaTime * smoothingSpeed);
val rollRot = xInput
val pitchRot = yInput
// Roll
// if (abs(controller.pitch + rollRot) >= 89.5 ) {
// mouseXSmoother.reset()
// } else {
//
// }
controller.rotateRoll(rollRot.toFloat())
// Yaw
// TODO:move all magic numbers to handling expression
val yawSpeed = handling * deltaTime * 0.25
val yawForce = sin(controller.roll.toRadians()) * yawSpeed * ( 1.0 - min(sqrt(normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed)), 0.5))
//Apply yaw globally as we don't want roll or pitch changes due to local yaw when looking up or down.
controller.applyGlobalYaw(yawForce.toFloat())
// Pitch
if (abs(controller.pitch + pitchRot) >= 89.5 ) {
mouseYSmoother.reset()
} else {
//controller.applyGlobalPitch(pitchRot.toFloat() * -1.0f)
controller.rotatePitch(pitchRot.toFloat())
}
//yaw, pitch, roll
return Vec3.ZERO
}
/*
* Normalizes the given value between a min and a max.
* The result is clamped between 0.0 and 1.0, where 0.0 represents x is at or below min
* and 1.0 represents x is at or above it.
*/
private fun normalizeVal(x: Double, min: Double, max: Double): Double {
require(max > min) { "max must be greater than min" }
return ((x - min) / (max - min)).coerceIn(0.0, 1.0)
}
override fun canJump(
@@ -254,7 +306,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
return false
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity
@@ -295,6 +347,12 @@ class DolphinSettings : RidingBehaviourSettings {
var strafeFactor = "0.2".asExpression()
private set
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 200.0, 128.0)".asExpression()
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 135.0, 45.0)".asExpression()
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 1.0, 0.35)".asExpression()
var accelerationExpr: Expression = "q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 3.0)), (1.0 / (20.0 * 8.0)))".asExpression()
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 120.0, 20.0)".asExpression()
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
@@ -50,7 +50,7 @@ class RideLoopSound(val ride: PokemonEntity, val soundSettings: RideSoundSetting
this.looping = true
this.delay = 0
this.volume = 0.1f
this.attenuation = SoundInstance.Attenuation.NONE // This class performs its own attenuation
this.attenuation = SoundInstance.Attenuation.LINEAR // This class performs its own attenuation
if (this.isPassenger) this.relative = true
}
@@ -16,6 +16,7 @@ import net.minecraft.world.phys.Vec3
import org.joml.Matrix3f
import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.sign
/**
* Class to handle the updating and containment of ride data used in animation.
@@ -33,6 +34,7 @@ class RidingAnimationData(val ride: PokemonEntity)
var rotSpring: Vec3Spring = Vec3Spring()
var rotDeltaSpring: Vec3Spring = Vec3Spring()
var localVelocitySpring: Vec3Spring = Vec3Spring()
var driverInputSpring: Vec3Spring = Vec3Spring()
fun update() {
val activeRide = ride.hasControllingPassenger()
@@ -136,6 +138,14 @@ class RidingAnimationData(val ride: PokemonEntity)
/******************************************************
* Update springs
*****************************************************/
val driver = ride.controllingPassenger
val vertInput = when {
driver != null && driver.jumping-> 1.0
driver != null && driver.isShiftKeyDown -> -1.0
else -> 0.0
}
val input = if(driver != null) Vec3(driver.xxa.toDouble() * -1, vertInput, driver.zza.toDouble()) else Vec3.ZERO
driverInputSpring.update(input, stiffness * 2, damping)
rotDeltaSpring.update(angDelta, stiffness, damping)
velocitySpring.update(currentVelocity, stiffness, damping)
rotSpring.update(currRot, stiffness, damping)
@@ -1,24 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning
import java.time.LocalDate
open class IrlDateRange (
val startDate: LocalDate,
val stopDate: LocalDate? = null
) {
operator fun contains(date: LocalDate): Boolean {
return if(stopDate != null) {
(startDate.isEqual(date) || startDate.isBefore(date)) && (stopDate.isAfter(date) || stopDate.isEqual(date))
} else {
startDate.isEqual(date)
}
}
}
@@ -1,20 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning
import java.time.LocalTime
open class IrlTimeRange(
val startTime: LocalTime,
val stopTime: LocalTime
) {
operator fun contains(time: LocalTime): Boolean {
return (time.isAfter(startTime) || time == startTime) && time.isBefore(stopTime)
}
}
@@ -74,8 +74,6 @@ object SpawnLoader {
.registerTypeAdapter(NPCClass::class.java, NPCClassReferenceAdapter)
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.registerTypeAdapter(IrlTimeRange::class.java, IrlTimeRangeAdapter)
.registerTypeAdapter(IrlDateRange::class.java, IrlDateRangeAdapter)
.create()
var deserializingConditionClass: Class<out SpawningCondition<*>>? = null
@@ -8,10 +8,7 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.IrlDateRange
import com.cobblemon.mod.common.api.spawning.IrlTimeRange
import com.cobblemon.mod.common.api.spawning.MoonPhaseRange
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
@@ -24,8 +21,6 @@ import net.minecraft.tags.TagKey
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.levelgen.WorldgenRandom
import net.minecraft.world.level.levelgen.structure.Structure
import java.time.LocalDate
import java.time.LocalTime
/**
* The root of spawning conditions that can be applied to a spawnable position. What type
@@ -63,8 +58,6 @@ abstract class SpawningCondition<T : SpawnablePosition> {
var timeRange: TimeRange? = null
var structures: MutableList<Either<ResourceLocation, TagKey<Structure>>>? = null
var isSlimeChunk: Boolean? = null
var irlTimeRange: IrlTimeRange? = null
var irlDateRange: IrlDateRange? = null
var markers: MutableList<String>? = null
@Transient
@@ -136,10 +129,6 @@ abstract class SpawningCondition<T : SpawnablePosition> {
val random = Random(seed)
return random.nextInt(10) == 0*/
} else if (irlTimeRange != null && !irlTimeRange!!.contains(LocalTime.now(Cobblemon.config.timeZoneId))){
return false
} else if (irlDateRange != null && !irlDateRange!!.contains(LocalDate.now(Cobblemon.config.timeZoneId))){
return false
}
return true
@@ -171,10 +160,6 @@ abstract class SpawningCondition<T : SpawnablePosition> {
return false
if (structures != null && structures!!.any { it == null })
return false
if(irlTimeRange != null && irlTimeRange!!.stopTime.isBefore(irlTimeRange!!.startTime))
return false
if(irlDateRange != null && irlDateRange!!.stopDate != null && irlDateRange!!.stopDate!!.isBefore(irlDateRange!!.startDate))
return false
return true
}
}
@@ -19,7 +19,15 @@ import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.render.models.blockbench.ExcludedLabels
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.animation.*
import com.cobblemon.mod.common.client.render.models.blockbench.animation.ActiveAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.BimanualSwingAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.BipedWalkAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PitchTiltAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PrimaryAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PunchAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.QuadrupedWalkAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.SingleBoneLookAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.WingFlapIdleAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunction
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.sineFunction
@@ -117,6 +125,22 @@ object ClientMoLangFunctions {
ObjectValue(anim)
}
},
"pitch_tilt" to Function {
model -> Function { params ->
val bone = params.getStringOrNull(0) ?: "root"
val maxChangePerTick = params.getDoubleOrNull(1) ?: 1.5F
val minPitch = params.getDoubleOrNull(2) ?: -45F
val maxPitch = params.getDoubleOrNull(3) ?: 45F
ObjectValue(
PitchTiltAnimation(
bone = model.getPart(bone),
minPitch = minPitch.toFloat(),
maxPitch = maxPitch.toFloat(),
maxChangePerTick = maxChangePerTick.toFloat()
)
)
}
},
"look" to Function {
model -> Function { params ->
val boneName = params.getString(0)
@@ -14,6 +14,7 @@ import com.cobblemon.mod.common.api.riding.behaviour.types.air.*
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeState
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.MinekartBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BoatBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BurstBehaviour
@@ -133,25 +134,34 @@ class RideControlsOverlay : Gui(Minecraft.getInstance()) {
// Configure what controls to show for each behaviour
when (currentBehaviourKey) {
BirdBehaviour.KEY -> {}
BoatBehaviour.KEY -> {}
BurstBehaviour.KEY -> {}
DolphinBehaviour.KEY -> {}
GliderBehaviour.KEY -> {}
HelicopterBehaviour.KEY -> {}
HorseBehaviour.KEY -> {
showVerticalMouse = false
disableHorizontalMovementKeys = true
showSneakKey = false
}
BoatBehaviour.KEY -> {}
GliderBehaviour.KEY -> {}
HelicopterBehaviour.KEY -> {}
HoverBehaviour.KEY -> {
showVerticalMouse = false
}
JetBehaviour.KEY -> {
showMovementKeys = false
showSneakKey = false
showJumpKey = false
}
BurstBehaviour.KEY -> {}
VehicleBehaviour.KEY -> {}
HoverBehaviour.KEY -> {
MinekartBehaviour.KEY -> {
showVerticalMouse = false
showHorizontalMouse = false
showSneakKey = false
}
RocketBehaviour.KEY -> {
showVerticalMouse = false
showHorizontalMouse = false
}
VehicleBehaviour.KEY -> {}
}
val centerX = minecraft.window.guiScaledWidth / 2
@@ -719,6 +719,7 @@ class PCGUI(
if (pasture.pastureScrollList.isHovered) pasture.pastureScrollList.mouseScrolled(mouseX, mouseY, amount, verticalAmount)
}
if (storageWidget.isHovered && mouseX < (storageWidget.x + StorageWidget.SCREEN_WIDTH)) this.storageWidget.box -= verticalAmount.toInt()
if (wallpaperWidget.isHovered) wallpaperWidget.mouseScrolled(mouseX, mouseY, amount, verticalAmount)
return super.mouseScrolled(mouseX, mouseY, amount, verticalAmount)
}
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.net.riding
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateDriverInputPacket
import net.minecraft.client.Minecraft
import net.minecraft.client.player.RemotePlayer
object ClientboundUpdateDriverInputHandler : ClientNetworkPacketHandler<ClientboundUpdateDriverInputPacket> {
override fun handle(packet: ClientboundUpdateDriverInputPacket, client: Minecraft) {
client.executeIfPossible {
val level = client.level ?: return@executeIfPossible
val entity = level.getEntity(packet.entityId)
if (entity is RemotePlayer) {
entity.xxa = packet.driverInput.x
entity.zza = packet.driverInput.z
entity.jumping = packet.driverInput.y == 1.0f
entity.isShiftKeyDown = packet.driverInput.y == -1.0f
}
}
}
}
@@ -0,0 +1,70 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.entity
import com.cobblemon.mod.common.api.riding.Rideable
import com.cobblemon.mod.common.client.MountedPokemonAnimationRenderController
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.math.Axis
import net.minecraft.util.Mth
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.phys.Vec3
import org.joml.AxisAngle4f
import org.joml.Vector3f
object MountedLivingEntityRenderer {
fun render(
entity: LivingEntity,
pokemon: PokemonEntity,
stack: PoseStack,
bob: Float,
yBodyRot: Float,
partialTicks: Float,
i: Float
) {
if (entity.vehicle !is Rideable) return
val matrix = stack.last().pose()
val seatIndex = pokemon.passengers.indexOf(entity)
val seat = pokemon.seats[seatIndex]
val delegate = pokemon.delegate as PokemonClientDelegate
val locator = delegate.locatorStates[seat.locator]
//Positions player
if (locator != null) {
MountedPokemonAnimationRenderController.setup(pokemon, partialTicks)
//Undo seat position
val playerPos = Vec3(
Mth.lerp(partialTicks.toDouble(), entity.xOld, entity.x),
Mth.lerp(partialTicks.toDouble(), entity.yOld, entity.y),
Mth.lerp(partialTicks.toDouble(), entity.zOld, entity.z),
)
val entityPos = Vec3(
Mth.lerp(partialTicks.toDouble(), pokemon.xOld, pokemon.x),
Mth.lerp(partialTicks.toDouble(), pokemon.yOld, pokemon.y),
Mth.lerp(partialTicks.toDouble(), pokemon.zOld, pokemon.z),
)
matrix.translate(playerPos.subtract(entityPos).toVector3f().negate())
matrix.translate(locator.matrix.getTranslation(Vector3f()))
val offset = Vector3f(0f, entity.bbHeight / 2, 0f).mul(-1f)
matrix.rotate(locator.matrix.getRotation(AxisAngle4f()))
matrix.rotate(Axis.YP.rotationDegrees(180 + yBodyRot))
matrix.translate(offset)
matrix.translate(Vector3f(0f, 0.35f, 0f))
}
}
}
@@ -607,6 +607,7 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
setDefault()
// Applies any of the state's queued actions.
state.preRender()
state.renderMarkers.clear()
// Performs a check that the current pose is correct and returns back which pose we should be applying. Even if
// a change of pose is necessary, if it's going to gradually transition there then we're still going to keep
// applying our current pose until that process is done.
@@ -758,8 +759,6 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
yRot = Mth.lerp(state.getPartialTicks(), entity.yBodyRotO, entity.yBodyRot)
yRot = 180f - Mth.wrapDegrees(yRot)
}
//For some reason locators are positioned upside-down so this fixes that
matrixStack.mulPose(Axis.ZP.rotationDegrees(180f))
} else if (entity is EmptyPokeBallEntity) {
scale = 0.7F
yRot = Mth.lerp(state.getPartialTicks(), entity.yRot, entity.yRotO)
@@ -775,6 +774,9 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
matrixStack.scale(scale, scale, scale)
matrixStack.mulPose(Axis.YP.rotationDegrees(yRot))
// Entities' locators are positioned upside-down so this fixes that
if(entity != null) matrixStack.mulPose(Axis.ZP.rotationDegrees(180f))
locatorAccess.update(matrixStack, entity, scale, state.locatorStates, isRoot = true)
}
@@ -131,6 +131,14 @@ abstract class PosableState : Schedulable {
/** A list of items to be rendered on a PosableModel during an animation */
val animationItems = mutableMapOf<String, ItemStack>()
/** Just some numbers that might be getting arbitrarily set and referenced. */
val numbers = mutableMapOf<String, Float>()
/**
* Markers that have been added to the state this render, cleared when animation is about to start.
* This is used to hand basic state from one animation to another.
*/
val renderMarkers = mutableSetOf<String>()
/** All of the MoLang functions that can be expose current riding data. */
val ridingFunctions = QueryStruct(hashMapOf())
.addFunction("pitch") { params ->
@@ -255,6 +263,36 @@ abstract class PosableState : Schedulable {
//?: return@addFunction DoubleValue(0.0)
return@addFunction DoubleValue((partialTickZVel / topSpeed).coerceIn(-1.0,1.0))
}
.addFunction("input_right") { params ->
val lookBackTick = params.getInt(0)
val pokemon = getEntity() as? PokemonEntity ?: return@addFunction DoubleValue(0.0)
val partialTickZVel = pokemon.ridingAnimationData.driverInputSpring.getInterpolated(currentPartialTicks.toDouble(), lookBackTick).x
val topSpeed = 1.0//pokemon.riding.getController(pokemon)?.getStat(pokemon, RidingStat.SPEED)
//?: return@addFunction DoubleValue(0.0)
return@addFunction DoubleValue((partialTickZVel / topSpeed).coerceIn(-1.0,1.0) * -1.0)
}
.addFunction("input_up") { params ->
val lookBackTick = params.getInt(0)
val pokemon = getEntity() as? PokemonEntity ?: return@addFunction DoubleValue(0.0)
val partialTickZVel = pokemon.ridingAnimationData.driverInputSpring.getInterpolated(currentPartialTicks.toDouble(), lookBackTick).y
val topSpeed = 1.0//pokemon.riding.getController(pokemon)?.getStat(pokemon, RidingStat.SPEED)
//?: return@addFunction DoubleValue(0.0)
return@addFunction DoubleValue((partialTickZVel / topSpeed).coerceIn(-1.0,1.0))
}
.addFunction("input_forward") { params ->
val lookBackTick = params.getInt(0)
val pokemon = getEntity() as? PokemonEntity ?: return@addFunction DoubleValue(0.0)
val partialTickZVel = pokemon.ridingAnimationData.driverInputSpring.getInterpolated(currentPartialTicks.toDouble(), lookBackTick).z
val topSpeed = 1.0//pokemon.riding.getController(pokemon)?.getStat(pokemon, RidingStat.SPEED)
//?: return@addFunction DoubleValue(0.0)
return@addFunction DoubleValue((partialTickZVel / topSpeed).coerceIn(-1.0,1.0))
}
/** All of the MoLang functions that can be applied to something with this state. */
@@ -288,6 +326,31 @@ abstract class PosableState : Schedulable {
}
}
}
.addFunction("recurring_sound") { params ->
if (params.get<MoValue>(0) !is StringValue) {
return@addFunction Unit
}
//param 0 - sound
//param 1 - offset
//param 2 -
val curr_time = reusableAnimTime.value
val entity = getEntity()
val soundEvent = SoundEvent.createVariableRangeEvent(params.getString(0).asIdentifierDefaultingNamespace())
val offset = params.getDouble(1)
val loopLength = params.getDouble(2)
if (soundEvent != null) {
val volume = 1F
val pitch = 1F
if( )
if (entity != null) {
if (!entity.isSilent) {
entity.level().playLocalSound(entity, soundEvent, entity.soundSource, volume, pitch)
}
} else {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(soundEvent, volume, pitch))
}
}
}
.addFunction("render_item") { params ->
if (params.get<MoValue>(0) !is StringValue) return@addFunction Unit
@@ -10,15 +10,12 @@ package com.cobblemon.mod.common.client.render.models.blockbench
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.client.render.models.blockbench.animation.ActiveAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.isBattling
import com.cobblemon.mod.common.util.isDusk
import com.cobblemon.mod.common.util.isStandingOnRedSand
import com.cobblemon.mod.common.util.isStandingOnSand
import com.cobblemon.mod.common.util.isStandingOnSandOrRedSand
import com.cobblemon.mod.common.util.isStandingOn
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
@@ -57,9 +54,9 @@ class PoseAdapter(
addCondition("isUnderWater") { entity, expectedValue -> entity.isUnderWater == expectedValue }
// Kept for compatibility
addCondition("isStandingOnRedSand") { entity, expectedValue -> entity.isStandingOnRedSand() == expectedValue }
addCondition("isStandingOnSand") { entity, expectedValue -> entity.isStandingOnSand() == expectedValue }
addCondition("isStandingOnSandOrRedSand") { entity, expectedValue -> entity.isStandingOnSandOrRedSand() == expectedValue }
addCondition("isStandingOnRedSand") { entity, expectedValue -> entity.isStandingOn(setOf("minecraft:red_sand")) == expectedValue }
addCondition("isStandingOnSand") { entity, expectedValue -> entity.isStandingOn(setOf("minecraft:sand")) == expectedValue }
addCondition("isStandingOnSandOrRedSand") { entity, expectedValue -> entity.isStandingOn(setOf("minecraft:sand", "minecraft:red_sand")) == expectedValue }
addCondition("isDusk") { entity, expectedValue -> entity.isDusk() == expectedValue }
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.animation
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.addRotation
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Bone
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.util.math.geometry.toDegrees
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.util.Mth
/**
* Animation used to tilt the targeted bone of the entity by a pitch that is proportionate
* to the direction the entity is moving. This is used to make birds and fish tilt upwards
* or downwards based on their motion.
*
* @author Hiroku
* @since August 9th, 2025
*/
class PitchTiltAnimation(
val bone: Bone,
val maxChangePerTick: Float = 1F,
val minPitch: Float = -45F,
val maxPitch: Float = 45F,
) : PoseAnimation() {
companion object {
const val PREVIOUS_ANGLE = "previous_pitch_tilt_angle"
const val PITCHED_TILT = "has_pitched_tilt"
}
override fun setupAnim(
context: RenderContext,
model: PosableModel,
state: PosableState,
limbSwing: Float,
limbSwingAmount: Float,
ageInTicks: Float,
headYaw: Float,
headPitch: Float,
intensity: Float
) {
val entity = context.entity ?: return
if (entity.passengers.isNotEmpty()) {
return
}
val delta = entity.deltaMovement
// The angleOfMotion should be 0 in the case that horizontal movement is 1 and vertical movement is 0.
// It should be the angle in degrees that becomes higher as the entity's motion becomes more upwards, and negative as the entity's motion becomes more downwards
val vertical = delta.y
val horizontal = delta.horizontalDistance()
val angleOfMotion = if (horizontal == 0.0) {
when {
vertical > 0 -> maxPitch
vertical < 0 -> minPitch
else -> 0F
}
} else {
Mth.atan2(vertical, horizontal).toDegrees()
}.coerceIn(minPitch, maxPitch)
val maxChangePerTick = this.maxChangePerTick * intensity
val lastAngle = state.numbers[PREVIOUS_ANGLE] ?: 0F
val interpolatedAngle = if (angleOfMotion - lastAngle > maxChangePerTick) {
lastAngle + maxChangePerTick
} else if (angleOfMotion - lastAngle < -maxChangePerTick) {
lastAngle - maxChangePerTick
} else {
angleOfMotion
}
bone.addRotation(0, -interpolatedAngle.toRadians())
state.numbers[PREVIOUS_ANGLE] = interpolatedAngle
state.renderMarkers += PITCHED_TILT
}
}
@@ -11,6 +11,8 @@ package com.cobblemon.mod.common.client.render.models.blockbench.animation
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.addRotation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PitchTiltAnimation.Companion.PITCHED_TILT
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PitchTiltAnimation.Companion.PREVIOUS_ANGLE
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Bone
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation.Companion.X_AXIS
@@ -63,7 +65,10 @@ class SingleBoneLookAnimation(
val head = bone ?: return
val pitch = pitchMultiplier * headPitch.coerceIn(minPitch, maxPitch)
val yaw = yawMultiplier * headYaw.coerceIn(minYaw, maxYaw)
head.addRotation(X_AXIS, pitch.toRadians() * intensity)
// If PitchTiltAnimation was applied then we should counteract that tilt. It does kinda
// assume that the tilt was on the body bone, though.
val counterTiltDegrees = if (PITCHED_TILT in state.renderMarkers) { state.numbers[PREVIOUS_ANGLE] ?: 0 } else 0F
head.addRotation(X_AXIS, (pitch.toRadians() * intensity) + counterTiltDegrees.toRadians())
head.addRotation(Y_AXIS, yaw.toRadians() * intensity)
}
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen1
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PitchTiltAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.createTransformation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
@@ -86,6 +87,7 @@ class PidgeyModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(
PitchTiltAnimation(bone = getPart("body"), maxChangePerTick = 1F),
singleBoneLook(),
bedrock("pidgey", "air_idle")
)
@@ -103,6 +105,7 @@ class PidgeyModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(
PitchTiltAnimation(bone = getPart("body"), maxChangePerTick = 1F),
singleBoneLook(),
bedrock("pidgey", "air_fly")
)
@@ -15,9 +15,7 @@ import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonP
import com.cobblemon.mod.common.client.render.models.blockbench.pose.CobblemonPose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.util.isBattling
import com.cobblemon.mod.common.util.isStandingOnRedSand
import com.cobblemon.mod.common.util.isStandingOnSand
import com.cobblemon.mod.common.util.isStandingOnSandOrRedSand
import com.cobblemon.mod.common.util.isStandingOn
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
@@ -63,7 +61,7 @@ class HippowdonModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
sleep = registerPose(
poseName = "sleep",
poseType = PoseType.SLEEP,
condition = { it.getEntity()?.isStandingOnSand() != true },
condition = { it.getEntity()?.isStandingOn(setOf("minecraft:sand")) != true },
transformedParts = arrayOf(
sand.createTransformation().withVisibility(visibility = false),
redsand.createTransformation().withVisibility(visibility = false)
@@ -77,7 +75,7 @@ class HippowdonModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
sleepredsand = registerPose(
poseName = "sleepsand",
poseType = PoseType.SLEEP,
condition = { it.getEntity()?.isStandingOnRedSand() == true },
condition = { it.getEntity()?.isStandingOn(setOf("minecraft:red_sand")) == true },
transformedParts = arrayOf(
sand.createTransformation().withVisibility(visibility = false),
redsand.createTransformation().withVisibility(visibility = false)
@@ -91,7 +89,7 @@ class HippowdonModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.UI_POSES + PoseType.STATIONARY_POSES,
condition = { !it.isBattling && it.getEntity()?.isStandingOnSandOrRedSand() != true },
condition = { !it.isBattling && it.getEntity()?.isStandingOn(setOf("minecraft:sand", "minecraft:red_sand")) != true },
quirks = arrayOf(blink, idlequirk),
transformedParts = arrayOf(
sand.createTransformation().withVisibility(visibility = false),
@@ -106,7 +104,7 @@ class HippowdonModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
walk = registerPose(
poseName = "walk",
poseTypes = PoseType.MOVING_POSES,
condition = { it.getEntity()?.isStandingOnSandOrRedSand() != true },
condition = { it.getEntity()?.isStandingOn(setOf("minecraft:sand", "minecraft:red_sand")) != true },
quirks = arrayOf(blink),
transformedParts = arrayOf(
sand.createTransformation().withVisibility(visibility = false),
@@ -122,7 +120,7 @@ class HippowdonModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
poseName = "standingsand",
poseTypes = PoseType.STATIONARY_POSES,
quirks = arrayOf(blink, idlequirk),
condition = { !it.isBattling && it.getEntity()?.isStandingOnSand() == true },
condition = { !it.isBattling && it.getEntity()?.isStandingOn(setOf("minecraft:sand")) == true },
transformedParts = arrayOf(
sand.createTransformation().withVisibility(visibility = false),
redsand.createTransformation().withVisibility(visibility = false)
@@ -137,7 +135,7 @@ class HippowdonModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
poseName = "standingredsand",
poseTypes = PoseType.STATIONARY_POSES,
quirks = arrayOf(blink, idlequirk),
condition = { !it.isBattling && it.getEntity()?.isStandingOnRedSand() == true },
condition = { !it.isBattling && it.getEntity()?.isStandingOn(setOf("minecraft:red_sand")) == true },
transformedParts = arrayOf(
sand.createTransformation().withVisibility(visibility = false),
redsand.createTransformation().withVisibility(visibility = false)
@@ -151,7 +149,7 @@ class HippowdonModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
walksand = registerPose(
poseName = "walksand",
poseTypes = PoseType.MOVING_POSES,
condition = { it.getEntity()?.isStandingOnSand() == true },
condition = { it.getEntity()?.isStandingOn(setOf("minecraft:sand")) == true },
quirks = arrayOf(blink),
transformedParts = arrayOf(
sand.createTransformation().withVisibility(visibility = false),
@@ -165,7 +163,7 @@ class HippowdonModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
walkredsand = registerPose(
poseName = "walkredsand",
poseTypes = PoseType.MOVING_POSES,
condition = { it.getEntity()?.isStandingOnRedSand() == true },
condition = { it.getEntity()?.isStandingOn(setOf("minecraft:red_sand")) == true },
quirks = arrayOf(blink),
transformedParts = arrayOf(
sand.createTransformation().withVisibility(visibility = false),
@@ -179,7 +177,7 @@ class HippowdonModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
battleidle = registerPose(
poseName = "battleidle",
poseTypes = PoseType.STATIONARY_POSES,
condition = { it.isBattling && it.getEntity()?.isStandingOnSandOrRedSand() != true },
condition = { it.isBattling && it.getEntity()?.isStandingOn(setOf("minecraft:sand", "minecraft:red_sand")) != true },
quirks = arrayOf(blink),
transformedParts = arrayOf(
sand.createTransformation().withVisibility(visibility = false),
@@ -194,7 +192,7 @@ class HippowdonModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
battleidlesand = registerPose(
poseName = "battleidlesand",
poseTypes = PoseType.STATIONARY_POSES,
condition = { it.getEntity()?.isStandingOnSand() == true && it.isBattling },
condition = { it.getEntity()?.isStandingOn(setOf("minecraft:sand")) == true && it.isBattling },
quirks = arrayOf(blink),
transformedParts = arrayOf(
sand.createTransformation().withVisibility(visibility = true),
@@ -209,7 +207,7 @@ class HippowdonModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
battleidleredsand = registerPose(
poseName = "battleidleredsand",
poseTypes = PoseType.STATIONARY_POSES,
condition = { it.getEntity()?.isStandingOnRedSand() == true && it.isBattling },
condition = { it.getEntity()?.isStandingOn(setOf("minecraft:red_sand")) == true && it.isBattling },
quirks = arrayOf(blink),
transformedParts = arrayOf(
sand.createTransformation().withVisibility(visibility = false),
@@ -1,73 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen5
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.animation.ActiveAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class GolurkModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
override val rootPart = root.registerChildWithAllChildren("golurk")
override val head = getPart("head")
override var portraitScale = 3.0F
override var portraitTranslation = Vec3(-0.3, 5.4, 0.0)
override var profileScale = 0.4F
override var profileTranslation = Vec3(0.0, 1.15, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
lateinit var sleep: Pose
override val cryAnimation = CryProvider { bedrockStateful("golurk", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("golurk", "blink") }
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.STATIONARY_POSES + PoseType.UI_POSES,
transformTicks = 20,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("golurk", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = PoseType.MOVING_POSES,
transformTicks = 20,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("golurk", "ground_walk")
)
)
sleep = registerPose(
poseName = "sleep",
transformTicks = 20,
poseType = PoseType.SLEEP,
animations = arrayOf(bedrock("golurk", "sleep"))
)
}
override fun getFaintAnimation(state: PosableState): ActiveAnimation? {
return if (state.isNotPosedIn(sleep)) bedrockStateful("golurk", "faint") else null
}
}
@@ -405,7 +405,6 @@ object VaryingModelRepository {
inbuilt("lombre", :: LombreModel)
inbuilt("ludicolo", :: LudicoloModel)
inbuilt("golett", :: GolettModel)
inbuilt("golurk", :: GolurkModel)
inbuilt("stantler", :: StantlerModel)
inbuilt("sneasel", :: SneaselModel)
inbuilt("weavile", :: WeavileModel)
@@ -36,16 +36,26 @@ import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity.Companion.SPAWN_DIRECTION
import com.cobblemon.mod.common.pokeball.PokeBall
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.effectiveName
import com.cobblemon.mod.common.util.isLookingAt
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.math.DoubleRange
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.math.remap
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.math.Axis
import kotlin.math.PI
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sqrt
import net.minecraft.ChatFormatting
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.Font.DisplayMode
import net.minecraft.client.renderer.*
import net.minecraft.client.renderer.LightTexture
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.entity.EntityRendererProvider
import net.minecraft.client.renderer.entity.ItemRenderer
import net.minecraft.client.renderer.entity.MobRenderer
@@ -57,8 +67,11 @@ import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
import net.minecraft.world.entity.Entity
import net.minecraft.world.phys.Vec3
import org.joml.*
import kotlin.math.*
import org.joml.Math
import org.joml.Matrix4f
import org.joml.Quaternionf
import org.joml.Vector3f
import org.joml.Vector4f
class PokemonRenderer(
context: EntityRendererProvider.Context
@@ -127,7 +140,7 @@ class PokemonRenderer(
clientDelegate
)
}
if(entity.platform != PlatformType.NONE) {
if (entity.platform != PlatformType.NONE) {
drawPlatform(
poseMatrix,
entity,
@@ -144,8 +157,7 @@ class PokemonRenderer(
if (entity.passengers.isNotEmpty()) {
renderRiding(entity, entityYaw, partialTicks, poseMatrix, buffer, packedLight)
}
else {
} else {
super.render(entity, entityYaw, partialTicks, poseMatrix, buffer, packedLight)
}
@@ -182,7 +194,7 @@ class PokemonRenderer(
val controller = rollable.orientationController
poseMatrix.pushPose()
if(!DISABLE_ROLLING_DEBUG && controller.active){
if (!DISABLE_ROLLING_DEBUG && controller.active) {
val matrix = poseMatrix.last().pose()
val yaw = Mth.rotLerp(partialTicks, entity.yBodyRotO, entity.yBodyRot)
val center = Vector3f(0f, entity.bbHeight/2, 0f)
@@ -0,0 +1,100 @@
/*
* 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.command
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.permission.CobblemonPermissions
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.commandLang
import com.cobblemon.mod.common.util.entity
import com.cobblemon.mod.common.util.permission
import com.cobblemon.mod.common.util.player
import com.cobblemon.mod.common.util.string
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.arguments.StringArgumentType
import com.mojang.brigadier.context.CommandContext
import net.minecraft.commands.CommandSourceStack
import net.minecraft.commands.Commands.argument
import net.minecraft.commands.Commands.literal
import net.minecraft.commands.arguments.EntityArgument
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.Entity
object RunMolangCommand {
private const val NAME = "runmolang"
private const val MOLANG = "molang"
private const val PLAYER = "player"
private const val NPC = "npc"
private const val POKEMON = "pokemon"
fun register(dispatcher : CommandDispatcher<CommandSourceStack>) {
dispatcher.register(literal(NAME)
.permission(CobblemonPermissions.RUN_MOLANG)
.then(
argument(MOLANG, StringArgumentType.string())
.executes { execute(it, it.string(MOLANG), null, null, null) }
.then(argument(PLAYER, EntityArgument.player())
.executes { execute(it, it.string(MOLANG), it.player(PLAYER), null, null) }
.then(argument(NPC, EntityArgument.entity())
.executes { execute(it, it.string(MOLANG), it.player(PLAYER), it.entity(
NPC
), null) }
.then(argument(POKEMON, EntityArgument.entity())
.executes { execute(it, it.string(MOLANG), it.player(PLAYER), it.entity(
NPC
), it.entity(POKEMON)) }
)
)
.then(argument(POKEMON, EntityArgument.entity())
.executes { execute(it, it.string(MOLANG), it.player(PLAYER), null, it.entity(
POKEMON
)) }
)
)
.then(argument(NPC, EntityArgument.entity())
.executes { execute(it, it.string(MOLANG), null, it.entity(NPC), null) }
.then(argument(POKEMON, EntityArgument.entity())
.executes { execute(it, it.string(MOLANG), null, it.entity(NPC), it.entity(
POKEMON
)) }
)
)
.then(argument(POKEMON, StringArgumentType.string())
.executes { execute(it, it.string(MOLANG), null, null, it.entity(POKEMON)) }
)
)
)
}
private fun execute(context: CommandContext<CommandSourceStack>, molang: String, player: ServerPlayer?, npc: Entity?, pokemon: Entity? = null): Int {
try {
val runtime = MoLangRuntime().setup()
val entity = context.source.entity
(npc as? NPCEntity)?.let { runtime.withQueryValue("npc", npc.struct) }
(pokemon as? PokemonEntity)?.let { runtime.withQueryValue("pokemon", pokemon.struct) }
player?.let { runtime.withQueryValue("player", player.asMoLangValue()) }
entity?.let { runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue()) }
val out = molang.asExpressionLike().resolve(runtime)
context.source.sendSuccess({ commandLang("molang.out", out.asString()) }, true)
} catch (e: Exception) {
e.printStackTrace()
}
return Command.SINGLE_SUCCESS
}
}
@@ -10,20 +10,20 @@ package com.cobblemon.mod.common.command
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.permission.CobblemonPermissions
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.entity
import com.cobblemon.mod.common.util.permission
import com.cobblemon.mod.common.util.player
import com.cobblemon.mod.common.util.resourceLocation
import com.cobblemon.mod.common.util.uuid
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.arguments.StringArgumentType
import com.mojang.brigadier.context.CommandContext
import java.util.UUID
import net.minecraft.commands.CommandSourceStack
import net.minecraft.commands.Commands.argument
import net.minecraft.commands.Commands.literal
@@ -31,6 +31,7 @@ import net.minecraft.commands.arguments.EntityArgument
import net.minecraft.commands.arguments.ResourceLocationArgument
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.Entity
object RunMolangScriptCommand {
private const val NAME = "runmolangscript"
@@ -47,38 +48,38 @@ object RunMolangScriptCommand {
.executes { execute(it, it.resourceLocation(SCRIPT), null, null, null) }
.then(argument(PLAYER, EntityArgument.player())
.executes { execute(it, it.resourceLocation(SCRIPT), it.player(PLAYER), null, null) }
.then(argument(NPC, StringArgumentType.string())
.executes { execute(it, it.resourceLocation(SCRIPT), it.player(PLAYER), it.uuid(NPC), null) }
.then(argument(POKEMON, StringArgumentType.string())
.executes { execute(it, it.resourceLocation(SCRIPT), it.player(PLAYER), it.uuid(NPC), it.uuid(POKEMON)) }
.then(argument(NPC, EntityArgument.entity())
.executes { execute(it, it.resourceLocation(SCRIPT), it.player(PLAYER), it.entity(NPC), null) }
.then(argument(POKEMON, EntityArgument.entity())
.executes { execute(it, it.resourceLocation(SCRIPT), it.player(PLAYER), it.entity(NPC), it.entity(POKEMON)) }
)
)
.then(argument(POKEMON, StringArgumentType.string())
.executes { execute(it, it.resourceLocation(SCRIPT), it.player(PLAYER), null, it.uuid(POKEMON)) }
.then(argument(POKEMON, EntityArgument.entity())
.executes { execute(it, it.resourceLocation(SCRIPT), it.player(PLAYER), null, it.entity(POKEMON)) }
)
)
.then(argument(NPC, StringArgumentType.string())
.executes { execute(it, it.resourceLocation(SCRIPT), null, it.uuid(NPC), null) }
.then(argument(POKEMON, StringArgumentType.string())
.executes { execute(it, it.resourceLocation(SCRIPT), null, it.uuid(NPC), it.uuid(POKEMON)) }
.then(argument(NPC, EntityArgument.entity())
.executes { execute(it, it.resourceLocation(SCRIPT), null, it.entity(NPC), null) }
.then(argument(POKEMON, EntityArgument.entity())
.executes { execute(it, it.resourceLocation(SCRIPT), null, it.entity(NPC), it.entity(POKEMON)) }
)
)
.then(argument(POKEMON, StringArgumentType.string())
.executes { execute(it, it.resourceLocation(SCRIPT), null, null, it.uuid(POKEMON)) }
.then(argument(POKEMON, EntityArgument.entity())
.executes { execute(it, it.resourceLocation(SCRIPT), null, null, it.entity(POKEMON)) }
)
)
)
}
private fun execute(context: CommandContext<CommandSourceStack>, scriptId: ResourceLocation, player: ServerPlayer?, npcId: UUID?, pokemonId: UUID? = null): Int {
private fun execute(context: CommandContext<CommandSourceStack>, scriptId: ResourceLocation, player: ServerPlayer?, npc: Entity?, pokemon: Entity? = null): Int {
try {
val runtime = MoLangRuntime().setup()
val npc = npcId?.let { context.source.level.getEntity(it) as? NPCEntity }
val pokemon = pokemonId?.let { context.source.level.getEntity(it) as? NPCEntity }
val entity = context.source.entity
npc?.let { runtime.withQueryValue("npc", npc.struct) }
pokemon?.let { runtime.withQueryValue("pokemon", pokemon.struct) }
(npc as? NPCEntity)?.let { runtime.withQueryValue("npc", npc.struct) }
(pokemon as? PokemonEntity)?.let { runtime.withQueryValue("pokemon", pokemon.struct) }
player?.let { runtime.withQueryValue("player", player.asMoLangValue()) }
entity?.let { runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue()) }
CobblemonScripts.run(scriptId, runtime)
} catch (e: Exception) {
@@ -18,7 +18,6 @@ enum class Category(val lang: String) {
Pokedex("pokedex"),
Storage("storage"),
Starter("starter"),
System("system"),
Interface("interface"),
Riding("riding"),
Debug("debug"),
@@ -16,11 +16,8 @@ import com.cobblemon.mod.common.config.constraint.IntConstraint
import com.cobblemon.mod.common.pokeball.catching.calculators.CobblemonCaptureCalculator
import com.cobblemon.mod.common.util.adapters.CaptureCalculatorAdapter
import com.cobblemon.mod.common.util.adapters.IntRangeAdapter
import com.cobblemon.mod.common.util.adapters.ZoneIdAdapter
import com.google.gson.GsonBuilder
import com.google.gson.annotations.SerializedName
import java.time.ZoneId
import java.time.ZoneOffset
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.isAccessible
@@ -32,7 +29,6 @@ class CobblemonConfig {
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
.registerTypeAdapter(ItemDropMethod::class.java, ItemDropMethod.adapter)
.registerTypeAdapter(CaptureCalculator::class.java, CaptureCalculatorAdapter)
.registerTypeHierarchyAdapter(ZoneId::class.java, ZoneIdAdapter)
.create()
}
@@ -267,9 +263,6 @@ class CobblemonConfig {
@CobblemonConfigField(Category.Pokedex, lang = "hide_unimplemented_pokemon_in_the_pokedex")
var hideUnimplementedPokemonInThePokedex = false
@CobblemonConfigField(Category.System, lang = "time_zone_id")
var timeZoneId: ZoneId = ZoneOffset.systemDefault()
@CobblemonConfigField(Category.Interface, lang = "summary_pokemon_follow_cursor")
var summaryPokemonFollowCursor = true
@@ -52,7 +52,6 @@ import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviours
import com.cobblemon.mod.common.api.riding.events.SelectDriverEvent
import com.cobblemon.mod.common.api.riding.sound.RideLoopSound
import com.cobblemon.mod.common.api.riding.sound.RideSoundManager
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.riding.util.RidingAnimationData
@@ -101,10 +100,18 @@ import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolut
import com.cobblemon.mod.common.pokemon.feature.StashHandler
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
import com.mojang.serialization.Codec
import com.mojang.serialization.Dynamic
import java.util.Optional
import java.util.UUID
import java.util.concurrent.CompletableFuture
import kotlin.math.PI
import kotlin.math.ceil
import net.minecraft.client.Minecraft
import net.minecraft.core.BlockPos
import net.minecraft.core.component.DataComponents
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.nbt.CompoundTag
@@ -153,15 +160,13 @@ import net.minecraft.world.item.ItemUtils
import net.minecraft.world.item.Items
import net.minecraft.world.level.Level
import net.minecraft.world.level.LightLayer
import net.minecraft.world.level.block.SuspiciousEffectHolder
import net.minecraft.world.level.gameevent.GameEvent
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.pathfinder.PathType
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
import java.util.*
import java.util.concurrent.CompletableFuture
import kotlin.math.PI
import kotlin.math.ceil
import kotlin.math.min
@Suppress("unused")
open class PokemonEntity(
@@ -301,6 +306,7 @@ open class PokemonEntity(
.also {
it.environment.query.addFunction("passenger_count") { DoubleValue(passengers.size.toDouble()) }
it.environment.query.addFunction("ride_velocity") { DoubleValue(getRideVelocity().length()) }
it.environment.query.addFunction("driver_input") { DoubleValue(min(ridingAnimationData.driverInputSpring.value.length(),1.0)) }
it.environment.query.addFunction("get_ride_stats") { params ->
val rideStat = RidingStat.valueOf(params.getString(0).uppercase())
val rideStyle = RidingStyle.valueOf(params.getString(1).uppercase())
@@ -560,9 +566,12 @@ open class PokemonEntity(
isPokemonFlying = flyDist - flyDistO > 0.005F
isPokemonWalking = walkDist - walkDistO > 0.005F
if (passengers.isNotEmpty()) {
if (passengers.isNotEmpty() && level().isClientSide) {
rideSoundManager.tick()
ridingAnimationData.update()
} else if (!passengers.isNotEmpty() && level().isClientSide)
{
rideSoundManager.stop()
}
flyDistO = flyDist
@@ -1068,22 +1077,10 @@ open class PokemonEntity(
player.playSound(SoundEvents.MOOSHROOM_MILK, 1.0f, 1.0f)
// if the Mooshtank ate a Flower beforehand
if (pokemon.lastFlowerFed != ItemStack.EMPTY && pokemon.aspects.any { it.contains("mooshtank-brown") }) {
when (pokemon.lastFlowerFed.item) {
Items.ALLIUM -> MobEffects.FIRE_RESISTANCE to 80
Items.AZURE_BLUET -> MobEffects.BLINDNESS to 160
Items.BLUE_ORCHID, Items.DANDELION -> MobEffects.SATURATION to 7
Items.CORNFLOWER -> MobEffects.JUMP to 120
Items.LILY_OF_THE_VALLEY -> MobEffects.POISON to 240
Items.OXEYE_DAISY -> MobEffects.REGENERATION to 160
Items.POPPY, Items.TORCHFLOWER -> MobEffects.NIGHT_VISION to 100
Items.PINK_TULIP, Items.RED_TULIP, Items.WHITE_TULIP, Items.ORANGE_TULIP -> MobEffects.WEAKNESS to 180
Items.WITHER_ROSE -> MobEffects.WITHER to 160
CobblemonItems.PEP_UP_FLOWER -> MobEffects.LEVITATION to 160
else -> null
}?.let {
SuspiciousEffectHolder.tryGet(pokemon.lastFlowerFed.item)?.let {
// modify the suspicious stew with the effect
val susStewStack = Items.SUSPICIOUS_STEW.defaultInstance
//SuspiciousStewItem.addEffectsToStew(susStewStack, listOf(StewEffect(it.first, it.second)))
val susStewStack = Items.SUSPICIOUS_STEW.defaultInstance.copy()
susStewStack.set(DataComponents.SUSPICIOUS_STEW_EFFECTS, it.suspiciousEffects)
val susStewEffect = ItemUtils.createFilledResult(itemStack, player, susStewStack)
//give player modified Suspicious Stew
player.setItemInHand(hand, susStewEffect)
@@ -1116,7 +1113,7 @@ open class PokemonEntity(
itemStack.`is`(Items.WITHER_ROSE) ||
itemStack.`is`(CobblemonItems.PEP_UP_FLOWER)
) {
if (pokemon.aspects.any { it.contains("mooshtank") }) {
if (pokemon.aspects.any { it.contains("mooshtank-brown") }) {
player.playSound(SoundEvents.MOOSHROOM_EAT, 1.0f, 1.0f)
pokemon.lastFlowerFed = itemStack
return InteractionResult.sidedSuccess(level().isClientSide)
@@ -1766,8 +1763,8 @@ open class PokemonEntity(
}
// Rotate velocity vector to face the current y rotation
val f = Mth.sin(this.yRot * 0.017453292f)
val g = Mth.cos(this.yRot * 0.017453292f)
val f = Mth.sin(this.yRot.toRadians())
val g = Mth.cos(this.yRot.toRadians())
val v = Vec3(
inp.x * g.toDouble() - inp.z * f.toDouble(),
inp.y,
@@ -2128,8 +2125,9 @@ open class PokemonEntity(
}
}
val rotation = behaviour.rotation(settings, state, this, driver)
this.yRotO = this.yRot
val rotation = behaviour.rotation(settings, state, this, driver)
setRot(rotation.y, rotation.x)
this.yHeadRot = this.yRot
this.yBodyRot = this.yRot
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.messages.client.orientation
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.net.messages.client.pokemon.update.ClientboundUpdateRidingStatePacket.Companion.ID
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readMatrix3f
import com.cobblemon.mod.common.util.writeMatrix3f
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.world.entity.player.Player
import org.joml.Matrix3f
import org.joml.Vector3f
/**
* Packet sent from the server to update the current input from
* the driver of a ridden pokemon. This data is needed for client
* side animation data updates found in RidingAnimationData
*
* @author Jackowes
* @since August 1st, 2025
*/
class ClientboundUpdateDriverInputPacket internal constructor(
val driverInput: Vector3f,
val entityId: Int
) : NetworkPacket<ClientboundUpdateDriverInputPacket> {
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeVarInt(entityId)
buffer.writeVector3f(driverInput)
}
companion object {
val ID = cobblemonResource("s2c_update_driver_input")
fun decode(buffer: RegistryFriendlyByteBuf): ClientboundUpdateDriverInputPacket {
val entityId = buffer.readVarInt()
val driverInput = buffer.readVector3f()
return ClientboundUpdateDriverInputPacket(driverInput, entityId)
}
}
}
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.messages.server.riding
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.RegistryFriendlyByteBuf
import org.joml.Vector3f
class ServerboundUpdateDriverInputPacket internal constructor(
val driverInput: Vector3f
) : NetworkPacket<ServerboundUpdateDriverInputPacket> {
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeVector3f(driverInput)
}
companion object {
val ID = cobblemonResource("c2s_update_driver_input")
fun decode(buffer: RegistryFriendlyByteBuf): ServerboundUpdateDriverInputPacket {
val driverInput = buffer.readVector3f()
return ServerboundUpdateDriverInputPacket(driverInput)
}
}
}
@@ -0,0 +1,28 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.serverhandling.riding
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.duck.PlayerDuck
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateDriverInputPacket
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
object DriverInputPacketHandler : ServerNetworkPacketHandler<ServerboundUpdateDriverInputPacket> {
override fun handle(
packet: ServerboundUpdateDriverInputPacket,
server: MinecraftServer,
player: ServerPlayer
) {
if (player is PlayerDuck) {
player.setDriverInput(packet.driverInput)
}
}
}
@@ -93,7 +93,13 @@ class SentOutState() : ActivePokemonState() {
this.dimension = entity.level().dimension()
}
override fun getIcon(pokemon: Pokemon) = cobblemonResource("textures/gui/party/party_icon_released.png")
override fun getIcon(pokemon: Pokemon): ResourceLocation {
val isBeingRidden = (pokemon.entity?.countPlayerPassengers() ?: 0) > 0
val icon = if (isBeingRidden) "mounted" else "released"
return cobblemonResource("textures/gui/party/party_icon_${icon}.png")
}
override fun writeToNBT(nbt: CompoundTag) = null
override fun writeToJSON(json: JsonObject) = null
@@ -16,4 +16,5 @@ import net.minecraft.commands.arguments.ResourceLocationArgument
fun CommandContext<CommandSourceStack>.player(argumentName: String = "player") = EntityArgument.getPlayer(this, argumentName)
fun CommandContext<CommandSourceStack>.string(argumentName: String) = this.getArgument(argumentName, String::class.java)
fun CommandContext<CommandSourceStack>.uuid(argumentName: String) = this.getArgument(argumentName, String::class.java).asUUID
fun CommandContext<CommandSourceStack>.resourceLocation(argumentName: String) = ResourceLocationArgument.getId(this, argumentName)
fun CommandContext<CommandSourceStack>.resourceLocation(argumentName: String) = ResourceLocationArgument.getId(this, argumentName)
fun CommandContext<CommandSourceStack>.entity(argumentName: String) = EntityArgument.getEntity(this, argumentName)
@@ -10,20 +10,22 @@ package com.cobblemon.mod.common.util
import com.google.common.collect.ImmutableMap
import com.mojang.serialization.Dynamic
import com.mojang.serialization.JavaOps
import java.util.ArrayDeque
import kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.min
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.registries.Registries
import net.minecraft.nbt.NbtOps
import net.minecraft.nbt.Tag
import net.minecraft.network.syncher.EntityDataAccessor
import net.minecraft.network.syncher.SynchedEntityData
import net.minecraft.tags.TagKey
import net.minecraft.util.ExtraCodecs
import net.minecraft.world.entity.Entity
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.BooleanOp
@@ -179,25 +181,21 @@ fun Entity.isDusk(): Boolean {
return time in 12000..13000
}
fun Entity.isStandingOnSand(): Boolean {
return isStandingOn(setOf(Blocks.SAND))
}
fun Entity.isStandingOnRedSand(): Boolean {
return isStandingOn(setOf(Blocks.RED_SAND))
}
fun Entity.isStandingOnSandOrRedSand(): Boolean {
return isStandingOn(setOf(Blocks.SAND, Blocks.RED_SAND))
}
fun Entity.isStandingOn(blocks: Set<Block>, depth: Int = 2): Boolean {
fun Entity.isStandingOn(blocks: Set<String>, depth: Int = 2): Boolean {
for (currentDepth in 1..depth) {
val bellowBlockPos = blockPosition().below(currentDepth)
val blockState = level().getBlockState(bellowBlockPos)
if (blockState.isAir || !blockState.isCollisionShapeFullBlock(level(), bellowBlockPos)) continue
if (blocks.contains(blockState.block)) return true
val elementOrTags = ExtraCodecs.TAG_OR_ELEMENT_ID.listOf().decode(JavaOps.INSTANCE, blocks).result().get().first
elementOrTags.forEach {
if (it.tag) {
if (blockState.blockHolder.`is`(TagKey.create(Registries.BLOCK, it.id))) return true
} else {
if (blockState.blockHolder.`is`(it.id)) return true
}
}
}
return false
@@ -1,59 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.util.adapters
import com.cobblemon.mod.common.api.spawning.IrlDateRange
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import java.lang.reflect.Type
import java.time.LocalDate
import java.time.format.DateTimeFormatter
object IrlDateRangeAdapter: JsonSerializer<IrlDateRange>, JsonDeserializer<IrlDateRange> {
val dateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("M/d")
//parsing dates requires a year instead of just assuming this year so we do jank
val dateFormatWithYear: DateTimeFormatter = DateTimeFormatter.ofPattern("uuuu/M/d")
override fun serialize(
src: IrlDateRange,
typeOfSrc: Type,
context: JsonSerializationContext
): JsonElement {
return if(src.stopDate != null){
JsonPrimitive(src.startDate.format(dateFormat) + "-" + src.stopDate.format(dateFormat))
} else {
JsonPrimitive(src.startDate.format(dateFormat))
}
}
override fun deserialize(
json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext
): IrlDateRange {
val year = LocalDate.now().year.toString()
return if(json.asString.contains("-")){
val dateStrings = json.asString.split("-")
IrlDateRange(
startDate = LocalDate.parse(year + "/" + dateStrings[0], dateFormatWithYear),
stopDate = LocalDate.parse(year + "/" + dateStrings[1], dateFormatWithYear)
)
} else {
IrlDateRange(
startDate = LocalDate.parse(year + "/" + json.asString, dateFormatWithYear)
)
}
}
}
@@ -1,45 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.util.adapters
import com.cobblemon.mod.common.api.spawning.IrlTimeRange
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import java.lang.reflect.Type
import java.time.LocalTime
import java.time.format.DateTimeFormatter
object IrlTimeRangeAdapter: JsonSerializer<IrlTimeRange>, JsonDeserializer<IrlTimeRange> {
val timeFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("H:mm")
override fun serialize(
src: IrlTimeRange,
typeOfSrc: Type,
context: JsonSerializationContext
): JsonElement {
return JsonPrimitive(src.startTime.format(timeFormat) + "-" + src.stopTime.format(timeFormat))
}
override fun deserialize(
json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext
): IrlTimeRange {
val timeStrings = json.asString.split("-")
return IrlTimeRange(
startTime = LocalTime.parse(timeStrings[0], timeFormat),
stopTime = LocalTime.parse(timeStrings[1], timeFormat)
)
}
}
@@ -19,6 +19,8 @@ import com.cobblemon.mod.common.api.riding.behaviour.types.composite.strategies.
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.strategies.RunStrategy
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.land.MinekartBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.MinekartSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.*
@@ -37,15 +39,19 @@ import java.lang.reflect.Type
object RidingBehaviourSettingsAdapter : JsonDeserializer<RidingBehaviourSettings?> {
val types: MutableMap<ResourceLocation, Class<out RidingBehaviourSettings>> = mutableMapOf(
BirdBehaviour.KEY to BirdSettings::class.java,
BirdNoRollBehaviour.KEY to BirdNoRollSettings::class.java,
DolphinBehaviour.KEY to DolphinSettings::class.java,
HorseBehaviour.KEY to HorseSettings::class.java,
BoatBehaviour.KEY to BoatSettings::class.java,
GliderBehaviour.KEY to GliderSettings::class.java,
HelicopterBehaviour.KEY to HelicopterSettings::class.java,
JetBehaviour.KEY to JetSettings::class.java,
JetNoRollBehaviour.KEY to JetNoRollSettings::class.java,
BurstBehaviour.KEY to BurstSettings::class.java,
VehicleBehaviour.KEY to VehicleSettings::class.java,
MinekartBehaviour.KEY to MinekartSettings::class.java,
HoverBehaviour.KEY to HoverSettings::class.java,
RocketBehaviour.KEY to RocketSettings::class.java,
CompositeBehaviour.KEY to CompositeSettings::class.java,
/*
@@ -1,51 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.util.adapters
import com.cobblemon.mod.common.Cobblemon
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import java.lang.reflect.Type
import java.time.ZoneId
import java.time.ZoneOffset
/**
* A type adapter for [ZoneOffset]s.
* This will never fail to deserialize and will instead default to the System's default time zone and log an error message.
*
* @author JPAK
* @since December 26, 2024
*/
object ZoneIdAdapter: JsonSerializer<ZoneId>, JsonDeserializer<ZoneId> {
override fun serialize(
src: ZoneId,
typeOfSrc: Type,
context: JsonSerializationContext
): JsonElement {
return JsonPrimitive(src.id)
}
override fun deserialize(
json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext
): ZoneId {
return try {
ZoneId.of(json.asString)
} catch (_: Exception) {
Cobblemon.LOGGER.error("Failed to load ZoneId from the ID {}, defaulting to the system time zone", json.asString)
ZoneOffset.systemDefault()
}
}
}
@@ -17503,6 +17503,10 @@
"3.1667": {
"effect": "animation.leather.wing_flap.medium"
}
},
"timeline": {
"1.125": "q.has_entity ? { q.sound('animation.chitin.wing_flap.medium'); };",
"3.1667": "q.has_entity ? { q.particle('cobblemon:ambient_shiny_sparkle', 'root'); };"
}
},
"animation.charizard.air_fly": {
@@ -18791,6 +18795,10 @@
"3.0833": {
"effect": "animation.leather.wing_flap.medium"
}
},
"timeline": {
"1.0833": "q.particle('cobblemon:ambient_shiny_sparkle', 'root');",
"3.0833": "q.sound('animation.chitin.wing_flap.medium');"
}
},
"animation.charizard.air_glide_TEST": {
@@ -6,9 +6,9 @@
"bones": {
"body": {
"rotation": [
"45*q.riding.velocity_forward(0)",
"45*math.clamp(q.riding.velocity_forward(0)* 0.5 + q.riding.input_forward(10), -1.0, 1.0)",
"0 - Math.Sin(((q.anim_time + 0.1) * 180 * 0.5) * 1) * 5",
"Math.Sin(((q.anim_time + 0.4) * 180 * 0.5) * 1) * 5 + 45*q.riding.velocity_right(0)"
"Math.Sin(((q.anim_time + 0.4) * 180 * 0.5) * 1) * 5 + 45*math.clamp(q.riding.velocity_right(0)* 0.5 + q.riding.input_right(10)* 0.5,-1.0,1.0)"
],
"position": ["Math.Sin((q.anim_time * 180 * 0.5) * 1) * 1.5", "7+Math.Sin((q.anim_time * 90 * 0.5) * 1) * 1.5", 0]
},
@@ -50,9 +50,9 @@
"bones": {
"body": {
"rotation": [
"2.5 + 45*q.riding.velocity_forward(0)",
"2.5 + 45*math.clamp(q.riding.velocity_forward(0) + q.riding.input_forward(5),-1.0,1.0)",
"- Math.Sin(((q.anim_time + 0.1) * 180 * 0.5) * 1) * 5",
"Math.Sin(((q.anim_time + 0.4) * 180 * 0.5) * 1) * 5 - 45*q.riding.velocity_right(0)"
"Math.Sin(((q.anim_time + 0.4) * 180 * 0.5) * 1) * 5 - 45*math.clamp(q.riding.velocity_right(0) + q.riding.right(5),-1.0,1.0)"
],
"position": ["Math.Sin((q.anim_time * 180 * 0.5) * 1) * 1.5", "7+Math.Sin((q.anim_time * 90 * 0.5) * 1) * 1.5", 0]
},
@@ -407,7 +407,7 @@
]
},
{
"name": "locator_seat_1",
"name": "locator_seat_2",
"parent": "body",
"pivot": [0, 0, 0],
"locators": {
@@ -4,11 +4,11 @@
{
"description": {
"identifier": "geometry.golurk",
"texture_width": 128,
"texture_height": 128,
"texture_width": 512,
"texture_height": 256,
"visible_bounds_width": 7,
"visible_bounds_height": 4.5,
"visible_bounds_offset": [0, 1.75, 0]
"visible_bounds_height": 7.5,
"visible_bounds_offset": [0, 3.25, 0]
},
"bones": [
{
@@ -18,342 +18,743 @@
{
"name": "body",
"parent": "golurk",
"pivot": [0, 38, 0]
"pivot": [0, 61, 0]
},
{
"name": "shoulder_pivot",
"parent": "body",
"pivot": [0, 68.5, 0]
},
{
"name": "torso",
"parent": "body",
"pivot": [0, 21.5, 0],
"parent": "shoulder_pivot",
"pivot": [0, 44.5, 0],
"cubes": [
{"origin": [-9, 19, -9], "size": [18, 5, 18], "uv": [0, 40]},
{"origin": [-7.5, 24, -7.5], "size": [15, 2, 15], "uv": [54, 40]},
{"origin": [-10, 26, -10], "size": [20, 20, 20], "uv": [0, 0]},
{"origin": [-8, 33, -11], "size": [11, 5, 1], "pivot": [-1.5, 35.5, -10.5], "rotation": [0, 0, 45], "uv": [98, 11]}
{"origin": [-18, 45, -18], "size": [36, 32, 36], "uv": [0, 0]},
{"origin": [-18, 51, -18], "size": [36, 24, 7], "inflate": -0.25, "uv": [170, 194]}
]
},
{
"name": "chest_glow_red",
"parent": "torso",
"pivot": [0, 63, -14.5],
"cubes": [
{"origin": [-18, 51, -18], "size": [36, 24, 7], "inflate": -0.3, "uv": [170, 225]}
]
},
{
"name": "chest_glow_off",
"parent": "torso",
"pivot": [0, 63, -14.5],
"cubes": [
{"origin": [-18, 51, -18], "size": [36, 24, 7], "inflate": -0.3, "uv": [170, 163]}
]
},
{
"name": "waist",
"parent": "torso",
"pivot": [0, 45.25, 0]
},
{
"name": "skirt",
"parent": "waist",
"pivot": [0, 45.25, 0],
"cubes": [
{"origin": [-14, 41, -14], "size": [28, 8, 28], "uv": [0, 96]},
{"origin": [-14, 41, -0.5], "size": [28, 3, 1], "inflate": 0.01, "pivot": [0, 42.5, 0], "rotation": [0, -90, 0], "uv": [109, 192]},
{"origin": [-14, 41, -0.5], "size": [28, 3, 1], "inflate": 0.01, "uv": [109, 197]},
{"origin": [-17, 34, -17], "size": [34, 7, 34], "uv": [0, 212]},
{"origin": [-17, 34, -0.5], "size": [34, 7, 1], "inflate": 0.01, "pivot": [0, 37.5, 0], "rotation": [0, -90, 0], "uv": [177, 145]},
{"origin": [-17, 34, -0.5], "size": [34, 7, 1], "inflate": 0.01, "uv": [177, 154]}
]
},
{
"name": "fire_waist_base",
"parent": "skirt",
"pivot": [0, 34.05, 0],
"cubes": [
{"origin": [-12, 34.05, -12], "size": [24, 0, 24], "uv": [244, 1]}
]
},
{
"name": "fire_waist",
"parent": "skirt",
"pivot": [0, 38.25, 0]
},
{
"name": "fire_waist_interior",
"parent": "fire_waist",
"pivot": [0, 38.25, 0],
"cubes": [
{"origin": [-16, 4.25, 0], "size": [32, 34, 0], "pivot": [0, 32.25, 0], "rotation": [0, 45, 0], "uv": [447, 35]}
]
},
{
"name": "fire_waist_interior2",
"parent": "fire_waist",
"pivot": [0, 38.25, 0],
"cubes": [
{"origin": [0, 4.25, -16], "size": [0, 34, 32], "pivot": [0, 32.25, 0], "rotation": [0, 45, 0], "uv": [447, -31]}
]
},
{
"name": "fire_waist_front",
"parent": "fire_waist",
"pivot": [0, 34, -12],
"rotation": [-10, 0, 0],
"cubes": [
{"origin": [-16, 8.25, -12], "size": [32, 30, 0], "uv": [317, 1]}
]
},
{
"name": "fire_waist_back",
"parent": "fire_waist",
"pivot": [0, 34, 12],
"rotation": [10, 0, 0],
"cubes": [
{"origin": [-16, 8.25, 12], "size": [32, 30, 0], "uv": [317, 32]}
]
},
{
"name": "fire_waist_right",
"parent": "fire_waist",
"pivot": [-12, 34, 0],
"rotation": [0, 0, 10],
"cubes": [
{"origin": [-12, 8.25, -16], "size": [0, 30, 32], "uv": [382, -31]}
]
},
{
"name": "fire_waist_left",
"parent": "fire_waist",
"pivot": [12, 34, 0],
"rotation": [0, 0, -10],
"cubes": [
{"origin": [12, 8.25, -16], "size": [0, 30, 32], "uv": [382, 0]}
]
},
{
"name": "bandage",
"parent": "torso",
"pivot": [-3.8, 64.6, -19],
"rotation": [0, 0, 45],
"cubes": [
{"origin": [-11.8, 60.6, -20], "size": [16, 8, 2], "uv": [119, 132]}
]
},
{
"name": "head",
"parent": "torso",
"pivot": [0.5, 46, 0.5],
"pivot": [0, 76, 0],
"cubes": [
{"origin": [-3, 44.75, -3], "size": [6, 6, 6], "uv": [83, 99]},
{"origin": [-4.5, 49.5, 0.5], "size": [6, 5, 0], "pivot": [1, 50.5, 0.5], "rotation": [0, 0, 10], "uv": [47, 63]}
{"origin": [-5, 74, -5], "size": [10, 10, 10], "uv": [136, 142]},
{"origin": [-1.5, 82, -1], "size": [4, 5, 2], "pivot": [1, 84.5, 0.5], "rotation": [0, 0, 10], "uv": [19, 20]},
{"origin": [-5.5, 84, 0], "size": [4, 3, 0], "pivot": [1, 84.5, 0.5], "rotation": [0, 0, 10], "uv": [26, 27]}
]
},
{
"name": "eyes",
"parent": "head",
"pivot": [0, 80, -5.01]
},
{
"name": "eye_left",
"parent": "head",
"pivot": [1.4, 47.5, -2.51],
"parent": "eyes",
"pivot": [2, 80, -5.01],
"cubes": [
{"origin": [0.65, 46.5, -3.01], "size": [1, 3, 1], "uv": [15, 12]},
{"origin": [1.15, 46.5, -3.01], "size": [1, 1, 1], "uv": [4, 18]}
]
},
{
"name": "eyelid_left",
"parent": "eye_left",
"pivot": [1.4, 47.5, -2.41],
"cubes": [
{"origin": [0.65, 46.5, -2.91], "size": [1, 3, 1], "inflate": 0.001, "uv": [15, 0]},
{"origin": [1.15, 46.5, -2.91], "size": [1, 1, 1], "inflate": 0.001, "uv": [0, 18]}
{"origin": [1, 78, -5.01], "size": [2, 4, 0], "uv": [32, 0]},
{"origin": [3, 78, -5.01], "size": [1, 2, 0], "uv": [24, 2]}
]
},
{
"name": "eye_right",
"parent": "head",
"pivot": [-1.65, 48.25, -2.51],
"parent": "eyes",
"pivot": [-2, 80, -5.01],
"cubes": [
{"origin": [-1.65, 46.5, -3.01], "size": [1, 3, 1], "uv": [0, 12]}
{"origin": [-3, 78, -5.01], "size": [2, 4, 0], "uv": [28, 0]}
]
},
{
"name": "eyelid_right",
"parent": "eye_right",
"pivot": [-1.15, 48, -2.41],
"name": "eyes_red",
"parent": "head",
"pivot": [0, 80, -4.91]
},
{
"name": "eye_red_left",
"parent": "eyes_red",
"pivot": [2, 80, -4.91],
"cubes": [
{"origin": [-1.65, 46.5, -2.91], "size": [1, 3, 1], "inflate": 0.001, "uv": [0, 0]}
{"origin": [1, 78, -4.91], "size": [2, 4, 0], "uv": [32, 4]},
{"origin": [3, 78, -4.91], "size": [1, 2, 0], "uv": [2, 2]}
]
},
{
"name": "eye_red_right",
"parent": "eyes_red",
"pivot": [-2, 80, -4.91],
"cubes": [
{"origin": [-3, 78, -4.91], "size": [2, 4, 0], "uv": [28, 4]}
]
},
{
"name": "eyes_off",
"parent": "head",
"pivot": [0, 80, -4.91]
},
{
"name": "eye_off_left",
"parent": "eyes_off",
"pivot": [2, 80, -4.91],
"cubes": [
{"origin": [1, 78, -4.91], "size": [2, 4, 0], "uv": [32, 8]},
{"origin": [3, 78, -4.91], "size": [1, 2, 0], "uv": [2, 22]}
]
},
{
"name": "eye_off_right",
"parent": "eyes_off",
"pivot": [-2, 80, -4.91],
"cubes": [
{"origin": [-3, 78, -4.91], "size": [2, 4, 0], "uv": [28, 8]}
]
},
{
"name": "shoulder_left",
"parent": "torso",
"pivot": [25.6, 67, 0],
"cubes": [
{"origin": [19.1, 76.4, -2.5], "size": [9, 5, 5], "pivot": [25.6, 68.4, 0], "rotation": [0, 0, 45], "uv": [102, 236]},
{"origin": [22.1, 81.4, -2.5], "size": [6, 5, 5], "pivot": [25.6, 68.4, 0], "rotation": [0, 0, 45], "uv": [177, 132]},
{"origin": [16.1, 59.4, -8.5], "size": [17, 17, 17], "pivot": [25.6, 68.4, 0], "rotation": [0, 0, 45], "uv": [68, 132]},
{"origin": [19.1, 62.4, -10.5], "size": [11, 11, 2], "pivot": [25.6, 68.4, -2], "rotation": [0, 0, 45], "uv": [0, 109]},
{"origin": [19.1, 62.4, 8.5], "size": [11, 11, 2], "pivot": [25.6, 68.4, 17], "rotation": [0, 0, 45], "uv": [46, 191]}
]
},
{
"name": "shoulder_glow_red_left",
"parent": "shoulder_left",
"pivot": [25.6, 68.4, -10.4],
"cubes": [
{"origin": [20.1, 63.4, -10.41], "size": [9, 9, 0], "pivot": [25.6, 68.4, -1.9], "rotation": [0, 0, 45], "uv": [200, 122]}
]
},
{
"name": "shoulder_glow_off_left",
"parent": "shoulder_left",
"pivot": [25.6, 68.4, -10.4],
"cubes": [
{"origin": [20.1, 63.4, -10.41], "size": [9, 9, 0], "pivot": [25.6, 68.4, -1.9], "rotation": [0, 0, 45], "uv": [200, 131]}
]
},
{
"name": "arm_left",
"parent": "torso",
"pivot": [13.6, 44, 0]
"parent": "shoulder_left",
"pivot": [24.1, 59.4, 0],
"cubes": [
{"origin": [20.1, 50.4, -3], "size": [8, 14, 6], "uv": [108, 0]}
]
},
{
"name": "arm_left2",
"name": "forearm_left",
"parent": "arm_left",
"pivot": [16.1, 43.9, 0],
"pivot": [24.1, 52.4, 0],
"cubes": [
{"origin": [11.1, 40.9, 4.5], "size": [7, 7, 2], "pivot": [13.6, 45.4, -1], "rotation": [0, 0, 45], "uv": [107, 17]},
{"origin": [4.6, 41.4, -2.5], "size": [5, 5, 5], "pivot": [13.6, 45.4, 0], "rotation": [0, 0, 45], "uv": [102, 106]},
{"origin": [11.1, 40.9, -6.5], "size": [7, 7, 2], "pivot": [13.6, 45.4, -1], "rotation": [0, 0, 45], "uv": [29, 63]},
{"origin": [9.6, 39.4, -4.5], "size": [10, 10, 9], "pivot": [13.6, 45.4, 0], "rotation": [0, 0, 45], "uv": [0, 63]},
{"origin": [16.6, 41.4, -2.5], "size": [9, 5, 5], "uv": [99, 42]}
{"origin": [16.6, 47.4, -8], "size": [15, 5, 16], "uv": [62, 191]},
{"origin": [13.6, 39.9, -10], "size": [21, 8, 20], "uv": [0, 68]}
]
},
{
"name": "fire_hand_base_left",
"parent": "forearm_left",
"pivot": [24.1, 39.95, 0],
"cubes": [
{"origin": [16.1, 39.95, -8], "size": [16, 0, 16], "uv": [326, 124], "mirror": true}
]
},
{
"name": "fire_hand_left",
"parent": "forearm_left",
"pivot": [24.1, 42.15, 0]
},
{
"name": "fire_hand_interior_left1",
"parent": "fire_hand_left",
"pivot": [24.1, 44.15, 0],
"cubes": [
{"origin": [12.6, 17.15, 0], "size": [23, 25, 0], "pivot": [24.1, 38.15, 0], "rotation": [0, -45, 0], "uv": [465, 150], "mirror": true}
]
},
{
"name": "fire_hand_interior_left2",
"parent": "fire_hand_left",
"pivot": [24.1, 44.15, 0],
"cubes": [
{"origin": [24.1, 17.15, -11.5], "size": [0, 25, 23], "pivot": [24.1, 38.15, 0], "rotation": [0, -45, 0], "uv": [465, 101], "mirror": true}
]
},
{
"name": "fire_hand_front_left",
"parent": "fire_hand_left",
"pivot": [24.1, 39.9, -8],
"rotation": [-10, 0, 0],
"cubes": [
{"origin": [13.1, 20.15, -8], "size": [22, 22, 0], "uv": [375, 124], "mirror": true}
]
},
{
"name": "fire_hand_back_left",
"parent": "fire_hand_left",
"pivot": [24.1, 39.9, 8],
"rotation": [10, 0, 0],
"cubes": [
{"origin": [13.1, 20.15, 8], "size": [22, 22, 0], "uv": [375, 147], "mirror": true}
]
},
{
"name": "fire_hand_outer_left",
"parent": "fire_hand_left",
"pivot": [32.1, 39.9, 0],
"rotation": [0, 0, -10],
"cubes": [
{"origin": [32.1, 20.15, -11], "size": [0, 22, 22], "uv": [420, 102], "mirror": true}
]
},
{
"name": "fire_hand_inner_left",
"parent": "fire_hand_left",
"pivot": [16.1, 39.9, 0],
"rotation": [0, 0, 10],
"cubes": [
{"origin": [16.1, 20.15, -11], "size": [0, 22, 22], "uv": [420, 125], "mirror": true}
]
},
{
"name": "hand_left",
"parent": "arm_left2",
"pivot": [24.85, 44, 0],
"parent": "forearm_left",
"pivot": [24.1, 39.9, 0],
"cubes": [
{"origin": [26.6, 38.4, -5.5], "size": [5, 11, 11], "uv": [38, 68]},
{"origin": [23.6, 39.4, -4.5], "size": [3, 9, 9], "uv": [0, 96]}
{"origin": [19.6, 27.9, -8], "size": [9, 12, 16], "inflate": 0.02, "uv": [186, 0]},
{"origin": [27.6, 28.9, -3.3], "size": [3, 9, 9], "pivot": [26.1, 34.9, 0.7], "rotation": [-45, 0, 0], "uv": [102, 218]}
]
},
{
"name": "hand_left2",
"name": "hand_glow_red_left",
"parent": "hand_left",
"pivot": [31.85, 42.5, 0],
"pivot": [30.5, 34.9, 0.7],
"cubes": [
{"origin": [32.25, 46.9, -2.75], "size": [5, 1, 5], "pivot": [34.5, 47.4, 0], "rotation": [0, -45, 0], "uv": [101, 99]},
{"origin": [31.1, 40.9, -4.5], "size": [7, 6, 9], "uv": [27, 90]}
{"origin": [30.51, 29.9, -2.3], "size": [0, 7, 7], "pivot": [26, 34.9, 0.7], "rotation": [-45, 0, 0], "uv": [238, 99]}
]
},
{
"name": "finger_left",
"parent": "hand_left2",
"pivot": [38.1, 46.4, -3.5],
"name": "hand_glow_off_left",
"parent": "hand_left",
"pivot": [30.5, 34.9, 0.7],
"cubes": [
{"origin": [38.1, 44.4, -4.5], "size": [3, 2, 2], "inflate": 0.001, "uv": [92, 32]}
{"origin": [30.51, 29.9, -2.3], "size": [0, 7, 7], "pivot": [26, 34.9, 0.7], "rotation": [-45, 0, 0], "uv": [238, 106]}
]
},
{
"name": "finger_left2",
"parent": "finger_left",
"pivot": [41.1, 46.4, -3.5],
"name": "finger_thumb_left",
"parent": "hand_left",
"pivot": [19.6, 34.9, -8],
"rotation": [-12.5, 0, 25],
"cubes": [
{"origin": [41.1, 44.4, -4.5], "size": [2, 2, 2], "uv": [60, 94]}
{"origin": [16.6, 29.9, -10.5], "size": [6, 9, 7], "uv": [108, 20]}
]
},
{
"name": "finger_left3",
"parent": "hand_left2",
"pivot": [38.1, 46.4, -1.25],
"name": "finger_thumb_left2",
"parent": "finger_thumb_left",
"pivot": [19.1, 29.9, -10.5],
"cubes": [
{"origin": [38.1, 44.4, -2.25], "size": [3, 2, 2], "inflate": 0.001, "uv": [86, 84]}
{"origin": [17.1, 23.9, -10.5], "size": [5, 6, 5], "uv": [0, 68]}
]
},
{
"name": "finger_left4",
"parent": "finger_left3",
"pivot": [41.1, 46.4, -1.25],
"name": "finger_index_left",
"parent": "hand_left",
"pivot": [26.6, 27.9, -6],
"cubes": [
{"origin": [41.1, 44.4, -2.25], "size": [3, 2, 2], "uv": [27, 86]}
{"origin": [21.6, 21.9, -8], "size": [6, 6, 4], "inflate": 0.015, "uv": [132, 122]}
]
},
{
"name": "finger_left5",
"parent": "hand_left2",
"pivot": [38.1, 46.4, 1],
"name": "finger_index_left2",
"parent": "finger_index_left",
"pivot": [27.1, 21.9, -6],
"cubes": [
{"origin": [38.1, 44.4, 0], "size": [3, 2, 2], "inflate": 0.001, "uv": [27, 82]}
{"origin": [22.1, 15.9, -8], "size": [5, 6, 4], "uv": [154, 96]}
]
},
{
"name": "finger_left6",
"parent": "finger_left5",
"pivot": [41.1, 46.4, 1],
"name": "finger_middle_left",
"parent": "hand_left",
"pivot": [26.6, 27.9, -2],
"cubes": [
{"origin": [41.1, 44.4, 0], "size": [2, 2, 2], "uv": [90, 36]}
{"origin": [21.6, 21.9, -4], "size": [6, 6, 4], "inflate": 0.01, "uv": [221, 29]}
]
},
{
"name": "finger_left7",
"parent": "hand_left2",
"pivot": [38.1, 46.4, 3.25],
"name": "finger_middle_left2",
"parent": "finger_middle_left",
"pivot": [27.1, 21.9, -2],
"cubes": [
{"origin": [38.1, 44.4, 2.25], "size": [2, 2, 2], "inflate": 0.001, "uv": [0, 86]}
{"origin": [22.1, 15.9, -4], "size": [5, 6, 4], "uv": [62, 78]}
]
},
{
"name": "finger_left8",
"parent": "finger_left7",
"pivot": [40.1, 46.4, 3.25],
"name": "finger_ring_left",
"parent": "hand_left",
"pivot": [26.6, 27.9, 2],
"cubes": [
{"origin": [40.1, 44.4, 2.25], "size": [2, 2, 2], "uv": [0, 82]}
{"origin": [21.6, 21.9, 0], "size": [6, 6, 4], "inflate": 0.015, "uv": [172, 122]}
]
},
{
"name": "finger_left9",
"parent": "hand_left2",
"pivot": [35.6, 43.15, -3.5],
"name": "finger_ring_left2",
"parent": "finger_ring_left",
"pivot": [27.1, 21.9, 2],
"cubes": [
{"origin": [35.1, 41.65, -5.5], "size": [3, 3, 3], "inflate": 0.001, "uv": [50, 90]}
{"origin": [22.1, 15.9, 0], "size": [5, 6, 4], "uv": [62, 68]}
]
},
{
"name": "finger_left10",
"parent": "finger_left9",
"pivot": [38.1, 43.15, -5.5],
"name": "finger_pinky_left",
"parent": "hand_left",
"pivot": [26.6, 27.9, 6],
"cubes": [
{"origin": [38.1, 41.65, -5.5], "size": [3, 3, 3], "uv": [89, 0]}
{"origin": [21.6, 21.9, 4], "size": [6, 6, 4], "inflate": 0.01, "uv": [226, 96]}
]
},
{
"name": "finger_pinky_left2",
"parent": "finger_pinky_left",
"pivot": [27.1, 21.9, 6],
"cubes": [
{"origin": [22.1, 16.9, 4], "size": [5, 5, 4], "uv": [2, 79]}
]
},
{
"name": "hand_fire_left",
"parent": "forearm_left",
"pivot": [24, 40.25, 0]
},
{
"name": "shoulder_right",
"parent": "torso",
"pivot": [-25.6, 67, 0],
"cubes": [
{"origin": [-28.1, 76.4, -2.5], "size": [9, 5, 5], "pivot": [-25.6, 68.4, 0], "rotation": [0, 0, -45], "uv": [0, 236]},
{"origin": [-28.1, 81.4, -2.5], "size": [6, 5, 5], "pivot": [-25.6, 68.4, 0], "rotation": [0, 0, -45], "uv": [155, 132]},
{"origin": [-33.1, 59.4, -8.5], "size": [17, 17, 17], "pivot": [-25.6, 68.4, 0], "rotation": [0, 0, -45], "uv": [0, 132]},
{"origin": [-30.1, 62.4, -10.5], "size": [11, 11, 2], "pivot": [-25.6, 68.4, -2], "rotation": [0, 0, -45], "uv": [0, 96]},
{"origin": [-30.1, 62.4, 8.5], "size": [11, 11, 2], "pivot": [-25.6, 68.4, 17], "rotation": [0, 0, -45], "uv": [51, 132]}
]
},
{
"name": "shoulder_glow_red_right",
"parent": "shoulder_right",
"pivot": [-25.6, 68.4, -10.4],
"cubes": [
{"origin": [-29.1, 63.4, -10.41], "size": [9, 9, 0], "pivot": [-25.6, 68.4, -1.9], "rotation": [0, 0, -45], "uv": [218, 122]}
]
},
{
"name": "shoulder_glow_off_right",
"parent": "shoulder_right",
"pivot": [-25.6, 68.4, -10.4],
"cubes": [
{"origin": [-29.1, 63.4, -10.41], "size": [9, 9, 0], "pivot": [-25.6, 68.4, -1.9], "rotation": [0, 0, -45], "uv": [218, 131]}
]
},
{
"name": "arm_right",
"parent": "torso",
"pivot": [-13.6, 44, 0]
"parent": "shoulder_right",
"pivot": [-24.1, 59.4, 0],
"cubes": [
{"origin": [-28.1, 50.4, -3], "size": [8, 14, 6], "uv": [0, 0]}
]
},
{
"name": "arm_right2",
"name": "forearm_right",
"parent": "arm_right",
"pivot": [-16.1, 43.9, 0],
"pivot": [-24.1, 52.4, 0],
"cubes": [
{"origin": [-25.6, 41.4, -2.5], "size": [9, 5, 5], "uv": [99, 32]},
{"origin": [-19.6, 39.4, -4.5], "size": [10, 10, 9], "pivot": [-13.6, 45.4, 0], "rotation": [0, 0, -45], "uv": [60, 0]},
{"origin": [-18.1, 40.9, -6.5], "size": [7, 7, 2], "pivot": [-13.6, 45.4, -1], "rotation": [0, 0, -45], "uv": [0, 40]},
{"origin": [-9.6, 41.4, -2.5], "size": [5, 5, 5], "pivot": [-13.6, 45.4, 0], "rotation": [0, 0, -45], "uv": [44, 105]},
{"origin": [-18.1, 40.9, 4.5], "size": [7, 7, 2], "pivot": [-13.6, 45.4, -1], "rotation": [0, 0, -45], "uv": [0, 49]}
{"origin": [-31.6, 47.4, -8], "size": [15, 5, 16], "uv": [0, 191]},
{"origin": [-34.6, 39.9, -10], "size": [21, 8, 20], "uv": [82, 68]}
]
},
{
"name": "fire_hand_base_right",
"parent": "forearm_right",
"pivot": [-24.1, 39.95, 0],
"cubes": [
{"origin": [-32.1, 39.95, -8], "size": [16, 0, 16], "uv": [326, 71]}
]
},
{
"name": "fire_hand_right",
"parent": "forearm_right",
"pivot": [-24.1, 42.15, 0]
},
{
"name": "fire_hand_interior_right1",
"parent": "fire_hand_right",
"pivot": [-24.1, 44.15, 0],
"cubes": [
{"origin": [-35.6, 17.15, 0], "size": [23, 25, 0], "pivot": [-24.1, 38.15, 0], "rotation": [0, 45, 0], "uv": [465, 97]}
]
},
{
"name": "fire_hand_interior_right2",
"parent": "fire_hand_right",
"pivot": [-24.1, 44.15, 0],
"cubes": [
{"origin": [-24.1, 17.15, -11.5], "size": [0, 25, 23], "pivot": [-24.1, 38.15, 0], "rotation": [0, 45, 0], "uv": [465, 48]}
]
},
{
"name": "fire_hand_front_right",
"parent": "fire_hand_right",
"pivot": [-24.1, 39.9, -8],
"rotation": [-10, 0, 0],
"cubes": [
{"origin": [-35.1, 20.15, -8], "size": [22, 22, 0], "uv": [375, 71]}
]
},
{
"name": "fire_hand_back_right",
"parent": "fire_hand_right",
"pivot": [-24.1, 39.9, 8],
"rotation": [10, 0, 0],
"cubes": [
{"origin": [-35.1, 20.15, 8], "size": [22, 22, 0], "uv": [375, 94]}
]
},
{
"name": "fire_hand_outer_right",
"parent": "fire_hand_right",
"pivot": [-32.1, 39.9, 0],
"rotation": [0, 0, 10],
"cubes": [
{"origin": [-32.1, 20.15, -11], "size": [0, 22, 22], "uv": [420, 49]}
]
},
{
"name": "fire_hand_inner_right",
"parent": "fire_hand_right",
"pivot": [-16.1, 39.9, 0],
"rotation": [0, 0, -10],
"cubes": [
{"origin": [-16.1, 20.15, -11], "size": [0, 22, 22], "uv": [420, 72]}
]
},
{
"name": "hand_right",
"parent": "arm_right2",
"pivot": [-24.85, 44, 0],
"parent": "forearm_right",
"pivot": [-24.1, 39.9, 0],
"cubes": [
{"origin": [-31.6, 38.4, -5.5], "size": [5, 11, 11], "uv": [61, 57]},
{"origin": [-26.6, 39.4, -4.5], "size": [3, 9, 9], "uv": [59, 92]}
{"origin": [-28.6, 27.9, -8], "size": [9, 12, 16], "inflate": 0.02, "uv": [136, 0]},
{"origin": [-30.6, 28.9, -3.3], "size": [3, 9, 9], "pivot": [-26.1, 34.9, 0.7], "rotation": [-45, 0, 0], "uv": [0, 218]}
]
},
{
"name": "hand_right2",
"name": "hand_glow_red_right",
"parent": "hand_right",
"pivot": [-31.85, 42.5, 0],
"pivot": [-30.5, 34.9, 0.7],
"cubes": [
{"origin": [-37.25, 46.9, -2.75], "size": [5, 1, 5], "pivot": [-34.5, 47.4, 0], "rotation": [0, 45, 0], "uv": [0, 12]},
{"origin": [-38.1, 40.9, -4.5], "size": [7, 6, 9], "uv": [88, 84]}
{"origin": [-30.51, 29.9, -2.3], "size": [0, 7, 7], "pivot": [-26, 34.9, 0.7], "rotation": [-45, 0, 0], "uv": [224, 99]}
]
},
{
"name": "finger_right",
"parent": "hand_right2",
"pivot": [-38.1, 46.4, -3.5],
"name": "hand_glow_off_right",
"parent": "hand_right",
"pivot": [-30.5, 34.9, 0.7],
"cubes": [
{"origin": [-41.1, 44.4, -4.5], "size": [3, 2, 2], "inflate": 0.001, "uv": [80, 23]}
{"origin": [-30.51, 29.9, -2.3], "size": [0, 7, 7], "pivot": [-26, 34.9, 0.7], "rotation": [-45, 0, 0], "uv": [224, 106]}
]
},
{
"name": "finger_right2",
"parent": "finger_right",
"pivot": [-41.1, 46.4, -3.5],
"name": "finger_thumb_right",
"parent": "hand_right",
"pivot": [-19.6, 34.9, -8],
"rotation": [-12.5, 0, -25],
"cubes": [
{"origin": [-43.1, 44.4, -4.5], "size": [2, 2, 2], "uv": [0, 67]}
{"origin": [-22.6, 29.9, -10.5], "size": [6, 9, 7], "uv": [0, 20]}
]
},
{
"name": "finger_right3",
"parent": "hand_right2",
"pivot": [-38.1, 46.4, -1.25],
"name": "finger_thumb_right2",
"parent": "finger_thumb_right",
"pivot": [-19.1, 29.9, -10.5],
"cubes": [
{"origin": [-41.1, 44.4, -2.25], "size": [3, 2, 2], "inflate": 0.001, "uv": [80, 19]}
{"origin": [-22.1, 23.9, -10.5], "size": [5, 6, 5], "uv": [82, 68]}
]
},
{
"name": "finger_right4",
"parent": "finger_right3",
"pivot": [-41.1, 46.4, -1.25],
"name": "finger_index_right",
"parent": "hand_right",
"pivot": [-26.6, 27.9, -6],
"cubes": [
{"origin": [-44.1, 44.4, -2.25], "size": [3, 2, 2], "uv": [38, 72]}
{"origin": [-27.6, 21.9, -8], "size": [6, 6, 4], "inflate": 0.015, "uv": [112, 122]}
]
},
{
"name": "finger_right5",
"parent": "hand_right2",
"pivot": [-38.1, 46.4, 1],
"name": "finger_index_right2",
"parent": "finger_index_right",
"pivot": [-27.1, 21.9, -6],
"cubes": [
{"origin": [-41.1, 44.4, 0], "size": [3, 2, 2], "inflate": 0.001, "uv": [59, 63]}
{"origin": [-27.1, 15.9, -8], "size": [5, 6, 4], "uv": [136, 96]}
]
},
{
"name": "finger_right6",
"parent": "finger_right5",
"pivot": [-41.1, 46.4, 1],
"name": "finger_middle_right",
"parent": "hand_right",
"pivot": [-26.6, 27.9, -2],
"cubes": [
{"origin": [-43.1, 44.4, 0], "size": [2, 2, 2], "uv": [0, 63]}
{"origin": [-27.6, 21.9, -4], "size": [6, 6, 4], "inflate": 0.01, "uv": [177, 29]}
]
},
{
"name": "finger_right7",
"parent": "hand_right2",
"pivot": [-38.1, 46.4, 3.25],
"name": "finger_middle_right2",
"parent": "finger_middle_right",
"pivot": [-27.1, 21.9, -2],
"cubes": [
{"origin": [-40.1, 44.4, 2.25], "size": [2, 2, 2], "inflate": 0.001, "uv": [60, 4]}
{"origin": [-27.1, 15.9, -4], "size": [5, 6, 4], "uv": [144, 78]}
]
},
{
"name": "finger_right8",
"parent": "finger_right7",
"pivot": [-40.1, 46.4, 3.25],
"name": "finger_ring_right",
"parent": "hand_right",
"pivot": [-26.6, 27.9, 2],
"cubes": [
{"origin": [-42.1, 44.4, 2.25], "size": [2, 2, 2], "uv": [60, 0]}
{"origin": [-27.6, 21.9, 0], "size": [6, 6, 4], "inflate": 0.015, "uv": [152, 122]}
]
},
{
"name": "finger_right9",
"parent": "hand_right2",
"pivot": [-35.6, 43.15, -3.5],
"name": "finger_ring_right2",
"parent": "finger_ring_right",
"pivot": [-27.1, 21.9, 2],
"cubes": [
{"origin": [-38.1, 41.65, -5.5], "size": [3, 3, 3], "inflate": 0.001, "uv": [80, 32]}
{"origin": [-27.1, 15.9, 0], "size": [5, 6, 4], "uv": [144, 68]}
]
},
{
"name": "finger_right10",
"parent": "finger_right9",
"pivot": [-38.1, 43.15, -5.5],
"name": "finger_pinky_right",
"parent": "hand_right",
"pivot": [-26.6, 27.9, 6],
"cubes": [
{"origin": [-41.1, 41.65, -5.5], "size": [3, 3, 3], "uv": [54, 49]}
{"origin": [-27.6, 21.9, 4], "size": [6, 6, 4], "inflate": 0.01, "uv": [206, 96]}
]
},
{
"name": "finger_pinky_right2",
"parent": "finger_pinky_right",
"pivot": [-27.1, 21.9, 6],
"cubes": [
{"origin": [-27.1, 16.9, 4], "size": [5, 5, 4], "uv": [84, 79]}
]
},
{
"name": "hand_fire_right",
"parent": "forearm_right",
"pivot": [-24, 40.25, 0]
},
{
"name": "seat_1_locator",
"parent": "torso",
"pivot": [0, 77, 9],
"locators": {
"seat_1": [0, 77, 10]
}
},
{
"name": "seat_2_locator",
"parent": "torso",
"pivot": [-14, 77, 13],
"locators": {
"seat_2": [-14, 77, 13]
}
},
{
"name": "seat_3_locator",
"parent": "torso",
"pivot": [14, 77, 13],
"locators": {
"seat_3": [14, 77, 13]
}
},
{
"name": "legs",
"parent": "shoulder_pivot",
"pivot": [0, 37, 0]
},
{
"name": "leg_left",
"parent": "body",
"pivot": [5, 20, 0],
"parent": "legs",
"pivot": [9.5, 37, 0],
"cubes": [
{"origin": [2.5, 14, -2.5], "size": [5, 7, 5], "uv": [24, 105]}
{"origin": [4, 27, -5.5], "size": [11, 11, 11], "uv": [188, 28]}
]
},
{
"name": "knee_left",
"parent": "leg_left",
"pivot": [9.5, 28, -0.25],
"cubes": [
{"origin": [6, 23, -3.5], "size": [7, 7, 7], "uv": [172, 50]}
]
},
{
"name": "leg_left2",
"parent": "leg_left",
"pivot": [5, 15, -0.25],
"parent": "knee_left",
"pivot": [9.5, 22.5, 0],
"cubes": [
{"origin": [3.5, 10, -2.25], "size": [3, 5, 4], "uv": [74, 92]}
{"origin": [3, 16, -6.5], "size": [13, 8, 13], "uv": [201, 56]},
{"origin": [0, 8, -8.5], "size": [19, 8, 17], "uv": [72, 166]}
]
},
{
"name": "foot_left",
"parent": "leg_left2",
"pivot": [5, 9.5, 0],
"pivot": [9.5, 8, -2],
"cubes": [
{"origin": [1.5, 7, -3.5], "size": [7, 4, 7], "uv": [98, 0]},
{"origin": [0.5, 2, -4.5], "size": [9, 5, 9], "uv": [86, 70]},
{"origin": [1, 0, -7.5], "size": [8, 2, 11], "uv": [80, 19]}
{"origin": [1, 0, -9.5], "size": [17, 8, 18], "uv": [154, 96]},
{"origin": [1, 0, -14.5], "size": [17, 6, 5], "uv": [208, 85]}
]
},
{
"name": "leg_right",
"parent": "body",
"pivot": [-5, 20, 0],
"parent": "legs",
"pivot": [-9.5, 37, 0],
"cubes": [
{"origin": [-7.5, 14, -2.5], "size": [5, 7, 5], "uv": [0, 0]}
{"origin": [-15, 27, -5.5], "size": [11, 11, 11], "uv": [144, 28]}
]
},
{
"name": "knee_right",
"parent": "leg_right",
"pivot": [-9.5, 28, -0.25],
"cubes": [
{"origin": [-13, 23, -3.5], "size": [7, 7, 7], "uv": [144, 50]}
]
},
{
"name": "leg_right2",
"parent": "leg_right",
"pivot": [-5, 15, -0.25],
"parent": "knee_right",
"pivot": [-9.5, 22.5, 0],
"cubes": [
{"origin": [-6.5, 10, -2.25], "size": [3, 5, 4], "uv": [54, 40]}
{"origin": [-16, 16, -6.5], "size": [13, 8, 13], "uv": [162, 64]},
{"origin": [-19, 8, -8.5], "size": [19, 8, 17], "uv": [0, 166]}
]
},
{
"name": "foot_right",
"parent": "leg_right2",
"pivot": [-5, 10.5, 0],
"pivot": [-9.5, 8, -2],
"cubes": [
{"origin": [-8.5, 7, -3.5], "size": [7, 4, 7], "uv": [82, 57]},
{"origin": [-9.5, 2, -4.5], "size": [9, 5, 9], "uv": [0, 82]},
{"origin": [-9, 0, -7.5], "size": [8, 2, 11], "uv": [59, 79]}
{"origin": [-18, 0, -9.5], "size": [17, 8, 18], "uv": [84, 96]},
{"origin": [-18, 0, -14.5], "size": [17, 6, 5], "uv": [164, 85]}
]
}
]
@@ -18,19 +18,32 @@
{
"name": "body",
"parent": "cyclizar",
"pivot": [0, 0, 0]
"pivot": [0, 11, 3.5]
},
{
"name": "hips",
"parent": "body",
"pivot": [0, 13.4, 5.75]
},
{
"name": "torso",
"parent": "hips",
"pivot": [0, 13.4, 5.75],
"cubes": [
{"origin": [-3, 10.75, -4], "size": [6, 7, 11], "inflate": 0.01, "uv": [40, 0]}
]
},
{
"name": "seat_1_locator",
"parent": "torso",
"pivot": [0, 17.75, 2],
"locators": {
"seat_1": [0, 17.75, 2]
}
},
{
"name": "seat",
"parent": "hips",
"parent": "torso",
"pivot": [0, 17.75, 4.5],
"rotation": [22.5, 0, 0],
"cubes": [
@@ -40,8 +53,8 @@
]
},
{
"name": "torso",
"parent": "hips",
"name": "torso2",
"parent": "torso",
"pivot": [0, 14, -3.05],
"cubes": [
{"origin": [-3.5, 10.75, -10.5], "size": [7, 7, 8], "uv": [40, 54]}
@@ -49,7 +62,7 @@
},
{
"name": "neck",
"parent": "torso",
"parent": "torso2",
"pivot": [0, 14, -9.35],
"rotation": [82.5, 0, 0],
"cubes": [
@@ -233,185 +246,9 @@
{"origin": [1.9, 20.9, -16], "size": [1, 2, 3], "inflate": 0.03, "uv": [0, 118], "mirror": true}
]
},
{
"name": "arm_left",
"parent": "torso",
"pivot": [3.25, 13.05, -4.75],
"cubes": [
{"origin": [1.75, 7.25, -6.75], "size": [3, 8, 4], "uv": [13, 116]}
]
},
{
"name": "elbow_left",
"parent": "arm_left",
"pivot": [3.25, 7.05, -4.75],
"cubes": [
{"origin": [1.75, 0.55, -6.25], "size": [3, 7, 3], "inflate": 0.25, "uv": [44, 81]}
]
},
{
"name": "hand_left",
"parent": "elbow_left",
"pivot": [3.25, 0.55, -5],
"cubes": [
{"origin": [1.25, -0.95, -7.75], "size": [4, 3, 4], "inflate": 0.01, "uv": [74, 98]}
]
},
{
"name": "fingers_left",
"parent": "hand_left",
"pivot": [3.25, 0.05, -7.5]
},
{
"name": "finger_left_left2",
"parent": "fingers_left",
"pivot": [2, 1.05, -7.75],
"rotation": [0, 22.5, 0],
"cubes": [
{"origin": [1, -0.95, -9.25], "size": [1, 2, 3], "uv": [54, 107]}
]
},
{
"name": "finger_left_left3",
"parent": "finger_left_left2",
"pivot": [1.5, 1.05, -9.25],
"cubes": [
{"origin": [1.5, -0.95, -11.25], "size": [0, 2, 2], "uv": [36, 112]}
]
},
{
"name": "middle_fingers_left",
"parent": "fingers_left",
"pivot": [3.25, 0.3, -7],
"cubes": [
{"origin": [2.25, -0.7, -8.75], "size": [2, 2, 2], "inflate": 0.25, "uv": [108, 33]}
]
},
{
"name": "middle_fingers_left2",
"parent": "middle_fingers_left",
"pivot": [3.25, 1.53333, -9.01667],
"cubes": [
{"origin": [2.25, -0.7, -10.25], "size": [2, 2, 1], "inflate": 0.26, "uv": [18, 111]}
]
},
{
"name": "middle_fingers_left3",
"parent": "middle_fingers_left2",
"pivot": [3.25, 1.05, -10.5],
"cubes": [
{"origin": [3.75, -0.95, -12.5], "size": [0, 2, 2], "uv": [40, 112]},
{"origin": [2.75, -0.95, -12.5], "size": [0, 2, 2], "uv": [112, 41]}
]
},
{
"name": "finger_left_left",
"parent": "fingers_left",
"pivot": [4.5, 1.05, -7.75],
"rotation": [0, -22.5, 0],
"cubes": [
{"origin": [4.5, -0.95, -9.25], "size": [1, 2, 3], "uv": [62, 107]}
]
},
{
"name": "finger_right_left4",
"parent": "finger_left_left",
"pivot": [5, 1.05, -9.25],
"cubes": [
{"origin": [5, -0.95, -11.25], "size": [0, 2, 2], "uv": [44, 112]}
]
},
{
"name": "arm_right",
"parent": "torso",
"pivot": [-3.25, 13.05, -4.75],
"cubes": [
{"origin": [-4.75, 7.25, -6.75], "size": [3, 8, 4], "uv": [29, 116]}
]
},
{
"name": "elbow_right",
"parent": "arm_right",
"pivot": [-3.25, 7.05, -4.75],
"cubes": [
{"origin": [-4.75, 0.55, -6.25], "size": [3, 7, 3], "inflate": 0.25, "uv": [0, 101]}
]
},
{
"name": "hand_right",
"parent": "elbow_right",
"pivot": [-3.25, 0.55, -5],
"cubes": [
{"origin": [-5.25, -0.95, -7.75], "size": [4, 3, 4], "inflate": 0.01, "uv": [100, 22]}
]
},
{
"name": "fingers_right",
"parent": "hand_right",
"pivot": [-3.25, 0.05, -7.5]
},
{
"name": "finger_left_right2",
"parent": "fingers_right",
"pivot": [-2, 1.05, -7.75],
"rotation": [0, -22.5, 0],
"cubes": [
{"origin": [-2, -0.95, -9.25], "size": [1, 2, 3], "uv": [106, 106]}
]
},
{
"name": "finger_left_right3",
"parent": "finger_left_right2",
"pivot": [-1.5, 1.05, -9.25],
"cubes": [
{"origin": [-1.5, -0.95, -11.25], "size": [0, 2, 2], "uv": [119, 93]}
]
},
{
"name": "middle_fingers_right",
"parent": "fingers_right",
"pivot": [-3.25, 0.3, -7],
"cubes": [
{"origin": [-4.25, -0.7, -8.75], "size": [2, 2, 2], "inflate": 0.25, "uv": [108, 29]}
]
},
{
"name": "middle_fingers_right2",
"parent": "middle_fingers_right",
"pivot": [-3.25, 1.53333, -9.01667],
"cubes": [
{"origin": [-4.25, -0.7, -10.25], "size": [2, 2, 1], "inflate": 0.26, "uv": [106, 66]}
]
},
{
"name": "middle_fingers_right3",
"parent": "middle_fingers_right2",
"pivot": [-3.25, 1.05, -10.5],
"cubes": [
{"origin": [-3.75, -0.95, -12.5], "size": [0, 2, 2], "uv": [28, 112]},
{"origin": [-2.75, -0.95, -12.5], "size": [0, 2, 2], "uv": [123, 93]}
]
},
{
"name": "finger_right_left",
"parent": "fingers_right",
"pivot": [-4.5, 1.05, -7.75],
"rotation": [0, 22.5, 0],
"cubes": [
{"origin": [-5.5, -0.95, -9.25], "size": [1, 2, 3], "uv": [28, 107]}
]
},
{
"name": "finger_right_left2",
"parent": "finger_right_left",
"pivot": [-5, 1.05, -9.25],
"cubes": [
{"origin": [-5, -0.95, -11.25], "size": [0, 2, 2], "uv": [32, 112]}
]
},
{
"name": "tail",
"parent": "hips",
"parent": "torso",
"pivot": [0, 14.5, 6.75],
"rotation": [17.5, 0, 0],
"cubes": [
@@ -460,13 +297,194 @@
},
{
"name": "tail_wheel",
"parent": "hips",
"parent": "torso",
"pivot": [0, 14.5, 5.75],
"rotation": [7.5, 0, 0],
"cubes": [
{"origin": [-2, 1.5, 1.75], "size": [4, 16, 16], "inflate": 0.02, "uv": [0, 0]}
]
},
{
"name": "ftorso2",
"parent": "hips",
"pivot": [0, 14, -3.05]
},
{
"name": "arm_left",
"parent": "ftorso2",
"pivot": [3.25, 13.05, -4.75],
"cubes": [
{"origin": [1.75, 7.25, -6.75], "size": [3, 8, 4], "uv": [13, 116]}
]
},
{
"name": "elbow_left",
"parent": "arm_left",
"pivot": [3.25, 7.05, -4.75],
"cubes": [
{"origin": [1.75, 0.55, -6.25], "size": [3, 7, 3], "inflate": 0.25, "uv": [44, 81]}
]
},
{
"name": "hand_left",
"parent": "elbow_left",
"pivot": [3.25, 0.55, -5],
"cubes": [
{"origin": [1.25, -0.95, -7.75], "size": [4, 3, 4], "inflate": 0.01, "uv": [74, 98]}
]
},
{
"name": "fingers_left",
"parent": "hand_left",
"pivot": [3.25, 0.05, -7.5]
},
{
"name": "finger_left_right",
"parent": "fingers_left",
"pivot": [2, 1.05, -7.75],
"rotation": [0, 22.5, 0],
"cubes": [
{"origin": [1, -0.95, -9.25], "size": [1, 2, 3], "uv": [54, 107]}
]
},
{
"name": "finger_left_right2",
"parent": "finger_left_right",
"pivot": [1.5, 1.05, -9.25],
"cubes": [
{"origin": [1.5, -0.95, -11.25], "size": [0, 2, 2], "uv": [36, 112]}
]
},
{
"name": "middle_fingers_left",
"parent": "fingers_left",
"pivot": [3.25, 0.3, -7],
"cubes": [
{"origin": [2.25, -0.7, -8.75], "size": [2, 2, 2], "inflate": 0.25, "uv": [108, 33]}
]
},
{
"name": "middle_fingers_left2",
"parent": "middle_fingers_left",
"pivot": [3.25, 1.53333, -9.01667],
"cubes": [
{"origin": [2.25, -0.7, -10.25], "size": [2, 2, 1], "inflate": 0.26, "uv": [18, 111]}
]
},
{
"name": "middle_fingers_left3",
"parent": "middle_fingers_left2",
"pivot": [3.25, 1.05, -10.5],
"cubes": [
{"origin": [3.75, -0.95, -12.5], "size": [0, 2, 2], "uv": [40, 112]},
{"origin": [2.75, -0.95, -12.5], "size": [0, 2, 2], "uv": [112, 41]}
]
},
{
"name": "finger_left_left",
"parent": "fingers_left",
"pivot": [4.5, 1.05, -7.75],
"rotation": [0, -22.5, 0],
"cubes": [
{"origin": [4.5, -0.95, -9.25], "size": [1, 2, 3], "uv": [62, 107]}
]
},
{
"name": "finger_left_left2",
"parent": "finger_left_left",
"pivot": [5, 1.05, -9.25],
"cubes": [
{"origin": [5, -0.95, -11.25], "size": [0, 2, 2], "uv": [44, 112]}
]
},
{
"name": "arm_right",
"parent": "ftorso2",
"pivot": [-3.25, 13.05, -4.75],
"cubes": [
{"origin": [-4.75, 7.25, -6.75], "size": [3, 8, 4], "uv": [29, 116]}
]
},
{
"name": "elbow_right",
"parent": "arm_right",
"pivot": [-3.25, 7.05, -4.75],
"cubes": [
{"origin": [-4.75, 0.55, -6.25], "size": [3, 7, 3], "inflate": 0.25, "uv": [0, 101]}
]
},
{
"name": "hand_right",
"parent": "elbow_right",
"pivot": [-3.25, 0.55, -5],
"cubes": [
{"origin": [-5.25, -0.95, -7.75], "size": [4, 3, 4], "inflate": 0.01, "uv": [100, 22]}
]
},
{
"name": "fingers_right",
"parent": "hand_right",
"pivot": [-3.25, 0.05, -7.5]
},
{
"name": "finger_right_left",
"parent": "fingers_right",
"pivot": [-2, 1.05, -7.75],
"rotation": [0, -22.5, 0],
"cubes": [
{"origin": [-2, -0.95, -9.25], "size": [1, 2, 3], "uv": [106, 106]}
]
},
{
"name": "finger_right_left2",
"parent": "finger_right_left",
"pivot": [-1.5, 1.05, -9.25],
"cubes": [
{"origin": [-1.5, -0.95, -11.25], "size": [0, 2, 2], "uv": [119, 93]}
]
},
{
"name": "middle_fingers_right",
"parent": "fingers_right",
"pivot": [-3.25, 0.3, -7],
"cubes": [
{"origin": [-4.25, -0.7, -8.75], "size": [2, 2, 2], "inflate": 0.25, "uv": [108, 29]}
]
},
{
"name": "middle_fingers_right2",
"parent": "middle_fingers_right",
"pivot": [-3.25, 1.53333, -9.01667],
"cubes": [
{"origin": [-4.25, -0.7, -10.25], "size": [2, 2, 1], "inflate": 0.26, "uv": [106, 66]}
]
},
{
"name": "middle_fingers_right3",
"parent": "middle_fingers_right2",
"pivot": [-3.25, 1.05, -10.5],
"cubes": [
{"origin": [-3.75, -0.95, -12.5], "size": [0, 2, 2], "uv": [28, 112]},
{"origin": [-2.75, -0.95, -12.5], "size": [0, 2, 2], "uv": [123, 93]}
]
},
{
"name": "finger_right_right",
"parent": "fingers_right",
"pivot": [-4.5, 1.05, -7.75],
"rotation": [0, 22.5, 0],
"cubes": [
{"origin": [-5.5, -0.95, -9.25], "size": [1, 2, 3], "uv": [28, 107]}
]
},
{
"name": "finger_right_right2",
"parent": "finger_right_right",
"pivot": [-5, 1.05, -9.25],
"cubes": [
{"origin": [-5, -0.95, -11.25], "size": [0, 2, 2], "uv": [32, 112]}
]
},
{
"name": "leg_left_rotate",
"parent": "body",
@@ -34,7 +34,7 @@
"namedAnimations": {
"faint": "q.bedrock_primary('magikarp', 'water_faint', q.curve('one'))"
},
"animations": ["q.look('head')", "q.bedrock('magikarp', 'water_idle')"],
"animations": ["q.pitch_tilt('body', 0.22)", "q.look('head')", "q.bedrock('magikarp', 'water_idle')"],
"quirks": ["q.bedrock_quirk('magikarp', 'blink')"]
},
"swim": {
@@ -42,7 +42,7 @@
"namedAnimations": {
"faint": "q.bedrock_primary('magikarp', 'water_faint', q.curve('one'))"
},
"animations": ["q.look('head')", "q.bedrock('magikarp', 'water_swim')"],
"animations": ["q.pitch_tilt('body', 0.22)", "q.look('head')", "q.bedrock('magikarp', 'water_swim')"],
"quirks": ["q.bedrock_quirk('magikarp', 'blink')"]
},
"sleep": {
@@ -0,0 +1,93 @@
{
"portraitScale": 1.66,
"portraitTranslation": [-0.14, -0.74, 0],
"profileScale": 1,
"profileTranslation": [0.0, 0.24, 0.0],
"rootBone": "golurk",
"firstPersonCameraOffset": {
"seat_1": [0, 0.25, 0]
},
"thirdPersonCameraOffset": {
"seat_1": [0, 5, 8],
"seat_1_reverse": [0, 3, 8]
},
"animations": {
"faint": "q.bedrock_primary('golurk', 'faint', q.curve('one'))",
"cry": "q.bedrock_stateful('golurk', 'cry')",
"recoil": "q.bedrock_stateful('golurk', 'recoil')"
},
"poses": {
"battle-standing": {
"poseTypes": ["STAND"],
"isBattle": true,
"animations": ["q.look('head')", "q.bedrock('golurk', 'battle_idle')"]
},
"standing": {
"poseTypes": ["STAND", "NONE", "PORTRAIT", "PROFILE"],
"isBattle": false,
"animations": ["q.look('head')", "q.bedrock('golurk', 'ground_idle')"]
},
"walk": {
"poseName": "walk",
"poseTypes": ["WALK"],
"isBattle": false,
"condition": "!q.in_air && !q.is_sprinting",
"animations": [
"q.look('head')",
"q.bedrock('golurk', 'ground_walk')",
{
"condition": "q.is_ridden",
"animation": "q.bedrock('golurk', 'ride_ground_run')"
}
],
"transformTicks": 5,
"transformToTicks": 5
},
"run": {
"poseName": "run",
"poseTypes": ["WALK"],
"isBattle": false,
"condition": "!q.in_air && q.is_sprinting",
"animations": [
"q.look('head')",
"q.bedrock('golurk', 'ground_run')",
{
"condition": "q.is_ridden",
"animation": "q.bedrock('golurk', 'ride_ground_run')"
}
],
"transformTicks": 5,
"transformToTicks": 5
},
"hover": {
"poseTypes": ["HOVER"],
"isBattle": false,
"animations": [
"q.look('head')", "q.bedrock('golurk', 'air_idle')",
{
"condition": "q.is_ridden",
"animation": "q.bedrock('golurk', 'ride_air_idle')"
}
],
"transformTicks": 5,
"transformToTicks": 5
},
"fly": {
"poseTypes": ["FLY"],
"isBattle": false,
"animations": [
"q.look('head')", "q.bedrock('golurk', 'air_fly')",
{
"condition": "q.is_ridden",
"animation": "q.bedrock('golurk', 'ride_air_fly')"
}
],
"transformTicks": 5,
"transformToTicks": 5
},
"sleep": {
"poseTypes": ["SLEEP"],
"animations": ["q.bedrock('golurk', 'sleep')"]
}
}
}
@@ -1,41 +1,106 @@
{
"portraitScale": 1.78,
"portraitTranslation": [-0.95,0.48,0],
"profileScale": 0.76,
"profileTranslation": [0,0.54,0],
"animations": {
"cry": "q.bedrock_stateful('cyclizar', 'cry')"
"portraitScale": 1.5,
"portraitTranslation": [0, 0.5, 0],
"profileScale": 0.5,
"profileTranslation": [0, 0.25, -5],
"rootBone": "cyclizar",
"firstPersonCameraOffset": {
"seat_1": [0, 0.25, 0]
},
"poses": {
"standing": {
"poseTypes": [
"STAND",
"NONE",
"PORTRAIT",
"PROFILE"
],
"isBattle": false,
"animations": [
"q.look('head', 1, 1, 25, -45, 45, -45)",
"q.bedrock('cyclizar', 'grounded')"
],
"quirks": [
"q.bedrock_quirk('cyclizar', 'blink')"
]
},
"walking": {
"poseTypes": [
"WALK",
"SWIM"
],
"animations": [
"q.look('head', 1, 1, 25, -45, 45, -45)",
"q.bedrock('cyclizar', 'grounded')",
"q.quadruped_walk(0.66, 0.6, 'arm_left', 'arm_right', 'leg_left_rotate', 'leg_right_rotate')"
],
"quirks": [
"q.bedrock_quirk('cyclizar', 'blink')"
]
}
"thirdPersonCameraOffset": {
"seat_1": [0, 2.5, 5],
"seat_1_reverse": [0, 2.5, 5]
},
"animations": {
},
"poses": {
"portrait": {
"poseTypes": ["PORTRAIT", "PROFILE"],
"isBattle": false,
"animations": ["q.look('head')", "q.bedrock('cyclizar', 'ground_idle')"],
"quirks": ["q.bedrock_quirk('cyclizar', 'blink')"]
},
"ride_jump": {
"poseTypes": ["WALK","STAND", "FLOAT", "NONE"],
"isBattle": false,
"condition": "q.in_air && q.is_ridden",
"animations": ["q.look('head')", "q.bedrock('cyclizar', 'ride_jump')"],
"quirks": ["q.bedrock_quirk('cyclizar', 'blink')"],
"transformTicks": 5,
"transformToTicks": 5
},
"standing": {
"poseTypes": ["STAND", "NONE"],
"isBattle": false,
"animations": ["q.look('head')", "q.bedrock('cyclizar', 'ground_idle')"],
"condition": "!q.is_ridden",
"quirks": [
"q.bedrock_quirk('cyclizar', 'blink')"
]
},
"walking": {
"poseTypes": ["WALK", "SWIM"],
"animations": ["q.look('head')", "q.bedrock('cyclizar', 'ground_walk')"],
"condition": "!q.is_ridden",
"quirks": ["q.bedrock_quirk('cyclizar', 'blink')"]
},
"mount_idle": {
"poseTypes": ["STAND"],
"isBattle": false,
"animations": [
"q.look('head')",
"q.bedrock('cyclizar', 'ride_drift')"
],
"condition": "q.is_ridden && !q.in_air",
"quirks": ["q.bedrock_quirk('cyclizar', 'blink')"]
},
"mount_walk": {
"poseTypes": ["WALK"],
"isBattle": false,
"animations": [
"q.look('head')",
"q.bedrock('cyclizar', 'ride_ground_run')",
"q.bedrock('cyclizar', 'ground_run')"
],
"condition": "q.is_ridden && !q.is_sprinting && !q.in_air && !q.is_drifting && !q.is_powered_drifting",
"quirks": ["q.bedrock_quirk('cyclizar', 'blink')"],
"transformTicks": 5,
"transformToTicks": 5
},
"mount_drift": {
"poseTypes": ["WALK"],
"isBattle": false,
"animations": [
"q.look('head')",
"q.bedrock('cyclizar', 'ride_ground_run')",
"q.bedrock('cyclizar', 'ride_drift')"
],
"condition": "q.is_ridden && !q.is_sprinting && !q.in_air && q.is_drifting && !q.is_powered_drifting",
"quirks": ["q.bedrock_quirk('cyclizar', 'blink')"],
"transformTicks": 5,
"transformToTicks": 5
},
"mount_powered_drift": {
"poseTypes": ["WALK"],
"isBattle": false,
"animations": [
"q.look('head')",
"q.bedrock('cyclizar', 'ride_ground_run')",
"q.bedrock('cyclizar', 'ride_drift')",
"q.bedrock('cyclizar', 'ride_drift_legs')",
"q.bedrock('cyclizar', 'ride_drift_override')"
],
"condition": "q.is_ridden && !q.is_sprinting && !q.in_air && !q.is_drifting && q.is_powered_drifting",
"quirks": ["q.bedrock_quirk('cyclizar', 'blink')"],
"transformTicks": 5,
"transformToTicks": 5
},
"mount_run": {
"poseTypes": ["WALK"],
"isBattle": false,
"condition": "!q.in_air && q.is_sprinting",
"animations": ["q.look('head')", "q.bedrock('cyclizar', 'ground_run')"],
"quirks": ["q.bedrock_quirk('cyclizar', 'blink')"]
}
}
}
}
@@ -6,22 +6,54 @@
"aspects": [],
"poser": "cobblemon:golurk",
"model": "cobblemon:golurk.geo",
"texture": "cobblemon:textures/pokemon/0623_golurk/golurk.png",
"texture": {
"frames": [
"cobblemon:textures/pokemon/0623_golurk/golurk_1.png",
"cobblemon:textures/pokemon/0623_golurk/golurk_2.png",
"cobblemon:textures/pokemon/0623_golurk/golurk_3.png"
],
"fps": 10,
"loop": true
},
"layers": [
{
"name": "color",
"texture": "cobblemon:textures/pokemon/0623_golurk/golurk_emissive.png",
"texture": {
"frames": [
"cobblemon:textures/pokemon/0623_golurk/golurk_emissive_1.png",
"cobblemon:textures/pokemon/0623_golurk/golurk_emissive_2.png",
"cobblemon:textures/pokemon/0623_golurk/golurk_emissive_3.png"
],
"fps": 10,
"loop": true
},
"emissive": true
}
]
},
{
"aspects": ["shiny"],
"texture": "cobblemon:textures/pokemon/0623_golurk/golurk_shiny.png",
"texture": {
"frames": [
"cobblemon:textures/pokemon/0623_golurk/golurk_shiny_1.png",
"cobblemon:textures/pokemon/0623_golurk/golurk_shiny_2.png",
"cobblemon:textures/pokemon/0623_golurk/golurk_shiny_3.png"
],
"fps": 10,
"loop": true
},
"layers": [
{
"name": "color",
"texture": "cobblemon:textures/pokemon/0623_golurk/golurk_shiny_emissive.png",
"texture": {
"frames": [
"cobblemon:textures/pokemon/0623_golurk/golurk_shiny_emissive_1.png",
"cobblemon:textures/pokemon/0623_golurk/golurk_shiny_emissive_2.png",
"cobblemon:textures/pokemon/0623_golurk/golurk_shiny_emissive_3.png"
],
"fps": 10,
"loop": true
},
"emissive": true
}
]
@@ -2340,6 +2340,7 @@
"cobblemon.command.takemark": "%1$s was taken from %2$s's %3$s.",
"cobblemon.command.takemark.does_not_have": "%1$s's %2$s does not have a %3$s!",
"cobblemon.command.mark.invalid": "Invalid mark name.",
"cobblemon.command.molang.out": "MoLang output: %1$s",
"cobblemon.command.pokeheal.in_battle": "You cannot heal your party while in a battle!",
"cobblemon.command.spawnpokemonfrompool": "A %1$s has spawned in the area.",
"cobblemon.command.spawnpokemonfrompool.unable_to_spawn": "Unable to spawn a Pokémon.",
@@ -2382,7 +2383,6 @@
"cobblemon.config.ui.category.interface": "Interface",
"cobblemon.config.ui.category.riding": "Riding",
"cobblemon.config.ui.category.debug": "Debug",
"cobblemon.config.ui.category.system": "System",
"cobblemon.config.ui.max_pokemon_level": "Max Pokémon Level",
"cobblemon.config.ui.max_pokemon_level.tooltip": "The maximum level Pokémon can get to.",
@@ -2565,8 +2565,6 @@
"cobblemon.config.ui.despawner_max_age_ticks.tooltip": "The age (in game ticks) a Pokémon must reach before it can be despawned at the near distance boundary.",
"cobblemon.config.ui.enable_debug_keys": "Enable Debug Keys",
"cobblemon.config.ui.enable_debug_keys.tooltip": "Enables additional commands and chat logging to assist with implementing Pokémon (see the wiki for a list of commands).",
"cobblemon.config.ui.time_zone_id": "Time Zone Id",
"cobblemon.config.ui.time_zone_id.tooltip": "Time Zone to use for some MoLangs and IRL time zone dates. Server Side only.",
"cobblemon.config.ui.allow_starter_on_join": "Allow Starter on Join",
"cobblemon.config.ui.prompt_starter_once_only": "Prompt Starter Once Only",
@@ -1175,6 +1175,14 @@
"sounds": [
{
"name": "cobblemon:ride/loop/saucer_flight_loop",
"attenuation_distance": 64.0
}
]
},
"ride.loop.rocket": {
"sounds": [
{
"name": "cobblemon:ride/loop/rocket_flight_loop",
"attenuation_distance": 48.0
}
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 441 B

After

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 530 B

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 541 B

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 B

After

Width:  |  Height:  |  Size: 323 B

Some files were not shown because too many files have changed in this diff Show More