Compare commits

..
Author SHA1 Message Date
Polymeta d11ef0411a swiper no swap
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-04-08 15:32:35 +01:00
982 changed files with 14949 additions and 47208 deletions
-1
View File
@@ -6,7 +6,6 @@
- Added filter functionality in PC UI, which supports `PokemonProperties` (e.g. `shiny=yes` shows all shiny Pokémon).
- Added `/changewallpaper <player> <boxNumber> <wallpaper>` command to change a box wallpaper through commands.
- Added `/renamebox <player> <boxNumber> <name>` command to rename a PC box through commands.
- Added IVs and EVs stat displays in PC.
### Developer
- Added `RenamePCBoxEvent.Pre` and `RenamePCBoxEvent.Post` events to prevent players from renaming a box or changing their input.
+4 -45
View File
@@ -13,14 +13,7 @@
- Added `visibility/hidden`, `visibility/hat` and `visibility/face` tags to control where and how certain items are rendered.
- Added Pokémon markings, toggleable within the summary.
- Added `/boxcount` command to change PC boxes amount
- Added cosmetics for Gurdurr, Conkeldurr, Squirtle Line, Sneasler, Sandile line, Treecko line, Braixen, Delphox, and Dragonite.
- Added cosmetics for Gurdurr, Conkeldurr, Squirtle Line, Sneasler, Sandle line, Treecko line, Braixen, Delphox, and Dragonite.
- Added `/transformmodelpart (position|rotation|scale) <modelPart> <transform: x y z>` command that can add transformations to a pokemon's model part.
- The player executing the command must be facing the target pokemon entity. Transformations are not persistent and will revert when resources are reloaded.
- Added lang keys for all moves and abilities up to Generation 9.
- Fixed Moon Ball moon phase logic to actually work correctly
- Added `translucent_cull` boolean option into resolver's layer to allow for translucent textures with culling
- Added [LambDynamicLights](https://modrinth.com/mod/lambdynamiclights) support for items held by Pokémon.
### Pokémon Added
@@ -73,15 +66,9 @@
### Animation updates for the following Pokémon
- Garchomp
- Tropius
- Torpius
- Nosepass
- Probopass
- Sneasler
- Braixen
- Delphox
- Cinderace
- Kangaskhan
### Model updates for the following Pokémon
- Gyarados
@@ -95,44 +82,23 @@
- Leafeon
- Glaceon
- Sylveon
- Treecko
- Grovyle
- Sceptile
- Honchkrow
- Garchomp
- Pidgeot
- Nosepass
- Probopass
- Kangaskhan
- Cinderace
- Magnezone
- Metagross
- Added Syrupy Apples.
- Added `/transformmodelpart (position|rotation|scale) <modelPart> <transform: x y z>` command that can add transformations to a pokemon's model part.
- The player executing the command must be facing the target pokemon entity. Transformations are not persistent and will revert when resources are reloaded.
### Changes
- Renamed `chargeGainedPerTick` config to `secondsToChargeHealingMachine`.
- 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
- Updated the following recipes: Air Balloon, Assault Vest, Binding Band, Black Belt, Blunder Policy, Choice Band, Choice Scarf, Cleanse Tag, Covert Cloak, Destiny Knot, Eject Button, Expert Belt, Focus Band, Focus Sash, Magnet, Metronome, Muscle Band, Power Anklet, Power Band, Power Belt, Power Bracer, Power Lens, Power Weight, Protective Pads, Protein, Punching Glove, Reaper Cloth, Rocky Helmet, Room Service, Sachet, Safety Goggles, Silk Scarf, Spell Tag, Utility Umbrella, Weakness Policy, Zinc
- Added alternate ingredient options to the following recipes: Cell Battery, Charcoal Stick, Dragon Fang, Miracle Seed, Mystic Water, Never Melt Ice, Twisted Spoon, Damp Rock, Heat Rock, Icy Rock, Smooth Rock
- Grouped together some recipes within the Recipe Book. Groups include: the seven basic Poké Balls, the seven basic Ancient Poké Balls, Gilded Chests, Pokedexes, and the Weather Rocks.
- Updated some item tags to better integrate behaviours between Cobblemon, Vanilla Minecraft, and other mods
- Removed Cooked Meat, Raw Meat, Protein Ingredients, and Zinc Ingredients, the first two are now using `c` namespace tags, the latter have better integrated use of tags within their recipes which removes need for custom tags.
- Added our seeds tag into `#c:seeds`, which is now made use of for the Miracle Seed recipe.
- Tweaked the Natural Materials Vanilla file to fit with the changes to tags
- Substantially optimised spawning checks mainly by front-loading biome filtering.
- When using the `cobblemon` or `generation_9` capture calculators a critical capture with a single shake will always play for successful captures when you've already registered the Pokémon as caught in your Pokédex.
- Improved the performance of saving Pokédex and player data.
- Pokémon hitbox now scales with entity attribute `generic.scale`.
- Removed Shulker aspect and replaced it with cosmetic_item-shulker_shell.
- Shulker shell Forretress is now a cosmetic rather than a special evo and thus all shulker Forretress will revert back to normal until a shulker shell is put in their cosmetic slot.
- Updated `doPokemonSpawning` gamerule to support per-dimension configurations.
- The Pokedex now displays a form name of a "normal" Pokémon for when the base form is still a named form.
- Improved the zoom functionality of the Pokédex Scanner by giving the levels logarithmic scaling.
- Added a subtle rotation effect to the Pokédex Scanner's wheel when zooming.
- Improved parity with vanilla mobs' drop behavior; loot and XP drop on death instead of after the entire death sequence finishes.
- Quirk animations no longer play in the battle GUI since they were pretty distracting.
### Fixes
- Fixed Particles sometimes facing the wrong direction (looking at you, Swords Dance)
@@ -161,9 +127,7 @@
- Fixed Pokémon marked as silent still playing shiny sounds and effects.
- Fixed an issue with newer versions of Fabric API where underground Pokémon were spawning in The End.
- Fixed spawning not working well when you're at high points surrounded by lower altitude spawning areas, such as flying.
- Fixed certain Pokémon with forms not having appropriate stock Pokédex entries.
- Fixed issue with Pokédex Scanner that caused the open/close overlay to have the wrong opacity values
- Fixed dragon's breath not being usable on the restoration tank when it should be
- Fixed swapping held items with wild Pokémon under certain circumstances causing duplicate items
### Developer
- A finished battle now has winners and losers set inside of `PokemonBattle` instead of them always being empty.
@@ -192,15 +156,11 @@
- Added `interpolate` boolean property to animated textures to allow gradual colour changes between frames.
- Fixed species additions not being capable of changing implemented status.
- Added support for action effects that are triggered by `|-activate|` Showdown instructions. `activate_{effect_id}` is the syntax.
- Added MoLang functions for rendering items `render_item(item_id, locator_name)` and `clear_items()`.
- Added Molang functions for rendering items `render_item(item_id, locator_name)` and `clear_items()`.
- Fixed location spawn filter components causing crashes
- Added `pokemon` as an available MoLang function for the `battleActor` functions.
- Added `spawn_pokemon` as an available MoLang function for the `worldHolder` functions.
- Added `attempt_wild_battle` as an available MoLang function.
- Added `pokemon` as an available Molang function for the `battleActor` functions.
- Fixed `heldItem` property inside spawn files not working and causing crashes
- Fixed `spawn_bedrock_particles` MoLang causing crashes when used in a server environment
- The Pokédex form lang key definition now follows `cobblemon.ui.pokedex.info.form.{species}-{formname}` instead of `cobblemon.ui.pokedex.info.form.{formname}`.
## [1.6.1 (January 26th, 2025)](#1-6-1)
@@ -306,7 +266,6 @@
- Geodude
- Graveler
- Golem
- Magnezone
### Cry updates for the following Pokémon
- Sceptile
@@ -13,12 +13,10 @@ import com.cobblemon.mod.common.client.render.layer.PokemonOnShoulderRenderer;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.pokemon.FormData;
import com.cobblemon.mod.common.pokemon.lighthing.LightingData;
import dev.lambdaurora.lambdynlights.LambDynLights;
import dev.lambdaurora.lambdynlights.api.DynamicLightHandler;
import dev.lambdaurora.lambdynlights.api.DynamicLightHandlers;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -55,19 +53,15 @@ public class LambDynamicLightsCompat {
private static int resolvedPokemonLightLevel(PokemonEntity pokemon) {
var underwater = !pokemon.level().getFluidState(BlockPos.containing(pokemon.getX(), pokemon.getEyeY(), pokemon.getZ())).isEmpty();
ItemStack item = pokemon.getShownItem();
int itemLightLevel = item.isEmpty()?0:LambDynLights.getLuminanceFromItemStack(item, underwater);
int formLightLevel = extractFormLightLevel(pokemon.getForm(), underwater).orElse(0);
return Math.max(itemLightLevel, formLightLevel);
return extractFormLightLevel(pokemon.getForm(), underwater).orElse(0);
}
private static int resolvedShoulderLightLevel(Player player) {
final var shoulderDataPair = PokemonOnShoulderRenderer.shoulderDataOf(player);
var underwater = !player.level().getFluidState(BlockPos.containing(player.getX(), player.getEyeY(), player.getZ())).isEmpty();
final int leftLightLevel = extractShoulderLightLevel(PokemonOnShoulderRenderer.shoulderDataFrom(player.getShoulderEntityLeft()), underwater);
final int rightLightLevel = extractShoulderLightLevel(PokemonOnShoulderRenderer.shoulderDataFrom(player.getShoulderEntityRight()), underwater);
return Math.max(leftLightLevel, rightLightLevel);
final Optional<Integer> leftLightLevel = extractShoulderLightLevel(shoulderDataPair.getFirst(), underwater);
final Optional<Integer> rightLightLevel = extractShoulderLightLevel(shoulderDataPair.getSecond(), underwater);
return Math.max(leftLightLevel.orElse(0), rightLightLevel.orElse(0));
}
private static Optional<Integer> extractFormLightLevel(@NotNull FormData form, boolean underwater) {
@@ -77,16 +71,11 @@ public class LambDynamicLightsCompat {
return Optional.of(form.getLightingData().getLightLevel());
}
private static int extractShoulderLightLevel(@Nullable PokemonOnShoulderRenderer.ShoulderData shoulderData, boolean underwater) {
private static Optional<Integer> extractShoulderLightLevel(@Nullable PokemonOnShoulderRenderer.ShoulderData shoulderData, boolean underwater) {
if (shoulderData == null) {
return 0;
return Optional.empty();
}
ItemStack item = shoulderData.getShownItem();
int itemLightLevel = item.isEmpty()?0:LambDynLights.getLuminanceFromItemStack(item, underwater);
int formLightLevel = extractFormLightLevel(shoulderData.getForm(), underwater).orElse(0);
return Math.max(itemLightLevel, formLightLevel);
return extractFormLightLevel(shoulderData.getForm(), underwater);
}
private static boolean liquidGlowModeSupport(@NotNull LightingData.LiquidGlowMode liquidGlowMode, boolean underwater) {
@@ -13,7 +13,6 @@ import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.api.riding.Rideable;
import com.cobblemon.mod.common.api.riding.Seat;
import com.cobblemon.mod.common.client.MountedPokemonAnimationRenderController;
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate;
import com.cobblemon.mod.common.client.render.MatrixWrapper;
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState;
@@ -61,8 +60,6 @@ public abstract class CameraMixin {
@Shadow
private BlockGetter level;
@Shadow private float partialTickTime;
@Unique private float returnTimer = 0;
@Unique private float rollAngleStart = 0;
@Unique Minecraft minecraft = Minecraft.getInstance();
@@ -70,7 +67,7 @@ public abstract class CameraMixin {
@Unique boolean disableRollableCameraDebug = false;
@Inject(method = "setRotation", at = @At("HEAD"), cancellable = true)
public void cobblemon$setRotation(float f, float g, CallbackInfo ci) {
public void open_camera$setRotation(float f, float g, CallbackInfo ci) {
if (!(this.entity instanceof OrientationControllable controllable) || disableRollableCameraDebug) return;
var controller = controllable.getOrientationController();
if (!controller.isActive() && controller.getOrientation() != null) {
@@ -88,7 +85,7 @@ public abstract class CameraMixin {
return;
}
controller.getOrientation().rotateZ((float) Math.toRadians(-rollAngleStart*(1-returnTimer)));
cobblemon$applyRotation();
applyRotation();
this.returnTimer += .05F;
ci.cancel();
} else {
@@ -98,7 +95,7 @@ public abstract class CameraMixin {
return;
}
if (controller.getOrientation() == null) return;
cobblemon$applyRotation();
applyRotation();
this.returnTimer = 0;
this.rollAngleStart = controller.getRoll();
@@ -107,10 +104,9 @@ public abstract class CameraMixin {
//If you want to move this to a delagate you need an AW for position
@WrapOperation(method = "setup", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setPosition(DDD)V"))
public void cobblemon$positionCamera(Camera instance, double x, double y, double z, Operation<Void> original, @Local(ordinal = 1, argsOnly = true) boolean thirdPersonReverse) {
public void positionCamera(Camera instance, double x, double y, double z, Operation<Void> original, @Local(ordinal = 1, argsOnly = true) boolean thirdPersonReverse) {
Entity entity = instance.getEntity();
Entity vehicle = entity.getVehicle();
MountedPokemonAnimationRenderController.INSTANCE.reset();
if(vehicle instanceof Rideable){
//RidingCameraDelagate.INSTANCE.positionCamera(instance, x, y, z, original);
@@ -119,20 +115,20 @@ public abstract class CameraMixin {
return;
}
PokemonClientDelegate delegate = (PokemonClientDelegate) pokemon.getDelegate();
int seatIndex = pokemon.getPassengers().indexOf(entity);
Seat seat = pokemon.getSeats().get(seatIndex);
Vec3 entityPos = new Vec3(
Mth.lerp(instance.getPartialTickTime(), pokemon.xOld, pokemon.getX()),
Mth.lerp(instance.getPartialTickTime(), pokemon.yOld, pokemon.getY()),
Mth.lerp(instance.getPartialTickTime(), pokemon.zOld, pokemon.getZ())
);
MountedPokemonAnimationRenderController.INSTANCE.setup(pokemon, partialTickTime);
int seatIndex = pokemon.getPassengers().indexOf(entity);
Seat seat = pokemon.getSeats().get(seatIndex);
OrientationControllable rollable = (OrientationControllable) entity;
OrientationController controller = rollable.getOrientationController();
if (!instance.isDetached() || Cobblemon.config.getThirdPartyViewBobbing()) {
PokemonClientDelegate delegate = (PokemonClientDelegate) pokemon.getDelegate();
MatrixWrapper locator = delegate.getLocatorStates().get(seat.getLocator());
if (locator == null) {
@@ -143,12 +139,10 @@ public abstract class CameraMixin {
Vec3 locatorOffset = new Vec3(locator.getMatrix().getTranslation(new Vector3f()));
float currEyeHeight = Mth.lerp(instance.getPartialTickTime(), eyeHeightOld, eyeHeight);
var rotatedEyeHeight = new Vector3f(0f, currEyeHeight - (entity.getBbHeight() / 2), 0f);
if (controller.isActive()) {
rotatedEyeHeight = controller.getRenderOrientation(partialTickTime).transform(rotatedEyeHeight);
}
Matrix3f orientation = controller.isActive() && controller.getOrientation() != null ? controller.getOrientation() : new Matrix3f();
Vec3 rotatedEyeHeight = new Vec3(orientation.transform(new Vector3f(0f, currEyeHeight - (entity.getBbHeight() / 2), 0f)));
Vec3 position = locatorOffset.add(entityPos).add(new Vec3(rotatedEyeHeight));
Vec3 position = locatorOffset.add(entityPos).add(rotatedEyeHeight);
setPosition(position);
} else {
Vector3f pos = entityPos.add(new Vec3(0, pokemon.getBbHeight() / 2, 0)).toVector3f();
@@ -185,7 +179,7 @@ public abstract class CameraMixin {
}
@Unique
private void cobblemon$applyRotation(){
private void applyRotation(){
if (!(this.entity instanceof OrientationControllable controllable) || controllable.getOrientationController().getOrientation() == null) return;
var controller = controllable.getOrientationController();
var newRotation = controller.getOrientation().normal(new Matrix3f()).getNormalizedRotation(new Quaternionf());
@@ -59,21 +59,8 @@ public class HumanoidModelMixin {
}
Entity vehicle = entity.getVehicle();
if (vehicle instanceof PokemonEntity pokemonEntity && entity instanceof AbstractClientPlayer) {
var shouldRotatePlayerHead = pokemonEntity.ifRidingAvailableSupply(false, (behaviour, settings, state) -> {
return behaviour.shouldRotatePlayerHead(settings, state, pokemonEntity);
});
if (!shouldRotatePlayerHead) {
netHeadYaw = 0f;
headPitch = 0f;
if (this.head != null) {
this.head.yRot = 0f;
this.head.xRot = 0f;
}
}
MountedPlayerRenderer.INSTANCE.animate(pokemonEntity, (AbstractClientPlayer) entity, relevantPartsByName, netHeadYaw, headPitch, ageInTicks, limbSwing, limbSwingAmount);
if (vehicle instanceof PokemonEntity && entity instanceof AbstractClientPlayer) {
MountedPlayerRenderer.INSTANCE.animate((PokemonEntity) vehicle, (AbstractClientPlayer) entity, relevantPartsByName, netHeadYaw, headPitch, ageInTicks, limbSwing, limbSwingAmount);
}
}
}
@@ -15,7 +15,6 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext;
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.blaze3d.Blaze3D;
import net.minecraft.client.Minecraft;
import net.minecraft.client.MouseHandler;
import net.minecraft.client.player.LocalPlayer;
@@ -30,10 +29,9 @@ 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;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
@Mixin(MouseHandler.class)
public abstract class MouseHandlerMixin {
public class MouseHandlerMixin {
@Shadow private double accumulatedScrollY;
@Shadow @Final private Minecraft minecraft;
@@ -51,14 +49,6 @@ public abstract class MouseHandlerMixin {
@Shadow private double accumulatedDY;
@Shadow private double lastHandleMovementTime;
@Shadow public abstract boolean isMouseGrabbed();
@Shadow protected abstract void turnPlayer(double movementTime);
@Unique private double cobblemon$timeDelta;
@Inject(
method = "onScroll",
at = @At(
@@ -171,21 +161,16 @@ public abstract class MouseHandlerMixin {
return false;
}
@Inject(method = "handleAccumulatedMovement", at = @At("HEAD"))
private void cobblemon$maintainMovementWhenInScreens2(CallbackInfo ci) {
double time = Blaze3D.getTime();
cobblemon$timeDelta = time - this.lastHandleMovementTime;
}
@Inject(method = "handleAccumulatedMovement", at = @At("TAIL"))
private void cobblemon$maintainMovementWhenInScreens(CallbackInfo ci) {
@Inject(method = "handleAccumulatedMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MouseHandler;isMouseGrabbed()Z", ordinal = 0))
private void cobblemon$maintainMovementWhenInScreens(CallbackInfo ci, @Local(ordinal = 1) double e) {
if (minecraft.player == null) return;
if (!(minecraft.player instanceof OrientationControllable controllable)) return;
if (!controllable.getOrientationController().isActive()) return;
if (minecraft.isPaused()) return;
if (isMouseGrabbed()) return;
this.turnPlayer(cobblemon$timeDelta);
var pitch = pitchSmoother.getNewDeltaValue(0, e);
var roll = rollSmoother.getNewDeltaValue(0, e);
controllable.getOrientationController().rotate(0.0F, (float)pitch, (float)roll);
}
@Unique
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.client.model.PlayerModel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
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(PlayerModel.class)
public class PlayerModelMixin {
@Inject(method = "setupAnim(Lnet/minecraft/world/entity/LivingEntity;FFFFF)V", at = @At(value = "HEAD"))
private void cobblemon$setHeadRotation(LivingEntity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch, CallbackInfo ci) {
if (!(entity instanceof Player)) return;
Entity vehicle = entity.getVehicle();
if (!(vehicle instanceof PokemonEntity pokemonEntity)) return;
var shouldRotatePlayerHead = pokemonEntity.ifRidingAvailableSupply(false, (behaviour, settings, state) -> {
return behaviour.shouldRotatePlayerHead(settings, state, pokemonEntity);
});
if (shouldRotatePlayerHead) return;
netHeadYaw = 0f;
headPitch = 0f;
}
}
@@ -86,9 +86,9 @@ import com.cobblemon.mod.common.permission.LaxPermissionValidator
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.aspects.COSMETIC_SLOT_ASPECT
import com.cobblemon.mod.common.pokemon.aspects.FORM_ASPECTS
import com.cobblemon.mod.common.pokemon.aspects.GENDER_ASPECT
import com.cobblemon.mod.common.pokemon.aspects.SHINY_ASPECT
import com.cobblemon.mod.common.pokemon.evolution.variants.BlockClickEvolution
import com.cobblemon.mod.common.pokemon.feature.TagSeasonResolver
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import com.cobblemon.mod.common.pokemon.properties.AspectPropertyType
@@ -100,7 +100,6 @@ import com.cobblemon.mod.common.pokemon.properties.UnaspectPropertyType
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
import com.cobblemon.mod.common.pokemon.properties.tags.PokemonFlagProperty
import com.cobblemon.mod.common.pokemon.stat.CobblemonStatProvider
import com.cobblemon.mod.common.pokemon.transformation.triggers.BlockClickTrigger
import com.cobblemon.mod.common.starter.CobblemonStarterHandler
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.cobblemonResource
@@ -255,8 +254,13 @@ object Cobblemon {
PlatformEvents.RIGHT_CLICK_BLOCK.subscribe { event ->
val player = event.player
val block = player.level().getBlockState(event.pos).block
val context = BlockClickTrigger.Context(block, player.level())
player.party().forEach { pokemon -> pokemon.triggerTransformations(BlockClickTrigger::class.java, context) }
player.party().forEach { pokemon ->
pokemon.lockedEvolutions
.filterIsInstance<BlockClickEvolution>()
.forEach { evolution ->
evolution.attemptEvolution(pokemon, BlockClickEvolution.BlockInteractionContext(block, player.level()))
}
}
}
PlatformEvents.CHANGE_DIMENSION.subscribe {
@@ -297,7 +301,6 @@ object Cobblemon {
SHINY_ASPECT.register()
GENDER_ASPECT.register()
FORM_ASPECTS.register()
COSMETIC_SLOT_ASPECT.register()
SpeciesFeatures.types["choice"] = ChoiceSpeciesFeatureProvider::class.java
@@ -293,21 +293,20 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField val REAPER_CLOTH = noSettingsItem("reaper_cloth")
@JvmField val PRISM_SCALE = noSettingsItem("prism_scale")
@JvmField val SACHET = noSettingsItem("sachet")
@JvmField val WHIPPED_DREAM = create("whipped_dream", foodItem(8, 0.3f))
@JvmField val STRAWBERRY_SWEET = create("strawberry_sweet", foodItem(6, 0.125f))
@JvmField val LOVE_SWEET = create("love_sweet", foodItem(6, 0.125f))
@JvmField val BERRY_SWEET = create("berry_sweet", foodItem(6, 0.125f))
@JvmField val CLOVER_SWEET = create("clover_sweet", foodItem(6, 0.125f))
@JvmField val FLOWER_SWEET = create("flower_sweet", foodItem(6, 0.125f))
@JvmField val STAR_SWEET = create("star_sweet", foodItem(6, 0.125f))
@JvmField val RIBBON_SWEET = create("ribbon_sweet", foodItem(6, 0.125f))
@JvmField val WHIPPED_DREAM = noSettingsItem("whipped_dream")
@JvmField val STRAWBERRY_SWEET = noSettingsItem("strawberry_sweet")
@JvmField val LOVE_SWEET = noSettingsItem("love_sweet")
@JvmField val BERRY_SWEET = noSettingsItem("berry_sweet")
@JvmField val CLOVER_SWEET = noSettingsItem("clover_sweet")
@JvmField val FLOWER_SWEET = noSettingsItem("flower_sweet")
@JvmField val STAR_SWEET = noSettingsItem("star_sweet")
@JvmField val RIBBON_SWEET = noSettingsItem("ribbon_sweet")
@JvmField val CHIPPED_POT = noSettingsItem("chipped_pot")
@JvmField val CRACKED_POT = noSettingsItem("cracked_pot")
@JvmField val MASTERPIECE_TEACUP = noSettingsItem("masterpiece_teacup")
@JvmField val UNREMARKABLE_TEACUP = noSettingsItem("unremarkable_teacup")
@JvmField val SWEET_APPLE = create("sweet_apple", foodItem(4, 0.3f))
@JvmField val TART_APPLE = create("tart_apple", foodItem(4, 0.3f))
@JvmField val SYRUPY_APPLE = create("syrupy_apple", foodItem(4, 0.3f))
@JvmField val SWEET_APPLE = noSettingsItem("sweet_apple")
@JvmField val TART_APPLE = noSettingsItem("tart_apple")
@JvmField val GALARICA_CUFF = noSettingsItem("galarica_cuff")
@JvmField val GALARICA_WREATH = noSettingsItem("galarica_wreath")
@JvmField val BLACK_AUGURITE = noSettingsItem("black_augurite")
@@ -1359,7 +1358,4 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
return createdItem
}
private fun foodItem(nutrition: Int, saturationModifier: Float): Item {
return Item(Item.Properties().food(FoodProperties.Builder().nutrition(nutrition).saturationModifier(saturationModifier).build()))
}
}
@@ -170,6 +170,9 @@ import com.cobblemon.mod.common.net.messages.server.trade.ChangeTradeAcceptanceP
import com.cobblemon.mod.common.net.messages.server.trade.OfferTradePacket
import com.cobblemon.mod.common.net.messages.server.trade.PerformTradePacket
import com.cobblemon.mod.common.net.messages.server.trade.UpdateTradeOfferPacket
import com.cobblemon.mod.common.net.serverhandling.ChallengeHandler
import com.cobblemon.mod.common.net.serverhandling.ChallengeResponseHandler
import com.cobblemon.mod.common.net.serverhandling.RequestInteractionsHandler
import com.cobblemon.mod.common.net.serverhandling.battle.BattleSelectActionsHandler
import com.cobblemon.mod.common.net.serverhandling.battle.RemoveSpectatorHandler
import com.cobblemon.mod.common.net.serverhandling.battle.SpectateBattleHandler
@@ -208,12 +211,6 @@ import com.cobblemon.mod.common.net.serverhandling.storage.SwapPCPartyPokemonHan
import com.cobblemon.mod.common.net.serverhandling.storage.party.MovePartyPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.storage.party.ReleasePCPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.storage.party.SwapPartyPokemonHandler
import com.cobblemon.mod.common.net.messages.client.pokemon.update.form.AddFormPacket
import com.cobblemon.mod.common.net.messages.client.pokemon.update.form.ClearFormsPacket
import com.cobblemon.mod.common.net.messages.client.pokemon.update.form.RemoveFormPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.form.AcceptFormPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.form.RevertFormPacket
import com.cobblemon.mod.common.net.serverhandling.*
import com.cobblemon.mod.common.net.serverhandling.storage.pc.*
import com.cobblemon.mod.common.net.serverhandling.trade.AcceptTradeRequestHandler
import com.cobblemon.mod.common.net.serverhandling.trade.CancelTradeHandler
@@ -433,11 +430,6 @@ object CobblemonNetwork {
// Pokédex scanning
list.add(PacketRegisterInfo(ServerConfirmedRegisterPacket.ID, ServerConfirmedRegisterPacket::decode, ServerConfirmedRegisterHandler))
// Form packets
list.add(PacketRegisterInfo(AddFormPacket.ID, AddFormPacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(ClearFormsPacket.ID, ClearFormsPacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(RemoveFormPacket.ID, RemoveFormPacket::decode, PokemonUpdatePacketHandler()))
// Debug / cheats
list.add(PacketRegisterInfo(CalculateSeatPositionsPacket.ID, CalculateSeatPositionsPacket::decode, CalculateSeatPositionsHandler))
@@ -541,10 +533,6 @@ object CobblemonNetwork {
// NPC packets
list.add(PacketRegisterInfo(SaveNPCPacket.ID, SaveNPCPacket::decode, SaveNPCHandler))
// Form Packets
list.add(PacketRegisterInfo(AcceptFormPacket.ID, AcceptFormPacket::decode, AcceptFormHandler))
list.add(PacketRegisterInfo(RevertFormPacket.ID, RevertFormPacket::decode, RevertFormHandler))
// Riding packet(s)
list.add(PacketRegisterInfo(ServerboundUpdateOrientationPacket.ID, ServerboundUpdateOrientationPacket::decode, OrientationPacketHandler))
list.add(PacketRegisterInfo(ServerboundUpdateRidingStatePacket.ID, ServerboundUpdateRidingStatePacket::decode, ServerboundUpdateRidingStateHandler))
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.api.abilities
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.data.ShowdownIdentifiable
import com.cobblemon.mod.common.util.codec.CodecUtils
import com.google.gson.JsonObject
import com.mojang.serialization.Codec
@@ -26,7 +25,7 @@ class AbilityTemplate(
var builder: (AbilityTemplate, forced: Boolean, priority: Priority) -> Ability = { template, forced, priority -> Ability(template, forced, priority) },
val displayName: String = "cobblemon.ability.$name",
val description: String = "cobblemon.ability.$name.desc"
) : ShowdownIdentifiable {
) {
/**
* Returns the Ability or if applicable the extension connected to this template
*/
@@ -46,8 +45,6 @@ class AbilityTemplate(
*/
fun create(json: JsonObject) = create().loadFromJSON(json)
override fun showdownId(): String = ShowdownIdentifiable.REGEX.replace(this.name.lowercase(), "")
companion object {
@JvmStatic
@@ -48,9 +48,9 @@ import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.battle.BattleEndPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleMessagePacket
import com.cobblemon.mod.common.pokemon.transformation.progress.DefeatTransformationProgress
import com.cobblemon.mod.common.pokemon.transformation.progress.LastBattleCriticalHitsTransformationProgress
import com.cobblemon.mod.common.pokemon.transformation.requirements.DefeatRequirement
import com.cobblemon.mod.common.pokemon.evolution.progress.DefeatEvolutionProgress
import com.cobblemon.mod.common.pokemon.evolution.progress.LastBattleCriticalHitsEvolutionProgress
import com.cobblemon.mod.common.pokemon.evolution.requirements.DefeatRequirement
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.giveOrDropItemStack
@@ -77,7 +77,7 @@ open class PokemonBattle(
val side2: BattleSide
) {
/** Whether logging will be silenced for this battle. */
var mute = false
var mute = true
val struct = this.asMoLangValue()
val runtime = MoLangRuntime().also { it.environment.query = struct }
@@ -92,7 +92,7 @@ open class PokemonBattle(
actor.battle = this
actor.pokemonList.forEach { battlePokemon ->
battlePokemon.effectedPokemon.evolutionProxy.current().progress()
.filterIsInstance<LastBattleCriticalHitsTransformationProgress>()
.filterIsInstance<LastBattleCriticalHitsEvolutionProgress>()
.forEach { it.reset() }
}
}
@@ -252,12 +252,11 @@ open class PokemonBattle(
val facedFainted = opponentPokemon.facedOpponents.contains(faintedPokemon)
val pokemon = opponentPokemon.effectedPokemon
if (facedFainted) {
// TODO refactor and apply to forms
pokemon.lockedEvolutions.forEach { evolution ->
evolution.requirements.filterIsInstance<DefeatRequirement>().forEach { defeatRequirement ->
if (defeatRequirement.target.matches(faintedPokemon.effectedPokemon)) {
val progress = pokemon.evolutionProxy.current().progressFirstOrCreate({ it is DefeatTransformationProgress && it.currentProgress().target == defeatRequirement.target }) { DefeatTransformationProgress() }
progress.updateProgress(DefeatTransformationProgress.Progress(defeatRequirement.target, progress.currentProgress().amount + 1))
val progress = pokemon.evolutionProxy.current().progressFirstOrCreate({ it is DefeatEvolutionProgress && it.currentProgress().target == defeatRequirement.target }) { DefeatEvolutionProgress() }
progress.updateProgress(DefeatEvolutionProgress.Progress(defeatRequirement.target, progress.currentProgress().amount + 1))
}
}
}
@@ -8,11 +8,11 @@
package com.cobblemon.mod.common.api.drop
import com.cobblemon.mod.common.api.pokemon.transformation.requirement.TransformationRequirement
import com.cobblemon.mod.common.api.pokemon.evolution.requirement.EvolutionRequirement
import com.cobblemon.mod.common.pokemon.Pokemon
class EvolutionItemDropEntry : ItemDropEntry() {
val requirements: MutableSet<TransformationRequirement> = mutableSetOf()
val requirements: MutableSet<EvolutionRequirement> = mutableSetOf()
override fun canDrop(pokemon: Pokemon?): Boolean = pokemon != null && requirements.all { it.check(pokemon) }
}
@@ -20,4 +20,4 @@ interface EntitySideDelegate<T : Entity> {
fun tick(entity: T) {}
fun onSyncedDataUpdated(data: EntityDataAccessor<*>) {}
fun addToStruct(struct: QueryStruct) {}
}
}
@@ -22,5 +22,4 @@ interface PokemonSideDelegate : EntitySideDelegate<PokemonEntity> {
fun handleStatus(status: Byte) {}
fun spawnShinyParticle(player: Player) {}
fun positionRider(passenger: Entity, positionUpdater: MoveFunction)
fun updateAge(age: Int) {}
}
}
@@ -12,8 +12,8 @@ import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.transformation.evolution.Evolution
/**
* Fired when an evolution is accepted.
@@ -10,8 +10,8 @@ package com.cobblemon.mod.common.api.events.pokemon.evolution
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.transformation.evolution.Evolution
/**
* Fired after an evolution finishes.
@@ -8,9 +8,9 @@
package com.cobblemon.mod.common.api.events.pokemon.evolution
import com.cobblemon.mod.common.pokemon.transformation.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.evolution.EvolutionDisplay
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.transformation.evolution.EvolutionDisplay
/**
* Fired before an evolution is sent to clients.
@@ -8,7 +8,7 @@
package com.cobblemon.mod.common.api.events.pokemon.evolution
import com.cobblemon.mod.common.pokemon.transformation.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.pokemon.Pokemon
/**
@@ -12,8 +12,8 @@ import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.transformation.evolution.Evolution
import com.cobblemon.mod.common.util.getBoolean
/**
@@ -14,7 +14,7 @@ import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.data.FossilRegistrySyncPacket
import com.cobblemon.mod.common.pokemon.transformation.adapters.LegacyItemConditionWrapperAdapter
import com.cobblemon.mod.common.pokemon.evolution.adapters.LegacyItemConditionWrapperAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.ItemLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.pokemonPropertiesShortAdapter
@@ -215,7 +215,6 @@ fun drawPosablePortrait(
ageInTicks: Float = 0F,
headYaw: Float = 0F,
headPitch: Float = 0F,
doQuirks: Boolean = true,
r: Float = 1F,
g: Float = 1F,
b: Float = 1F,
@@ -241,7 +240,6 @@ fun drawPosablePortrait(
context.put(RenderContext.SPECIES, identifier)
context.put(RenderContext.ASPECTS, state.currentAspects)
context.put(RenderContext.POSABLE_STATE, state)
context.put(RenderContext.DO_QUIRKS, doQuirks)
val renderType = RenderType.entityCutout(texture)
@@ -74,7 +74,7 @@ abstract class RequestManager<T : ServerPlayerActionRequest> {
?: this.getInboundRequest(receiver.uuid, requestID)
/** Determines whether [player] can receive a [T]. */
open fun isBusy(player: ServerPlayer): Boolean = player.isBusy() || player.isPartyBusy()
open fun isBusy(player: ServerPlayer): Boolean = player.isInBattle() || player.isTrading() || player.party().find { it.entity?.isBusy == true } != null
/** Determines if the [target] is a valid interaction request target. */
abstract fun isValidInteraction(player: ServerPlayer, target: ServerPlayer): Boolean
@@ -29,6 +29,7 @@ import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.animations.NPCProvider
import com.cobblemon.mod.common.api.pokedex.*
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
@@ -37,7 +38,6 @@ import com.cobblemon.mod.common.api.storage.party.PartyStore
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore
import com.cobblemon.mod.common.api.storage.pc.PCPosition
import com.cobblemon.mod.common.api.storage.pc.PCStore
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
@@ -56,10 +56,9 @@ import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticl
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.pokemon.transformation.evolution.Evolution
import com.cobblemon.mod.common.pokemon.transformation.triggers.ItemInteractionTrigger
import com.cobblemon.mod.common.pokemon.transformation.triggers.LevelUpTrigger
import com.cobblemon.mod.common.pokemon.transformation.triggers.TradeTrigger
import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.LevelUpEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.TradeEvolution
import com.cobblemon.mod.common.util.*
import com.mojang.datafixers.util.Either
import net.minecraft.commands.arguments.EntityAnchorArgument
@@ -294,27 +293,6 @@ object MoLangFunctions {
packet.sendToPlayersAround(x, y, z, 64.0, world.dimension())
}
}
map.put("spawn_pokemon") { params ->
val x = params.getInt(0)
val y = params.getInt(1)
val z = params.getInt(2)
val props = params.getString(3).toProperties()
val pos = BlockPos(x, y, z)
if (!Level.isInSpawnableBounds(pos)) {
return@put DoubleValue.ZERO
}
val pokemon = props.createEntity(world)
pokemon.moveTo(pos, pokemon.yRot, pokemon.xRot)
if (world.addFreshEntity(pokemon)) {
return@put pokemon.struct
} else {
return@put DoubleValue.ZERO
}
}
map.put("get_entities_around") { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
@@ -832,14 +810,9 @@ object MoLangFunctions {
map.put("is_moving") { DoubleValue((pokemonEntity.moveControl as? PokemonMoveControl)?.hasWanted() == true) }
map.put("is_flying") { DoubleValue(pokemonEntity.getBehaviourFlag(PokemonBehaviourFlag.FLYING)) }
map.put("is_gliding") { DoubleValue(pokemonEntity.useRidingAltPose()) }
map.put("is_ridden") { DoubleValue(pokemonEntity.hasControllingPassenger()) }
map.put("has_aspect") { DoubleValue(it.getString(0) in pokemonEntity.aspects) }
map.put("is_pokemon") { DoubleValue.ONE }
map.put("is_holding_item") { DoubleValue(!pokemonEntity.entityData.get(PokemonEntity.SHOWN_HELD_ITEM).let {
it.isEmpty || it.`is`(CobblemonItemTags.WEARABLE_HAT_ITEMS) || it.`is`(CobblemonItemTags.WEARABLE_FACE_ITEMS)
}) }
map.put("is_wearing_hat") { DoubleValue(pokemonEntity.entityData.get(PokemonEntity.SHOWN_HELD_ITEM).`is`(CobblemonItemTags.WEARABLE_HAT_ITEMS)) }
map.put("is_wearing_face") { DoubleValue(pokemonEntity.entityData.get(PokemonEntity.SHOWN_HELD_ITEM).`is`(CobblemonItemTags.WEARABLE_FACE_ITEMS)) }
map.put("is_holding_item") { DoubleValue(!pokemonEntity.entityData.get(PokemonEntity.SHOWN_HELD_ITEM).isEmpty) }
map
}
)
@@ -1180,15 +1153,16 @@ object MoLangFunctions {
map.put("result") { evolution.result.asMoLangValue() }
if (evolution.trigger is LevelUpTrigger) {
if (evolution is LevelUpEvolution) {
map.put("is_level_up") { DoubleValue.ONE }
} else if (evolution.trigger is TradeTrigger) {
} else if (evolution is TradeEvolution) {
map.put("is_trade") { DoubleValue.ONE }
} else if (evolution.trigger is ItemInteractionTrigger) {
} else if (evolution is ItemInteractionEvolution) {
map.put("is_item") { DoubleValue.ONE }
}
map.put("is_optional") { DoubleValue(evolution.optional) }
map.put("consumes_held_item") { DoubleValue(evolution.consumeHeldItem) }
map
}
@@ -12,7 +12,6 @@ import com.bedrockk.molang.runtime.struct.MoStruct
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.data.ShowdownIdentifiable
import com.cobblemon.mod.common.api.moves.animations.ActionEffectTimeline
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
import com.cobblemon.mod.common.api.moves.categories.DamageCategory
@@ -57,7 +56,7 @@ open class MoveTemplate(
val critRatio: Double,
val effectChances: Array<Double>,
val actionEffect: ActionEffectTimeline?
) : ShowdownIdentifiable {
) {
val struct: MoStruct by lazy {
QueryStruct(hashMapOf())
.addFunction("name") { StringValue(name) }
@@ -193,6 +192,4 @@ open class MoveTemplate(
}
return this.elementalType
}
override fun showdownId() = ShowdownIdentifiable.REGEX.replace(this.name.lowercase(), "")
}
@@ -19,4 +19,4 @@ interface Decodable {
*/
fun decode(buffer: RegistryFriendlyByteBuf)
}
}
@@ -25,4 +25,4 @@ interface Encodable {
*/
fun encode(buffer: RegistryFriendlyByteBuf)
}
}
@@ -107,22 +107,4 @@ open class OrientationController(val entity: LivingEntity) {
val roll: Float
get() = Mth.wrapDegrees(-upVector.angleSigned(UP, forwardVector).toDegrees())
fun applyGlobalYaw(deltaYawDegrees: Float) = updateOrientation { original ->
val euler = Vector3f()
original.getEulerAnglesXYZ(euler)
val pitch0 = euler.x
val roll0 = euler.z
val unroll = Matrix3f().rotateZ(-roll0)
val unpitch = Matrix3f().rotateX(-pitch0)
val flattened = Matrix3f(original).mul(unroll).mul(unpitch)
//Flip to reflect current acceptance of what direction is pos for yaw
val globalYaw = Matrix3f().rotationY(-1.0f * deltaYawDegrees.toRadians())
val rePitch = Matrix3f().rotateX(pitch0)
val reRoll = Matrix3f().rotateZ(roll0)
return@updateOrientation Matrix3f(globalYaw).mul(flattened).mul(rePitch).mul(reRoll)
}
}
@@ -77,18 +77,18 @@ object CatchRateModifiers {
/**
* Used by [PokeBalls.MOON_BALL].
* Boosts the catch rate 1.5× during Moon Phases 2 and 6 between the times of 12000 – 24000.
* 2.5× during Moon Phases 1 and 7 between the times of 12000 – 24000.
* 4× during Moon Phase 0 between the times of 12000 – 24000.
* Boosts the catch rate 1.5× during Moon Phases 3 and 7 between the times of 12000 – 24000.
* 2.5× during Moon Phases 2 and 8 between the times of 12000 – 24000.
* 4× during Moon Phase 1 between the times of 12000 – 24000.
* 1× otherwise.
*/
val MOON_PHASES: CatchRateModifier = WorldStateModifier { _, entity ->
if (entity.level().gameTime in 12000..24000)
return@WorldStateModifier 1F
when (entity.level().moonPhase) {
2, 6 -> 1.5F
1, 7 -> 2.5F
0 -> 4F
3, 7 -> 1.5F
2, 8 -> 2.5F
1 -> 4F
else -> 1F
}
}
@@ -466,18 +466,29 @@ open class PokemonProperties {
}
fun matches(properties: PokemonProperties): Boolean {
// Custom properties check could be duplicated but can't assume 3rd party hasn't done anything type specific
return this.commonMatches(properties) && this.customProperties.containsAll(properties.customProperties)
return properties.asString() == this.asString()
}
// Undefined properties will match whatever they're compared with
private fun commonMatches(pokemon: Pokemon): Boolean {
level?.takeIf { it != pokemon.level }?.let { return false }
shiny?.takeIf { it != pokemon.shiny }?.let { return false }
gender?.takeIf { it != pokemon.gender }?.let { return false }
species?.takeIf { it.asSpecies() != pokemon.species }?.let { return false }
species?.run {
try {
val species = if (this == "random") {
PokemonSpecies.species.random()
} else {
PokemonSpecies.getByIdentifier(this.asIdentifierDefaultingNamespace()) ?: return@run
}
if (pokemon.species != species) {
return false
}
} catch (e: ResourceLocationException) {
return false
}
}
nickname?.takeIf { it.string != pokemon.nickname?.string }?.let { return false }
form?.takeIf { !it.equals(pokemon.form.formOnlyShowdownId(), true) }?.let { return false }
form?.takeIf { !it.equals(pokemon.form.name, true) }?.let { return false }
friendship?.takeIf { it != pokemon.friendship }?.let { return false }
pokeball?.takeIf { it != pokemon.caughtBall.name.toString() }?.let { return false }
nature?.takeIf { it != pokemon.nature.name.toString() }?.let { return false }
@@ -516,36 +527,6 @@ open class PokemonProperties {
return true
}
// Undefined properties will match whatever they're compared with
private fun commonMatches(properties: PokemonProperties): Boolean {
level?.takeIf { it != properties.level }?.let { return false }
shiny?.takeIf { it != properties.shiny }?.let { return false }
gender?.takeIf { it != properties.gender }?.let { return false }
species?.takeIf { it.asSpecies() != properties.species?.asSpecies() }?.let { return false }
nickname?.takeIf { it.string != properties.nickname?.string }?.let { return false }
form?.takeIf { it != properties.form }?.let { return false }
friendship?.takeIf { it != properties.friendship }?.let { return false }
pokeball?.takeIf { it != properties.pokeball }?.let { return false }
nature?.takeIf { it != properties.nature }?.let { return false }
ability?.takeIf { it != properties.ability }?.let { return false }
status?.takeIf { it != properties.status }?.let { return false }
ivs?.forEach{ stat ->
if (stat.value != properties.ivs?.get(stat.key)) { return false }
}
evs?.forEach{ stat ->
if (stat.value != properties.evs?.get(stat.key)) { return false }
}
type?.takeIf { it != properties.type }?.let { return false }
teraType?.takeIf { it != properties.teraType }?.let { return false }
dmaxLevel?.takeIf { it != properties.dmaxLevel }?.let { return false }
gmaxFactor?.takeIf { it != properties.gmaxFactor }?.let { return false }
tradeable?.takeIf { it != properties.tradeable }?.let { return false }
originalTrainer?.takeIf { it != properties.originalTrainer }?.let{ return false }
originalTrainerType?.takeIf { it != properties.originalTrainerType }?.let{ return false }
moves?.takeIf { it.any { move -> properties.moves?.none { it == move } == true } }?.let { return false }
return true
}
// TODO Review the need for this, usage for us is in commands that deviate from the behavior established by multiple Pokémon query equivalent
fun isSubSetOf(properties: PokemonProperties): Boolean {
level?.takeIf { it != properties.level }?.let { return false }
@@ -817,15 +798,4 @@ open class PokemonProperties {
return potentialAbility.template.create(false, potentialAbility.priority)
}
private fun String.asSpecies() =
try {
if (this == "random") {
PokemonSpecies.species.random()
} else {
PokemonSpecies.getByIdentifier(this.asIdentifierDefaultingNamespace())
}
}
catch (e: ResourceLocationException) {
null
}
}
@@ -12,8 +12,6 @@ import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.abilities.AbilityPool
import com.cobblemon.mod.common.api.abilities.AbilityTemplate
import com.cobblemon.mod.common.api.abilities.CommonAbility
import com.cobblemon.mod.common.api.abilities.PotentialAbility
import com.cobblemon.mod.common.api.ai.SleepDepth
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.data.JsonDataRegistry
@@ -26,15 +24,14 @@ import com.cobblemon.mod.common.api.moves.adapters.MoveTemplateAdapter
import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffect
import com.cobblemon.mod.common.api.pokemon.effect.adapter.ShoulderEffectAdapter
import com.cobblemon.mod.common.api.pokemon.egg.EggGroup
import com.cobblemon.mod.common.pokemon.transformation.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.transformation.evolution.PreEvolution
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.evolution.PreEvolution
import com.cobblemon.mod.common.api.pokemon.evolution.requirement.EvolutionRequirement
import com.cobblemon.mod.common.api.pokemon.experience.ExperienceGroup
import com.cobblemon.mod.common.api.pokemon.experience.ExperienceGroupAdapter
import com.cobblemon.mod.common.api.pokemon.moves.Learnset
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.pokemon.transformation.requirement.TransformationRequirement
import com.cobblemon.mod.common.api.pokemon.transformation.trigger.TransformationTrigger
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.stats.RidingStatDefinition
@@ -45,16 +42,11 @@ import com.cobblemon.mod.common.net.messages.client.data.SpeciesRegistrySyncPack
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.pokemon.SpeciesAdditions
import com.cobblemon.mod.common.pokemon.abilities.HiddenAbility
import com.cobblemon.mod.common.pokemon.abilities.SpecialAbility
import com.cobblemon.mod.common.pokemon.evolution.adapters.CobblemonEvolutionAdapter
import com.cobblemon.mod.common.pokemon.evolution.adapters.CobblemonPreEvolutionAdapter
import com.cobblemon.mod.common.pokemon.evolution.adapters.CobblemonRequirementAdapter
import com.cobblemon.mod.common.pokemon.evolution.adapters.LegacyItemConditionWrapperAdapter
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import com.cobblemon.mod.common.pokemon.transformation.adapters.*
import com.cobblemon.mod.common.pokemon.transformation.form.GigantamaxForm
import com.cobblemon.mod.common.pokemon.transformation.form.PermanentForm
import com.cobblemon.mod.common.pokemon.transformation.form.StandardForm
import com.cobblemon.mod.common.pokemon.transformation.form.TemporaryForm
import com.cobblemon.mod.common.pokemon.transformation.requirements.HeldItemRequirement
import com.cobblemon.mod.common.pokemon.transformation.requirements.MoveSetRequirement
import com.cobblemon.mod.common.util.adapters.*
import com.cobblemon.mod.common.util.adapters.RidingBehaviourSettingsAdapter
import com.cobblemon.mod.common.util.cobblemonResource
@@ -77,7 +69,6 @@ import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.levelgen.structure.Structure
import net.minecraft.world.phys.AABB
import java.util.NoSuchElementException
object PokemonSpecies : JsonDataRegistry<Species> {
@@ -93,8 +84,10 @@ object PokemonSpecies : JsonDataRegistry<Species> {
.registerTypeAdapter(ExperienceGroup::class.java, ExperienceGroupAdapter)
.registerTypeAdapter(EntityDimensions::class.java, EntityDimensionsAdapter)
.registerTypeAdapter(Learnset::class.java, LearnsetAdapter)
.registerTypeAdapter(Evolution::class.java, CobblemonEvolutionAdapter)
.registerTypeAdapter(AABB::class.java, BoxAdapter)
.registerTypeAdapter(AbilityPool::class.java, AbilityPoolAdapter)
.registerTypeAdapter(EvolutionRequirement::class.java, CobblemonRequirementAdapter)
.registerTypeAdapter(PreEvolution::class.java, CobblemonPreEvolutionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(Set::class.java, Evolution::class.java).type, LazySetAdapter(Evolution::class))
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
@@ -113,10 +106,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Structure::class.java).type, StructureLikeConditionAdapter)
.registerTypeAdapter(EggGroup::class.java, EggGroupAdapter)
.registerTypeAdapter(MobEffect::class.java, RegistryElementAdapter<MobEffect>(BuiltInRegistries::MOB_EFFECT))
.registerTypeAdapter(TransformationTrigger::class.java, CobblemonTriggerAdapter)
.registerTypeAdapter(TransformationRequirement::class.java, CobblemonRequirementAdapter)
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
.registerTypeAdapter(FormData::class.java, FormAdapter)
.registerTypeAdapter(RidingBehaviourSettings::class.java, RidingBehaviourSettingsAdapter)
.registerTypeAdapter(RidingStatDefinition::class.java, RidingStatDefinitionAdapter)
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
@@ -136,9 +126,6 @@ object PokemonSpecies : JsonDataRegistry<Species> {
get() = this.speciesByIdentifier.values
val implemented = mutableListOf<Species>()
val forms: Collection<FormData>
get() = this.species.flatMap { it.allForms }
init {
SpeciesAdditions.observable.subscribe {
this.species.forEach(Species::initialize)
@@ -238,18 +225,34 @@ object PokemonSpecies : JsonDataRegistry<Species> {
}
/**
* The representation of [FormData] in the context of Showdown.
* The representation of a [Species] and/or [FormData] in the context of showdown.
* This is intended as a sort of DTO that can be easily converted between JSON and Java/JS objects.
*
* @param form The [FormData] being converted into a species (Showdown considers them species).
* @param species The [Species] being converted or the base species if the form is not null.
* @param form The [FormData] being converted int o species (Showdown considers them species) will be null when dealing with the base form.
*/
@Suppress("unused")
internal open class ShowdownSpecies(form: FormData) {
val num = form.species.nationalPokedexNumber
val name = form.showdownName()
val types = form.types.map { it.name.replaceFirstChar(Char::uppercase) }
val eggGroups = form.eggGroups.map { it.showdownID }
val gender: String? = when (form.maleRatio) {
internal class ShowdownSpecies(species: Species, form: FormData?) {
val num = species.nationalPokedexNumber
val name = if (form != null) "${createShowdownName(species)}-${form.name}" else createShowdownName(species)
val baseSpecies = if (form != null) createShowdownName(species) else this.name
val forme = form?.name
// ToDo baseForme
val otherFormes = if (form == null && species.forms.isNotEmpty()) species.forms.map { "${this.name}-${it.name}" } else emptyList()
val formeOrder = if (form == null && this.otherFormes.isNotEmpty()) arrayListOf(this.name, *this.otherFormes.toTypedArray()) else emptyList()
val abilities: Map<String, String> = mapOf(
"0" to "No Ability",
"1" to "No Ability",
"H" to "No Ability",
"S" to "No Ability"
)
val types = (form?.types ?: species.types).map { it.name.replaceFirstChar(Char::uppercase) }
val preevo: String? = (form?.preEvolution ?: species.preEvolution)?.let { if (it.form == it.species.standardForm) createShowdownName(it.species) else "${createShowdownName(it.species)}-${it.form.name}" }
// For the context of battles the content here doesn't matter whatsoever and due to how PokemonProperties work we can't guarantee an actual specific species is defined.
val evos = if ((form?.evolutions ?: species.evolutions).isEmpty()) emptyList() else arrayListOf("")
val nfe = this.evos.isNotEmpty()
val eggGroups = (form?.eggGroups ?: species.eggGroups).map { it.showdownID }
val gender: String? = when (form?.maleRatio ?: species.maleRatio) {
0F -> "F"
1F -> "M"
-1F, 1.125F -> "N"
@@ -257,132 +260,35 @@ object PokemonSpecies : JsonDataRegistry<Species> {
}
val genderRatio = if (this.gender == null)
mapOf(
"M" to (form.maleRatio),
"F" to (1F - (form.maleRatio))
"maleRatio" to (form?.maleRatio ?: species.maleRatio),
"femaleRation" to (1F - (form?.maleRatio ?: species.maleRatio))
) else null
val baseStats = Stats.PERMANENT.associateBy({ it.showdownId }, { form.baseStats[it] ?: 1 })
val heightm = form.height / 10
val weightkg = form.weight / 10
val baseStats = mapOf(
"hp" to (form?.baseStats?.get(Stats.HP) ?: species.baseStats[Stats.HP] ?: 1),
"atk" to (form?.baseStats?.get(Stats.ATTACK) ?: species.baseStats[Stats.ATTACK] ?: 1),
"def" to (form?.baseStats?.get(Stats.DEFENCE) ?: species.baseStats[Stats.DEFENCE] ?: 1),
"spa" to (form?.baseStats?.get(Stats.SPECIAL_ATTACK) ?: species.baseStats[Stats.SPECIAL_ATTACK] ?: 1),
"spd" to (form?.baseStats?.get(Stats.SPECIAL_DEFENCE) ?: species.baseStats[Stats.SPECIAL_DEFENCE] ?: 1),
"spe" to (form?.baseStats?.get(Stats.SPEED) ?: species.baseStats[Stats.SPEED] ?: 1)
)
val heightm = (form?.height ?: species.height) / 10
val weightkg = (form?.weight ?: species.weight) / 10
// This is ugly, but we already have it hardcoded in the mod anyway
val maxHP = if (this.num == 292) 1 else null
val cannotDynamax = form.dynamaxBlocked
val abilities = form.showdownAbilityMapping()
// Common fields exempt from standard forms
val baseSpecies = if (form !is StandardForm) form.species.standardForm.showdownName() else null
val forme = if (form !is StandardForm) form.name else null
/**
* The following fields are optional and currently omitted
*
* cosmeticFormes (not needed, we have species features)
* addedType (tertiary type [by name or showdownid] added by moves or abilities)
* tier (smogon tryhard bs)
* doublesTier (smogon tryhard bs)
* natDexTier (smogon tryhard bs)
* evoType (the evolution method used to evolve from 'prevo'; trade', 'useItem', 'levelMove', 'levelExtra', 'levelFriendship', 'levelHold', 'other')
* evoMove (the move [by name or showdownid] used to evolve from 'prevo')
* evoLevel (the minimum level required to evolve from 'prevo')
* canHatch (flag for baby pokemon)
* color (color, value is arbitrary)
* tags (flags for filtering pokemon; 'Mega', 'Mythical', 'Sub-Legendary', 'Restricted Legendary', 'Paradox')
* unreleasedHidden (whether this pokemon has an unreleased hidden ability)
* maleOnlyHidden (whether it is only possible to get the hidden ability on a male pokemon)
* gmaxUnreleased (whether this pokemon has an unreleased gmax form)
* pokemonGoData (no one cares)
*/
val maxHP = if (species.showdownId() == "shedinja") 1 else null
val canGigantamax: String? = if (form?.gigantamaxMove != null) form.gigantamaxMove.name else null
val cannotDynamax = form?.dynamaxBlocked ?: species.dynamaxBlocked
// ToDo battleOnly
// ToDo changesFrom
val requiredMove = form?.requiredMove
val requiredItem = form?.requiredItem
val requiredItems = form?.requiredItems
}
/**
* The representation of a [PermanentForm] in the context of Showdown.
*
* @param form The [PermanentForm] being converted into a species.
*/
internal open class ShowdownPermanentForm(form: PermanentForm) : ShowdownSpecies(form) {
// species this form evolves from
val prevo = form.preEvolution?.form?.showdownName()
// For the context of battles the content here doesn't matter whatsoever and due to how PokemonProperties work we can't guarantee an actual specific species is defined.
val evos = form.evolutions.map { "" }
// not fully evolved
val nfe = this.evos.isNotEmpty()
// whether there is a gmax form to change to
val canGigantamax: String? = form.changeableForms.filterIsInstance<GigantamaxForm>().firstOrNull()?.name // TODO id better
}
/**
* The representation of a [StandardForm] in the context of Showdown.
*
* @param form The [StandardForm] being converted into a species.
*/
internal class ShowdownStandardForm(form: StandardForm) : ShowdownPermanentForm(form) {
// list of all forms descending from this base species
val otherFormes = form.flattenForms().map { it.showdownName() }
// list of forms [by name or showdownid] in the order they appear in game [union of baseSpecies, otherFormes, and cosmeticFormes] with the exception of ending with 'Gmax' forms
val formeOrder = if (this.otherFormes.isNotEmpty()) arrayListOf(this.name, *this.otherFormes.toTypedArray()) else null
// TODO we don't enforce any form order with our Species registration - right now this looks like only a problem with Mega Rayquaza, but investigate changes needed on Showdown's end to enable these lists to be more dynamic
}
/**
* The representation of a [TemporaryForm] in the context of Showdown.
*
* @param form The [TemporaryForm] being converted into a species.
*/
internal class ShowdownTemporaryForm(form: TemporaryForm) : ShowdownSpecies(form) {
// form change can occur during a battle
val battleOnly = if (form.battleOnly) form.parentForm.showdownName() else null
// form change can occur outside of battle (mutually exclusive with battleOnly)
val changesFrom = if (!form.battleOnly) form.parentForm.showdownName() else null
// pokemon must know this move to be in this form
val requiredMove = form.requirements.filterIsInstance<MoveSetRequirement>().firstOrNull()?.move?.showdownId() // TODO update showdown to support 'requiredMoves'
// pokemon must hold one of these item(s) to be in this form
val requiredItems = form.requirements.filterIsInstance<HeldItemRequirement>().firstOrNull()?.let { requirement ->
try {
requirement.itemCondition.items.get().map { BuiltInRegistries.ITEM.getKey(it.value()).path } // TODO held items on showdown are ignorant of namespace
}
catch (e: NoSuchElementException)
{
Cobblemon.LOGGER.error("Failed to resolve required held item for ${form.showdownId()}}") // TODO this needs to be done after tags are loaded...
null
}
private fun createShowdownName(species: Species): String {
if (species.resourceIdentifier.namespace == Cobblemon.MODID) {
return species.name
}
return "${species.resourceIdentifier.namespace}:${species.name}"
}
/** Accommodate for Showdown bullshit. */
/*
private fun TemporaryForm.showdownParentForm(): Any {
if (this.parentOverrides.isNotEmpty())
return this.species.standardForm.flattenForms().filter {
parentOverrides.contains(it.showdownId())
}.map {
it.showdownName()
}
else
return parentForm.showdownName()
}
*/
/**
* Constructs a mapping of the [PotentialAbility]s belonging to a [FormData] in Showdown identifiable format.
*
* @return A map of type (numeric for [CommonAbility]s, H for [HiddenAbility], S for [SpecialAbility]) to showdownid.
*/
private fun FormData.showdownAbilityMapping(): Map<String, String>? {
// potential abilities
val commonAbilities = this.abilities.filterIsInstance<CommonAbility>()
val hiddenAbility = this.abilities.find { it is HiddenAbility }
val specialAbility = this.abilities.find { it is SpecialAbility }
// can have multiple common abilities
val abilities = mutableMapOf<String, String>()
val mapping: (String, PotentialAbility) -> Unit = { key, ability ->
abilities.put(key, ability.template.showdownId())
}
// can only have one hidden and one special ability
commonAbilities.forEachIndexed { index, ability -> mapping(index.toString(), ability) }
hiddenAbility?.let { ability -> mapping("H", ability) }
specialAbility?.let { ability -> mapping("S", ability) }
return if (abilities.isEmpty()) null else abilities
}
}
@@ -1,24 +0,0 @@
package com.cobblemon.mod.common.api.pokemon.aspect
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeature
import com.cobblemon.mod.common.pokemon.Pokemon
/**
* A provider of aspects for a [SpeciesFeature].
*
* @author Segfault Guy
* @since Feb 12th, 2024
*/
interface FeatureAspectProvider: AspectProvider {
var default: String?
var isAspect: Boolean
/** Returns true if this [aspect] is provided by this provider. */
fun matches(aspect: String): Boolean
/** Creates a [SpeciesFeature] that provides the respective [aspect]. */
fun from(aspect: String): SpeciesFeature<*>?
/** Sets the [aspect] that's provided. */
fun set(pokemon: Pokemon, aspect: String)
}
@@ -24,11 +24,11 @@ interface SingleConditionalAspectProvider : AspectProvider {
fun getForFeature(name: String): SingleConditionalAspectProvider {
return object : SingleConditionalAspectProvider {
override val aspect: String = name
override fun meetsCondition(pokemon: Pokemon) = pokemon.getFeature<FlagSpeciesFeature>(name)?.value == true
override fun meetsCondition(pokemon: Pokemon) = pokemon.getFeature<FlagSpeciesFeature>(name)?.enabled == true
override fun meetsCondition(pokemonProperties: PokemonProperties) = pokemonProperties
.customProperties
.filterIsInstance<FlagSpeciesFeature>()
.any { it.name == name && it.value }
.any { it.name == name && it.enabled }
}
}
@@ -0,0 +1,54 @@
/*
* 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.pokemon.evolution
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.TradeEvolution
/**
* Represents an evolution of a [Pokemon] that can only occur during specific actions and with added context.
* For the default implementations see [ItemInteractionEvolution] & [TradeEvolution].
*
* @param RC The context given at runtime when querying the [Evolution].
* @param TC The context that is serialized from JSON during species loading, this is what the [RC] is expected to match against.
* @author Licious
* @since March 19th, 2022
*/
interface ContextEvolution<RC, TC> : Evolution {
/**
* The target context for this [Evolution] to even be tested.
*/
val requiredContext: TC
/**
* Attempts to evolve the given [Pokemon] under the given context of type [RC].
*
* @param pokemon The [Pokemon] attempting to evolve.
* @param context The context of this query.
* @return If the evolution was successful.
*/
fun attemptEvolution(pokemon: Pokemon, context: RC): Boolean {
if (this.testContext(pokemon, context) && super.test(pokemon)) {
return super.evolve(pokemon)
}
return false
}
/**
* Checks if the given context is valid for the [requiredContext].
*
* @param pokemon The [Pokemon] attempting to evolve.
* @param context The context of this query.
* @return If the context matched the [requiredContext].
*/
fun testContext(pokemon: Pokemon, context: RC): Boolean
}
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.pokemon.transformation.evolution
package com.cobblemon.mod.common.api.pokemon.evolution
import com.cobblemon.mod.common.Cobblemon.playerDataManager
import com.cobblemon.mod.common.CobblemonItems
@@ -17,24 +17,20 @@ import com.cobblemon.mod.common.api.drop.DropTable
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.pokemon.PokemonGainedEvent
import com.cobblemon.mod.common.api.events.pokemon.evolution.EvolutionCompleteEvent
import com.cobblemon.mod.common.api.events.pokemon.evolution.EvolutionDisplayEvent
import com.cobblemon.mod.common.api.events.pokemon.evolution.EvolutionTestedEvent
import com.cobblemon.mod.common.api.moves.BenchedMove
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.transformation.Transformation
import com.cobblemon.mod.common.api.pokemon.transformation.requirement.TransformationRequirement
import com.cobblemon.mod.common.api.pokemon.transformation.trigger.ContextTrigger
import com.cobblemon.mod.common.api.pokemon.transformation.trigger.PassiveTrigger
import com.cobblemon.mod.common.api.pokemon.transformation.trigger.TransformationTrigger
import com.cobblemon.mod.common.api.pokemon.evolution.requirement.EvolutionRequirement
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.item.PokeBallItem
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.activestate.ShoulderedState
import com.cobblemon.mod.common.pokemon.transformation.triggers.ItemInteractionTrigger
import com.cobblemon.mod.common.pokemon.transformation.triggers.LevelUpTrigger
import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.LevelUpEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.TradeEvolution
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.party
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
@@ -45,32 +41,58 @@ import net.minecraft.world.item.ItemStack
/**
* Represents an evolution of a [Pokemon], this is the server side counterpart of [EvolutionDisplay].
* Following Pokémon these can be triggered by 3 possible events, level ups, trades or using an item.
* For the default implementations see [LevelUpEvolution], [TradeEvolution] & [ItemInteractionEvolution].
* Also see [PassiveEvolution] & [ContextEvolution].
*
* @author Licious
* @since March 19th, 2022
*/
class Evolution(
override val id: String = "",
/** The [TransformationRequirement]s that need to be satisfied for this evolution to start. */
override val requirements: Set<TransformationRequirement> = setOf(),
/** The [TransformationTrigger] to kickstart this evolution. */
override val trigger: TransformationTrigger = LevelUpTrigger(),
/** If this evolution can be deferred and started whenever rather than immediately on trigger. */
override val optional: Boolean = true,
/** If this evolution needs its requirements reevaluated to remain valid. */
override val reevaluate: Boolean = true,
/** The result of this evolution. */
val result: PokemonProperties = PokemonProperties(),
/** The shed result of this evolution. */
val shedder: PokemonProperties? = null,
/** The [MoveTemplate]s that will be offered to be learnt upon evolving. */
val learnableMoves: Set<MoveTemplate> = setOf(),
/** The items that will drop once the evolution finishes. */
val drops: DropTable = DropTable()
) : Transformation {
interface Evolution : EvolutionLike {
override fun test(pokemon: Pokemon, context: ContextTrigger.Context?): Boolean {
val result = super.test(pokemon, context)
/**
* The result of this evolution.
*/
val result: PokemonProperties
/**
* The shed result of this evolution.
*/
val shedder: PokemonProperties?
/**
* If this evolution allows the user to choose when to start it or not.
*/
var optional: Boolean
/**
* If this [Evolution] will consume the [Pokemon.heldItem]
*/
var consumeHeldItem: Boolean
/**
* The [EvolutionRequirement]s behind this evolution.
*/
val requirements: MutableSet<EvolutionRequirement>
/**
* The [MoveTemplate]s that will be offered to be learnt upon evolving.
*/
val learnableMoves: MutableSet<MoveTemplate>
/**
* The items that will drop once the evolution finishes.
*/
val drops: DropTable
/**
* Checks if the given [Pokemon] passes all the conditions and is ready to evolve.
*
* @param pokemon The [Pokemon] being queried.
* @return If the [Evolution] can start.
*/
fun test(pokemon: Pokemon): Boolean {
val result = this.requirements.all { requirement -> requirement.check(pokemon) }
val event = EvolutionTestedEvent(pokemon, this, result, result)
CobblemonEvents.EVOLUTION_TESTED.post(event)
return event.result
@@ -78,21 +100,20 @@ class Evolution(
/**
* Starts this evolution or queues it if [optional] is true.
* Side effects may occur based on [consumeHeldItem].
*
* @param pokemon The [Pokemon] being evolved.
* @param context The optional [TriggerContext] needed to evolve.
* @return Whether the [Evolution] was successful.
*/
override fun start(pokemon: Pokemon, context: ContextTrigger.Context?): Boolean {
if (this.test(pokemon, context)) {
return if (this.optional)
pokemon.evolutionProxy.server().add(this)
else {
this.forceStart(pokemon)
true
}
fun evolve(pokemon: Pokemon): Boolean {
if (this.consumeHeldItem) {
pokemon.swapHeldItem(ItemStack.EMPTY)
}
return false
if (this.optional) {
// All the networking is handled under the hood, see EvolutionController.
return pokemon.evolutionProxy.server().add(this)
}
this.forceEvolve(pokemon)
return true
}
fun shed(pokemon: Pokemon): Boolean {
@@ -132,7 +153,7 @@ class Evolution(
*
* @param pokemon The [Pokemon] being evolved.
*/
override fun forceStart(pokemon: Pokemon) {
fun forceEvolve(pokemon: Pokemon) {
// This is a switch to enable/disable the evolution effect while we get particles improved
val useEvolutionEffect = true
@@ -145,21 +166,19 @@ class Evolution(
if (pokemonEntity == null || !useEvolutionEffect) {
pokemon.getOwnerPlayer()?.playNotifySound(CobblemonSounds.EVOLUTION_UI, SoundSource.PLAYERS, 1F, 1F)
evolutionMethod(pokemon)
super.forceStart(pokemon)
pokemon.getOwnerPlayer()?.sendSystemMessage(lang("ui.evolve.into", preEvoName, pokemon.species.translatedName))
} else {
pokemonEntity.entityData.set(PokemonEntity.TRANSFORMATION_STARTED, true)
pokemonEntity.entityData.set(PokemonEntity.EVOLUTION_STARTED, true)
pokemonEntity.navigation.stop()
pokemonEntity.after(1F) {
evolutionAnimation(pokemonEntity)
}
pokemonEntity.after(11.2F) {
evolutionMethod(pokemon)
super.forceStart(pokemon)
}
pokemonEntity.after(seconds = 12F) {
cryAnimation(pokemonEntity)
pokemonEntity.entityData.set(PokemonEntity.TRANSFORMATION_STARTED, false)
pokemonEntity.entityData.set(PokemonEntity.EVOLUTION_STARTED, false)
pokemon.getOwnerPlayer()?.sendSystemMessage(lang("ui.evolve.into", preEvoName, pokemon.species.translatedName))
}
}
@@ -199,7 +218,8 @@ class Evolution(
}
// we want to instantly tick for example you might only evolve your Bulbasaur at level 34 so Venusaur should be immediately available
pokemon.triggerTransformations(PassiveTrigger::class.java)
pokemon.evolutions.filterIsInstance<PassiveEvolution>().forEach { evolution -> evolution.attemptEvolution(pokemon) }
pokemon.lockedEvolutions.filterIsInstance<PassiveEvolution>().forEach { evolution -> evolution.attemptEvolution(pokemon) }
this.shed(pokemon)
@@ -218,12 +238,7 @@ class Evolution(
CobblemonEvents.POKEMON_GAINED.post(PokemonGainedEvent(pokemon.getOwnerUUID()!!, pokemon))
}
override fun convertToDisplay(pokemon: Pokemon): EvolutionDisplay {
val result = pokemon.clone(true)
this.result.apply(result)
val expectedDisplay = EvolutionDisplay(this.id, result)
val event = EvolutionDisplayEvent(result, expectedDisplay, this)
CobblemonEvents.EVOLUTION_DISPLAY.post(event)
return event.display
fun applyTo(pokemon: Pokemon) {
result.apply(pokemon)
}
}
}
@@ -0,0 +1,69 @@
/*
* 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.pokemon.evolution
import com.cobblemon.mod.common.api.pokemon.evolution.progress.EvolutionProgress
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.evolution.controller.ClientEvolutionController
import com.cobblemon.mod.common.pokemon.evolution.controller.ServerEvolutionController
/**
* Responsible for holding all available [EvolutionLike]s in the [Pokemon].
* Also handles all the networking behind them.
* For the Cobblemon default implementations see [ClientEvolutionController] & [ServerEvolutionController].
*
* @author Licious
* @since April 28th, 2022
*/
interface EvolutionController<T : EvolutionLike, out I : PreProcessor> : MutableSet<T> {
/**
* Resolves the [Pokemon] attached to this controller.
*
* @return The [Pokemon] this controller is attached to.
*/
fun pokemon(): Pokemon
/**
* Starts the given evolution on this controller.
* All the necessary networking will be handled.
*
* @param evolution The [EvolutionLike] starting.
*/
fun start(evolution: T)
/**
* Returns an immutable collection of [EvolutionProgress]es attached to this controller.
*
* @return The currently tracked [EvolutionProgress]es.
*/
fun progress(): Collection<EvolutionProgress<*>>
/**
* Attaches a [EvolutionProgress] to this controller.
*
* @param P The type of [EvolutionProgress].
* @param progress The [EvolutionProgress] being attached for tracking.
* @return The instance being tracked.
*/
fun <P : EvolutionProgress<*>> trackProgress(progress: P): P
/**
* Attempts to find an [EvolutionProgress] in [progress] or creates and tracks using [trackProgress].
*
* @param P The type of [EvolutionProgress].
* @param predicate The lambda attempting to find specific progress. This needs to ensure the [EvolutionProgress] is of type [P] or the cast will fail.
* @param progressFactory The lambda creating a new instance of type [P] if [predicate] can't find an element.
* @return The found match in [progress] or the created and now tracked with [trackProgress].
*/
fun <P : EvolutionProgress<*>> progressFirstOrCreate(predicate: (progress: EvolutionProgress<*>) -> Boolean, progressFactory: () -> P): P
fun asIntermediate(): I
}
@@ -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.api.pokemon.evolution
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.pokemon.evolution.CobblemonEvolutionDisplay
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
/**
* Represents an evolution of a [Pokemon], this is the client side counterpart of [Evolution].
* This has no attachments to any data regarding the evolution itself and only serves for display purposes and basic communication.
*
* @author Licious
* @since April 28th, 2022
*/
interface EvolutionDisplay : EvolutionLike {
/**
* The [Species] of the evolution result.
*/
val species: Species
/**
* The aspects of the evolution result.
* These are used by the client to resolve the entity it needs to display.
*/
val aspects: Set<String>
companion object {
@JvmStatic
val CODEC: Codec<EvolutionDisplay> = RecordCodecBuilder.create { instance ->
instance.group(
Codec.STRING.fieldOf("id").forGetter(EvolutionDisplay::id),
Species.BY_IDENTIFIER_CODEC.fieldOf("species").forGetter(EvolutionDisplay::species),
Codec.list(Codec.STRING).fieldOf("aspects").forGetter { it.aspects.toList() }
).apply(instance) { id, species, aspects -> CobblemonEvolutionDisplay(id, species, aspects.toSet()) }
}
}
}
@@ -6,20 +6,20 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.transformation
package com.cobblemon.mod.common.api.pokemon.evolution
/**
* Represents the base of a transformation.
* It can either be the full data container present on the server side [Transformation].
* Or the client side representation for display [TransformationDisplay].
* Represents the base of an evolution.
* It can either be the full data container present on the server side [Evolution].
* Or the client side representation for display [EvolutionDisplay].
*
* @author Licious
* @since April 28th, 2022
*/
interface TransformationLike {
interface EvolutionLike {
/**
* The unique id of the transformation.
* The unique id of the evolution.
* It should be human-readable, I.E pikachu_level
*/
val id: String
@@ -0,0 +1,59 @@
/*
* 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.pokemon.evolution
/**
* Responsible for holding the different [EvolutionController] implementations based on the logical side.
* If you haven't already checked your side previously you can use [isClient].
*
* @param C The type of [EvolutionLike] on the client side.
* @param S The type of [EvolutionLike] on the server side.
* @param CI The type of [PreProcessor] on the client side.
* @param SI The type of [PreProcessor] on the server side.
*
* @author Licious
* @since June 18th, 2022
*/
interface EvolutionProxy<C : EvolutionLike, S : EvolutionLike, CI : PreProcessor, SI : PreProcessor> {
/**
* Checks if the current logical side is the client.
*
* @return True if the current logical side is the client.
*/
fun isClient(): Boolean
/**
* Returns the current [EvolutionController].
* Keep in mind this will have type erasure and as such most operations will not be possible.
* It's recommended to work with [client] or [server] when possible.
*
* @return The current [EvolutionController].
*/
fun current(): EvolutionController<out EvolutionLike, *>
/**
* The client side implementation of the [EvolutionController].
*
* @throws [ClassCastException] if called from the server side.
*
* @return The client side implementation of the [EvolutionController] if possible.
*/
fun client(): EvolutionController<C, CI>
/**
* The server side implementation of the [EvolutionController].
*
* @throws [ClassCastException] if called from the client side.
*
* @return The server side implementation of the [EvolutionController] if possible.
*/
fun server(): EvolutionController<S, SI>
}
@@ -0,0 +1,43 @@
/*
* 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.pokemon.evolution
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.evolution.variants.LevelUpEvolution
/**
* Represents an evolution of a [Pokemon] that can occur without any additional context or actions.
* For the default implementation see [LevelUpEvolution].
*
* @author Licious
* @since March 19th, 2022
*/
interface PassiveEvolution : Evolution {
/**
* Checks if the given [Pokemon] satisfies the requirements.
* If yes the evolution will attempt to start.
*
* @param pokemon The [Pokemon] being tested.
* @return If the [Pokemon] will evolve.
*/
fun attemptEvolution(pokemon: Pokemon): Boolean {
if (super.test(pokemon)) {
return super.evolve(pokemon)
}
return false
}
/**
* If the evolution will disappear once the conditions are no longer met.
*/
val permanent: Boolean
}
@@ -6,11 +6,11 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.transformation.evolution
package com.cobblemon.mod.common.api.pokemon.evolution
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.pokemon.transformation.evolution.StandardPreEvolution
import com.cobblemon.mod.common.pokemon.evolution.StandardPreEvolution
/**
* Represents the previous stage in the evolutionary line of a given Pokémon.
@@ -6,14 +6,13 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.transformation.proxy
package com.cobblemon.mod.common.api.pokemon.evolution
import com.cobblemon.mod.common.api.pokemon.transformation.controller.TransformationController
import com.cobblemon.mod.common.pokemon.Pokemon
/**
* TODO
*/
fun interface PreProcessor {
fun create(pokemon: Pokemon): TransformationController<*, *>
fun create(pokemon: Pokemon): EvolutionController<*, *>
}
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.evolution.adapters
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.pokemon.evolution.adapters.CobblemonEvolutionAdapter
import com.google.gson.JsonDeserializer
import com.google.gson.JsonSerializer
import kotlin.reflect.KClass
/**
* Saves and loads [Evolution]s with JSON.
* For the default implementation see [CobblemonEvolutionAdapter].
*
* @author Licious
* @since March 20th, 2022
*/
interface EvolutionAdapter : JsonDeserializer<Evolution>, JsonSerializer<Evolution> {
/**
* Registers the given type of [Evolution] to it's associated ID for deserialization.
*
* @param T The type of [Evolution].
* @param id The id of the evolution event.
* @param type The [KClass] of the type.
*/
fun <T : Evolution> registerType(id: String, type: KClass<T>)
}
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.evolution.adapters
import com.cobblemon.mod.common.api.pokemon.evolution.requirement.EvolutionRequirement
import com.cobblemon.mod.common.pokemon.evolution.adapters.CobblemonRequirementAdapter
import com.google.gson.JsonDeserializer
import com.google.gson.JsonSerializer
import kotlin.reflect.KClass
/**
* Saves and loads [EvolutionRequirement]s with JSON.
* For the default implementation see [CobblemonRequirementAdapter].
*
* @author Licious
* @since March 21st, 2022
*/
interface RequirementAdapter : JsonDeserializer<EvolutionRequirement>, JsonSerializer<EvolutionRequirement> {
/**
* Registers the given type of [EvolutionRequirement] to it's associated ID for deserialization.
*
* @param T The type of [EvolutionRequirement].
* @param id The id of the evolution event.
* @param type The [KClass] of the type.
*/
fun <T : EvolutionRequirement> registerType(id: String, type: KClass<T>)
}
@@ -6,36 +6,25 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.transformation.progress
package com.cobblemon.mod.common.api.pokemon.evolution.progress
import com.cobblemon.mod.common.api.data.Identifiable
import com.cobblemon.mod.common.api.pokemon.transformation.controller.TransformationController
import com.cobblemon.mod.common.api.pokemon.transformation.requirement.TransformationRequirement
import com.cobblemon.mod.common.api.pokemon.transformation.trigger.TransformationTrigger
import com.cobblemon.mod.common.api.pokemon.evolution.EvolutionController
import com.cobblemon.mod.common.pokemon.Pokemon
import com.google.gson.JsonObject
import net.minecraft.nbt.CompoundTag
import net.minecraft.resources.ResourceLocation
/**
* Represents a container of progression towards the completion of a [TransformationRequirement].
* To register a transformation progress use [TransformationProgressTypes.registerType].
* Represents a container of progression towards the completion of an evolution requirement.
* To register an evolution progress use [EvolutionProgressTypes.registerType].
*
* @param T The type of the progression.
*
* @author Licious
* @since January 27th, 2023
*/
interface TransformationProgress<T : TransformationProgress.Progress> : Identifiable {
/**
* Represents progression towards a [TransformationRequirement].
*
* @property amount The value required to be complete.
*
* @author Segfault Guy
* @since April 18th, 2025
*/
interface Progress {
val amount: Int
}
interface EvolutionProgress<T> : Identifiable {
/**
* Gets the current progress of type [T].
@@ -58,7 +47,7 @@ interface TransformationProgress<T : TransformationProgress.Progress> : Identifi
fun reset()
/**
* Used to decide if the given progress should be kept in the [pokemon]s [TransformationController].
* Used to decide if the given progress should be kept in the [pokemon]s [EvolutionController].
*
* @param pokemon The [Pokemon] being queried.
* @return True if the progress is still required otherwise false.
@@ -66,16 +55,16 @@ interface TransformationProgress<T : TransformationProgress.Progress> : Identifi
fun shouldKeep(pokemon: Pokemon): Boolean
/**
* The [TransformationProgressType] of this.
* The [EvolutionProgressType] of this.
*
* @return The [TransformationProgressType] of this.
* @return The [EvolutionProgressType] of this.
*/
fun type(): TransformationProgressType<*>
fun type(): EvolutionProgressType<*>
companion object {
@JvmStatic
fun codec() = TransformationProgressTypes.codec()
fun codec() = EvolutionProgressTypes.codec()
}
}
@@ -6,10 +6,10 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.transformation.progress
package com.cobblemon.mod.common.api.pokemon.evolution.progress
import com.mojang.serialization.MapCodec
data class TransformationProgressType<T : TransformationProgress<*>>(
data class EvolutionProgressType<T : EvolutionProgress<*>>(
val codec: MapCodec<T>
)
@@ -0,0 +1,66 @@
/*
* 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.pokemon.evolution.progress
import com.cobblemon.mod.common.pokemon.evolution.controller.ServerEvolutionController
import com.cobblemon.mod.common.pokemon.evolution.progress.*
import com.cobblemon.mod.common.util.cobblemonResource
import com.mojang.serialization.Codec
import com.mojang.serialization.Lifecycle
import net.minecraft.core.MappedRegistry
import net.minecraft.core.Registry
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
import kotlin.jvm.Throws
object EvolutionProgressTypes {
private val REGISTRY: Registry<EvolutionProgressType<*>> = MappedRegistry(
ResourceKey.createRegistryKey(cobblemonResource("evolution_progress")), Lifecycle.stable()
)
@JvmStatic
val DAMAGE_TAKEN = this.registerType(DamageTakenEvolutionProgress.ID, EvolutionProgressType(DamageTakenEvolutionProgress.CODEC))
@JvmStatic
val DEFEAT = this.registerType(DefeatEvolutionProgress.ID, EvolutionProgressType(DefeatEvolutionProgress.CODEC))
@JvmStatic
val LAST_BATTLE_CRITICAL_HITS = this.registerType(LastBattleCriticalHitsEvolutionProgress.ID, EvolutionProgressType(LastBattleCriticalHitsEvolutionProgress.CODEC))
@JvmStatic
val RECOIL = this.registerType(RecoilEvolutionProgress.ID, EvolutionProgressType(RecoilEvolutionProgress.CODEC))
@JvmStatic
val USE_MOVE = this.registerType(UseMoveEvolutionProgress.ID, EvolutionProgressType(UseMoveEvolutionProgress.CODEC))
/**
* Registers an evolution progress.
*
* @param T The type of the [EvolutionProgress].
* @param id The [Identifier].
* @param type The [EvolutionProgressType] record.
* @return The registered [EvolutionProgressType] of type [T].
*
* @throws [IllegalStateException] if [Registry.register] fails.
*/
@Throws(IllegalStateException::class)
@JvmStatic
fun <T : EvolutionProgress<*>> registerType(id: ResourceLocation, type: EvolutionProgressType<T>): EvolutionProgressType<T> {
return Registry.register(REGISTRY, id, type)
}
/**
* Creates a codec instance of this registry.
*
* @return The generated [Codec] of [EvolutionProgress].
*/
fun codec(): Codec<EvolutionProgress<*>> = REGISTRY.byNameCodec().dispatch(
ServerEvolutionController.ID_KEY,
{ it.type() },
{ it.codec }
)
}
@@ -6,22 +6,23 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.transformation.requirement
package com.cobblemon.mod.common.api.pokemon.evolution.requirement
import com.cobblemon.mod.common.api.pokemon.transformation.Transformation
import com.cobblemon.mod.common.api.pokemon.transformation.trigger.ContextTrigger
import com.cobblemon.mod.common.api.pokemon.evolution.ContextEvolution
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.pokemon.Pokemon
/**
* Represents a requirement for a transformation.
* Represents a requirement for an evolution.
* Requirements are constant and will never change regardless of the backing action.
* For dynamic requirements see [ContextEvolution].
*
* See [Transformation.requirements] & [Transformation.test] for usage.
* See [Evolution.requirements] & [Evolution.test] for usage.
*
* @author Licious
* @since March 19th, 2022
*/
fun interface TransformationRequirement {
fun interface EvolutionRequirement {
/**
* Checks if the given [Pokemon] satisfies the requirement.
@@ -31,10 +32,4 @@ fun interface TransformationRequirement {
*/
fun check(pokemon: Pokemon): Boolean
/**
* Callback after the [Transformation] has been performed.
*
* @param pokemon The [Pokemon] being queried.
*/
fun fulfill(pokemon: Pokemon) {}
}
@@ -12,7 +12,7 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.pokemon.OriginalTrainerType
import com.cobblemon.mod.common.pokemon.transformation.requirements.LevelRequirement
import com.cobblemon.mod.common.pokemon.evolution.requirements.LevelRequirement
import kotlin.math.pow
import kotlin.math.roundToInt
@@ -9,7 +9,7 @@
package com.cobblemon.mod.common.api.pokemon.feature
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.aspect.FeatureAspectProvider
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
import com.cobblemon.mod.common.api.properties.CustomPokemonPropertyType
import com.cobblemon.mod.common.client.gui.summary.featurerenderers.SummarySpeciesFeatureRenderer
import com.cobblemon.mod.common.pokemon.Pokemon
@@ -29,11 +29,11 @@ import net.minecraft.network.RegistryFriendlyByteBuf
*/
open class ChoiceSpeciesFeatureProvider(
override var keys: List<String>,
override var default: String? = null,
var default: String? = null,
var choices: List<String> = listOf(),
override var isAspect: Boolean = true,
var isAspect: Boolean = true,
var aspectFormat: String = "{{choice}}"
) : SynchronizedSpeciesFeatureProvider<StringSpeciesFeature>, CustomPokemonPropertyType<StringSpeciesFeature>, FeatureAspectProvider {
) : SynchronizedSpeciesFeatureProvider<StringSpeciesFeature>, CustomPokemonPropertyType<StringSpeciesFeature>, AspectProvider {
override var needsKey = true
override var visible = false
fun getAspect(feature: StringSpeciesFeature) = aspectFormat.substitute("choice", feature.value)
@@ -76,28 +76,6 @@ open class ChoiceSpeciesFeatureProvider(
return aspects
}
fun parseValue(aspect: String): String? {
if (!matches(aspect)) return null
val head = aspectFormat.substringBefore("{{choice}}")
val tail = aspectFormat.substringAfter("{{choice}}").takeIf { it.isNotBlank() }
var value = aspect.substringAfter(head)
tail?.let { value = value.substringBefore(it) }
return value
}
override fun matches(aspect: String): Boolean = this.isAspect && this.getAllAspects().contains(aspect)
override fun from(aspect: String): SpeciesFeature<*>? {
val value = parseValue(aspect)
return if (value != null && choices.contains(value)) this.fromString(value) else null
}
override fun set(pokemon: Pokemon, aspect: String) {
val value = parseValue(aspect)
if (value != null && choices.contains(value)) this.get(pokemon)?.value = value
}
override fun examples() = choices
internal constructor(): this(emptyList())
@@ -145,9 +123,7 @@ open class ChoiceSpeciesFeatureProvider(
override fun provide(pokemon: Pokemon): Set<String> {
return if (isAspect) {
get(pokemon)?.let {
setOf(getAspect(it))
} ?: emptySet()
get(pokemon)?.let { setOf(getAspect(it)) } ?: emptySet()
} else {
emptySet()
}
@@ -9,7 +9,7 @@
package com.cobblemon.mod.common.api.pokemon.feature
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.aspect.FeatureAspectProvider
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
import com.cobblemon.mod.common.api.properties.CustomPokemonPropertyType
import com.cobblemon.mod.common.client.gui.summary.featurerenderers.SummarySpeciesFeatureRenderer
@@ -30,74 +30,65 @@ import kotlin.random.Random
* @author Hiroku
* @since May 13th, 2022
*/
open class FlagSpeciesFeature(override val name: String) : SynchronizedSpeciesFeature<Boolean>, CustomPokemonProperty {
open class FlagSpeciesFeature(override val name: String) : SynchronizedSpeciesFeature, CustomPokemonProperty {
constructor(name: String, enabled: Boolean): this(name) {
this.value = enabled
this.enabled = enabled
}
override var value: Boolean = false
var enabled = false
override fun saveToNBT(pokemonNBT: CompoundTag): CompoundTag {
pokemonNBT.putBoolean(name, value)
pokemonNBT.putBoolean(name, enabled)
return pokemonNBT
}
override fun loadFromNBT(pokemonNBT: CompoundTag): SpeciesFeature<Boolean> {
value = if (pokemonNBT.contains(name)) pokemonNBT.getBoolean(name) else value
override fun loadFromNBT(pokemonNBT: CompoundTag): SpeciesFeature {
enabled = if (pokemonNBT.contains(name)) pokemonNBT.getBoolean(name) else enabled
return this
}
override fun saveToJSON(pokemonJSON: JsonObject): JsonObject {
pokemonJSON.addProperty(name, value)
pokemonJSON.addProperty(name, enabled)
return pokemonJSON
}
override fun loadFromJSON(pokemonJSON: JsonObject): SpeciesFeature<Boolean> {
override fun loadFromJSON(pokemonJSON: JsonObject): SpeciesFeature {
val isEnabled = pokemonJSON.get(name)?.asBoolean
value = isEnabled ?: this.value
enabled = isEnabled ?: this.enabled
return this
}
override fun saveToBuffer(buffer: RegistryFriendlyByteBuf, toClient: Boolean) {
buffer.writeBoolean(value)
buffer.writeBoolean(enabled)
}
override fun loadFromBuffer(buffer: RegistryFriendlyByteBuf) {
value = buffer.readBoolean()
enabled = buffer.readBoolean()
}
override fun asString() = "$name=$value"
override fun asString() = "$name=$enabled"
override fun apply(pokemon: Pokemon) {
val featureProvider = SpeciesFeatures.getFeature(name) ?: return
if (featureProvider in SpeciesFeatures.getFeaturesFor(pokemon.species)) {
val existingFeature = pokemon.getFeature<FlagSpeciesFeature>(name)
if (existingFeature != null) {
existingFeature.value = value
existingFeature.enabled = enabled
} else {
pokemon.features.add(FlagSpeciesFeature(name, value))
pokemon.features.add(FlagSpeciesFeature(name, enabled))
}
pokemon.updateAspects()
}
}
override fun matches(pokemon: Pokemon) = pokemon.getFeature<FlagSpeciesFeature>(name)?.value == value
override fun matches(pokemon: Pokemon) = pokemon.getFeature<FlagSpeciesFeature>(name)?.enabled == enabled
}
class FlagSpeciesFeatureProvider : SynchronizedSpeciesFeatureProvider<FlagSpeciesFeature>, CustomPokemonPropertyType<FlagSpeciesFeature>, FeatureAspectProvider {
class FlagSpeciesFeatureProvider : SynchronizedSpeciesFeatureProvider<FlagSpeciesFeature>, CustomPokemonPropertyType<FlagSpeciesFeature>, AspectProvider {
override var keys: List<String> = emptyList()
// Uses get() = true because that way there's no backing field. It MUST be true, this way no JSON trickery will overwrite it
override val needsKey get() = true
override var default: String? = null
override var isAspect = true
override fun matches(aspect: String): Boolean = this.isAspect && this.keys.first() == aspect
override fun from(aspect: String): SpeciesFeature<*>? = if (matches(aspect)) fromString(null) else null
override fun set(pokemon: Pokemon, aspect: String) {
this.get(pokemon)?.value = true
}
var default: String? = null
var isAspect = true
override var visible: Boolean = false
override fun invoke(buffer: RegistryFriendlyByteBuf, name: String): FlagSpeciesFeature? {
@@ -179,7 +170,7 @@ class FlagSpeciesFeatureProvider : SynchronizedSpeciesFeatureProvider<FlagSpecie
}
override fun provide(pokemon: Pokemon): Set<String> {
return if (isAspect && pokemon.getFeature<FlagSpeciesFeature>(keys.first())?.value == true) {
return if (isAspect && pokemon.getFeature<FlagSpeciesFeature>(keys.first())?.enabled == true) {
setOf(keys.first())
} else {
emptySet()
@@ -187,7 +178,7 @@ class FlagSpeciesFeatureProvider : SynchronizedSpeciesFeatureProvider<FlagSpecie
}
override fun provide(properties: PokemonProperties): Set<String> {
return if (isAspect && properties.customProperties.filterIsInstance<FlagSpeciesFeature>().find { it.name == keys.first() }?.value == true) {
return if (isAspect && properties.customProperties.filterIsInstance<FlagSpeciesFeature>().find { it.name == keys.first() }?.enabled == true) {
setOf(keys.first())
} else {
emptySet()
@@ -84,7 +84,7 @@ object GlobalSpeciesFeatures : JsonDataRegistry<SpeciesFeatureProvider<*>> {
}
fun register(name: String, provider: SpeciesFeatureProvider<*>) = register(name, provider, isCoded = true)
fun <T : SpeciesFeature<*>> register(name: String, providerLambda: () -> T) {
fun <T : SpeciesFeature> register(name: String, providerLambda: () -> T) {
register(name, object : SpeciesFeatureProvider<T> {
override fun invoke(pokemon: Pokemon) = providerLambda()
override fun invoke(nbt: CompoundTag) = providerLambda().apply { loadFromNBT(nbt) }
@@ -26,8 +26,8 @@ import net.minecraft.world.phys.Vec3
* A species feature value that's just an integer. Complex stuff.
* @author Hiroku
*/
class IntSpeciesFeature(override var name: String) : SynchronizedSpeciesFeature<Int>, CustomPokemonProperty {
override var value = 0
class IntSpeciesFeature(override var name: String) : SynchronizedSpeciesFeature, CustomPokemonProperty {
var value = 0
constructor() : this("")
constructor(name: String, value: Int) : this(name) {
@@ -39,7 +39,7 @@ class IntSpeciesFeature(override var name: String) : SynchronizedSpeciesFeature<
return pokemonNBT
}
override fun loadFromNBT(pokemonNBT: CompoundTag): SynchronizedSpeciesFeature<Int> {
override fun loadFromNBT(pokemonNBT: CompoundTag): SynchronizedSpeciesFeature {
value = pokemonNBT.getInt(name)
return this
}
@@ -49,7 +49,7 @@ class IntSpeciesFeature(override var name: String) : SynchronizedSpeciesFeature<
return pokemonJSON
}
override fun loadFromJSON(pokemonJSON: JsonObject): SpeciesFeature<Int> {
override fun loadFromJSON(pokemonJSON: JsonObject): SpeciesFeature {
value = pokemonJSON.get(name).asInt
return this
}
@@ -24,11 +24,10 @@ import net.minecraft.nbt.CompoundTag
* @author Hiroku
* @since May 13th, 2022
*/
interface SpeciesFeature<T> {
interface SpeciesFeature {
val name: String
var value: T
fun saveToNBT(pokemonNBT: CompoundTag): CompoundTag
fun loadFromNBT(pokemonNBT: CompoundTag): SpeciesFeature<T>
fun loadFromNBT(pokemonNBT: CompoundTag): SpeciesFeature
fun saveToJSON(pokemonJSON: JsonObject): JsonObject
fun loadFromJSON(pokemonJSON: JsonObject): SpeciesFeature<T>
fun loadFromJSON(pokemonJSON: JsonObject): SpeciesFeature
}
@@ -21,7 +21,7 @@ import net.minecraft.nbt.CompoundTag
* @author Hiroku
* @since November 29th, 2022
*/
interface SpeciesFeatureProvider<T : SpeciesFeature<*>> {
interface SpeciesFeatureProvider<T : SpeciesFeature> {
/**
* Attempts to create a species feature for the given [Pokemon]. It is appropriate to check for an existing feature
* that is compatible with this type and transfer / retain that feature instead of constructing a new one. For example
@@ -24,14 +24,14 @@ import net.minecraft.network.RegistryFriendlyByteBuf
*/
class StringSpeciesFeature(
override val name: String,
override var value: String
) : SynchronizedSpeciesFeature<String>, CustomPokemonProperty {
var value: String
) : SynchronizedSpeciesFeature, CustomPokemonProperty {
override fun saveToNBT(pokemonNBT: CompoundTag): CompoundTag {
pokemonNBT.putString(name, value)
return pokemonNBT
}
override fun loadFromNBT(pokemonNBT: CompoundTag): SpeciesFeature<String> {
override fun loadFromNBT(pokemonNBT: CompoundTag): SpeciesFeature {
value = pokemonNBT.getString(name)?.takeIf { it.isNotBlank() }?.lowercase() ?: return this
return this
}
@@ -41,7 +41,7 @@ class StringSpeciesFeature(
return pokemonJSON
}
override fun loadFromJSON(pokemonJSON: JsonObject): SpeciesFeature<String> {
override fun loadFromJSON(pokemonJSON: JsonObject): SpeciesFeature {
value = pokemonJSON.get(name)?.asString?.lowercase() ?: return this
return this
}
@@ -16,4 +16,4 @@ import com.cobblemon.mod.common.api.serialization.BufferSerializer
* @author Hiroku
* @since November 13th, 2023
*/
interface SynchronizedSpeciesFeature<T> : SpeciesFeature<T>, BufferSerializer
interface SynchronizedSpeciesFeature : SpeciesFeature, BufferSerializer
@@ -22,7 +22,7 @@ import net.minecraft.network.RegistryFriendlyByteBuf
* @author Hiroku
* @since November 13th, 2023
*/
interface SynchronizedSpeciesFeatureProvider<T : SynchronizedSpeciesFeature<*>> : SpeciesFeatureProvider<T>, BufferSerializer {
interface SynchronizedSpeciesFeatureProvider<T : SynchronizedSpeciesFeature> : SpeciesFeatureProvider<T>, BufferSerializer {
var visible: Boolean
operator fun invoke(buffer: RegistryFriendlyByteBuf, name: String): T?
/** Gets the feature from this Pokémon, if it has been created yet. */
@@ -1,88 +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.pokemon.transformation
import com.cobblemon.mod.common.api.pokemon.transformation.controller.TransformationController
import com.cobblemon.mod.common.api.pokemon.transformation.requirement.TransformationRequirement
import com.cobblemon.mod.common.api.pokemon.transformation.trigger.ContextTrigger
import com.cobblemon.mod.common.api.pokemon.transformation.trigger.TransformationTrigger
import com.cobblemon.mod.common.pokemon.Pokemon
/**
* Represents a transformation of a [Pokemon]. This is the server side counterpart of [TransformationDisplay] and serves as
* the driver of the transformation. These are triggered by a player-driven or world-driven event.
*
* @author Segfault Guy
* @since Sept 8th, 2023
*/
interface Transformation : TransformationLike {
/** The [TransformationTrigger] to attempt this transformation. */
val trigger: TransformationTrigger
/** The [TransformationRequirement]s that need to be satisfied for this transformation. */
val requirements: Set<TransformationRequirement>
/** If this transformation can be deferred by the user and started whenever. */
val optional: Boolean
/** If this transformation needs its requirements reevaluated to remain valid in a [TransformationController]. */
val reevaluate: Boolean
/** Checks if all [TransformationRequirement]s are met. */
fun checkRequirements(pokemon: Pokemon) = this.requirements.all { requirement -> requirement.check(pokemon) }
/**
* Tests if the given [Pokemon] passes all the conditions and is ready to transform.
*
* @param pokemon The [Pokemon] being queried.
* @param context The optional [ContextTrigger.Context] needed to transform.
* @return Whether the [Transformation] can start.
*/
fun test(pokemon: Pokemon, context: ContextTrigger.Context?) = this.trigger.testTrigger(context) && this.checkRequirements(pokemon)
/**
* Starts this transformation if conditions are met.
*
* @param pokemon The [Pokemon] being transformed.
* @param context The optional [ContextTrigger.Context] needed to transform.
* @return Whether the [Transformation] was successful.
*/
fun start(pokemon: Pokemon, context: ContextTrigger.Context?): Boolean {
if (test(pokemon, context)) {
forceStart(pokemon)
return true
}
return false
}
/**
* Starts this transformation without evaluating the requirements.
*
* @param pokemon The [Pokemon] being transformed.
*/
fun forceStart(pokemon: Pokemon) { this.requirements.forEach { it.fulfill(pokemon) } }
/**
* Converts this transformation to its client side representation.
*
* @param pokemon The [Pokemon] being transformed.
* @return The [TransformationDisplay] representing this transformation on the client.
*/
fun convertToDisplay(pokemon: Pokemon): TransformationDisplay
/**
* Validates whether a transformation still meets the [TransformationRequirement]s.
* If [Transformation.reevaluate] is false, this check will be bypassed.
*
* @param pokemon the [Pokemon] to test against.
* @return Whether this transformation is still valid and can be made for [pokemon].
*/
fun reevaluate(pokemon: Pokemon) = !this.reevaluate || this.checkRequirements(pokemon)
}
@@ -1,33 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.transformation
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.Species
/**
* Represents a transformation of a [Pokemon], this is the client side counterpart of [Transformation].
* This has no attachments to any data regarding the transformation itself and only serves for display purposes and basic communication.
*
* @author Licious
* @since April 28th, 2022
*/
interface TransformationDisplay : TransformationLike {
/**
* The [Species] of the transformation result.
*/
val species: Species
/**
* The aspects of the transformation result.
* These are used by the client to resolve the entity it needs to display.
*/
val aspects: Set<String>
}
@@ -1,35 +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.pokemon.transformation.adapters
import com.cobblemon.mod.common.api.pokemon.transformation.requirement.TransformationRequirement
import com.cobblemon.mod.common.pokemon.transformation.adapters.CobblemonRequirementAdapter
import com.google.gson.JsonDeserializer
import com.google.gson.JsonSerializer
import kotlin.reflect.KClass
/**
* Saves and loads [TransformationRequirement]s with JSON.
* For the default implementation see [CobblemonRequirementAdapter].
*
* @author Licious
* @since March 21st, 2022
*/
interface RequirementAdapter : JsonDeserializer<TransformationRequirement>, JsonSerializer<TransformationRequirement> {
/**
* Registers the given type of [TransformationRequirement] to it's associated ID for deserialization.
*
* @param T The type of [TransformationRequirement].
* @param id The id of the evolution event.
* @param type The [KClass] of the type.
*/
fun <T : TransformationRequirement> registerType(id: String, type: KClass<T>)
}
@@ -1,35 +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.pokemon.transformation.adapters
import com.cobblemon.mod.common.api.pokemon.transformation.trigger.TransformationTrigger
import com.cobblemon.mod.common.pokemon.transformation.adapters.CobblemonTriggerAdapter
import com.google.gson.JsonDeserializer
import com.google.gson.JsonSerializer
import kotlin.reflect.KClass
/**
* Saves and loads [TransformationTrigger]s with JSON.
* For the default implementation see [CobblemonTriggerAdapter].
*
* @author Licious
* @since March 20th, 2022
*/
interface TriggerAdapter : JsonDeserializer<TransformationTrigger>, JsonSerializer<TransformationTrigger> {
/**
* Registers the given type of [TransformationTrigger] to it's associated ID for deserialization.
*
* @param T The type of [TransformationTrigger].
* @param id The id of the evolution event.
* @param type The [KClass] of the type.
*/
fun <T : TransformationTrigger> registerType(id: String, type: KClass<T>)
}
@@ -1,106 +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.pokemon.transformation.controller
import com.cobblemon.mod.common.api.pokemon.transformation.TransformationLike
import com.cobblemon.mod.common.api.pokemon.transformation.proxy.PreProcessor
import com.cobblemon.mod.common.api.pokemon.transformation.progress.TransformationProgress
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.transformation.controller.CobblemonClientTransformationController
import com.cobblemon.mod.common.pokemon.transformation.controller.CobblemonServerTransformationController
/**
* Responsible for holding all available [TransformationLike]s in the [Pokemon].
* Also handles all the networking behind them.
* For the Cobblemon default implementations see [CobblemonClientTransformationController] & [CobblemonServerTransformationController].
*
* @author Licious
* @since April 28th, 2022
*/
interface TransformationController<T : TransformationLike, out I : PreProcessor> : MutableSet<T> {
/**
* Resolves the [Pokemon] attached to this controller.
*
* @return The [Pokemon] this controller is attached to.
*/
fun pokemon(): Pokemon
/**
* Starts the given transformation on this controller.
* All the necessary networking will be handled.
*
* @param transformation The [TransformationLike] starting.
*/
fun start(transformation: T)
/** Reverts the active [TransformationLike] for this controller's [pokemon]. */
fun revert() // TODO dunno if i like this
/**
* Returns an immutable collection of [TransformationProgress]es attached to this controller.
*
* @return The currently tracked [TransformationProgress]es.
*/
fun progress(): Collection<TransformationProgress<*>>
/**
* Attaches a [TransformationProgress] to this controller.
*
* @param P The type of [TransformationProgress].
* @param progress The [TransformationProgress] being attached for tracking.
* @return The instance being tracked.
*/
fun <P : TransformationProgress<*>> trackProgress(progress: P): P
/**
* Attempts to find an [TransformationProgress] in [progress] or creates and tracks using [trackProgress].
*
* @param P The type of [TransformationProgress].
* @param predicate The lambda attempting to find specific progress. This needs to ensure the [TransformationProgress] is of type [P] or the cast will fail.
* @param progressFactory The lambda creating a new instance of type [P] if [predicate] can't find an element.
* @return The found match in [progress] or the created and now tracked with [trackProgress].
*/
fun <P : TransformationProgress<*>> progressFirstOrCreate(predicate: (progress: TransformationProgress<*>) -> Boolean, progressFactory: () -> P): P
fun asIntermediate(): I
/**
* Guard for [start] to prevent a transformation from executing while the [pokemon] is busy or if the transformation
* is not in the controller.
*
* @param transformation the [TransformationLike] to validate.
* @return whether this controller is capable of starting the transformation.
*/
fun canStart(transformation: T): Boolean
/**
* Whether this controller is capable of executing [start] for a transformation. This checks if the [pokemon] is busy
* or if there is a transformation ready in the controller.
*
* @return whether this controller is capable of starting a transformation.
*/
fun canStart(): Boolean
/**
* Validates the [TransformationLike] currently active for this controller's [pokemon] and determines whether it can be
* reverted and if the pokemon is capable of reverting right now.
*
* @return whether the transformation can be reverted.
*/
fun canRevert(): Boolean
/**
* Retrieves the [TransformationLike] in this controller that matches the provided id.
*
* @param id the identifier of the transformation.
* @return the transformation matching this id.
*/
fun getById(id: String) = this.firstOrNull { transformation -> transformation.id.equals(id, true) }
}
@@ -1,72 +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.pokemon.transformation.progress
import com.cobblemon.mod.common.pokemon.transformation.progress.*
import com.cobblemon.mod.common.util.cobblemonResource
import com.mojang.serialization.Codec
import com.mojang.serialization.Lifecycle
import net.minecraft.core.MappedRegistry
import net.minecraft.core.Registry
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
import kotlin.jvm.Throws
/**
* Registry of [TransformationProgressType]s.
*
* @author Licious
* @since July 6th, 2024
*/
object TransformationProgressTypes {
private const val ID_KEY = "id"
private val REGISTRY: Registry<TransformationProgressType<*>> = MappedRegistry(
ResourceKey.createRegistryKey(cobblemonResource("transformation_progress")), Lifecycle.stable()
)
@JvmStatic
val DAMAGE_TAKEN = this.registerType(DamageTakenTransformationProgress.ID, TransformationProgressType(DamageTakenTransformationProgress.CODEC))
@JvmStatic
val DEFEAT = this.registerType(DefeatTransformationProgress.ID, TransformationProgressType(DefeatTransformationProgress.CODEC))
@JvmStatic
val LAST_BATTLE_CRITICAL_HITS = this.registerType(LastBattleCriticalHitsTransformationProgress.ID, TransformationProgressType(LastBattleCriticalHitsTransformationProgress.CODEC))
@JvmStatic
val RECOIL = this.registerType(RecoilTransformationProgress.ID, TransformationProgressType(RecoilTransformationProgress.CODEC))
@JvmStatic
val USE_MOVE = this.registerType(UseMoveTransformationProgress.ID, TransformationProgressType(UseMoveTransformationProgress.CODEC))
/**
* Registers a transformation progress.
*
* @param T The type of the [TransformationProgress].
* @param id The [ResourceLocation].
* @param type The [TransformationProgressType] record.
* @return The registered [TransformationProgressType] of type [T].
*
* @throws [IllegalStateException] if [Registry.register] fails.
*/
@Throws(IllegalStateException::class)
@JvmStatic
fun <T : TransformationProgress<*>> registerType(id: ResourceLocation, type: TransformationProgressType<T>): TransformationProgressType<T> {
return Registry.register(REGISTRY, id, type)
}
/**
* Creates a codec instance of this registry.
*
* @return The generated [Codec] of [TransformationProgress].
*/
fun codec(): Codec<TransformationProgress<*>> = REGISTRY.byNameCodec().dispatch(
ID_KEY,
{ it.type() },
{ it.codec }
)
}
@@ -1,64 +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.pokemon.transformation.proxy
import com.cobblemon.mod.common.api.pokemon.transformation.Transformation
import com.cobblemon.mod.common.api.pokemon.transformation.TransformationDisplay
import com.cobblemon.mod.common.api.pokemon.transformation.TransformationLike
import com.cobblemon.mod.common.api.pokemon.transformation.controller.TransformationController
/**
* Responsible for holding the different [TransformationController] implementations based on the logical side.
* If you haven't already checked your side previously you can use [isClient].
*
* @param C The type of [TransformationDisplay] on the client side.
* @param S The type of [Transformation] on the server side.
* @param CI The type of [PreProcessor] on the client side.
* @param SI The type of [PreProcessor] on the server side.
*
* @author Licious, Segfault Guy
* @since January 19th, 2025
*/
interface TransformationProxy<C : TransformationDisplay, S : Transformation, CI : PreProcessor, SI : PreProcessor> {
/**
* Checks if the current logical side is the client.
*
* @return True if the current logical side is the client.
*/
fun isClient(): Boolean
/**
* Returns the current [TransformationController].
* Keep in mind this will have type erasure and as such most operations will not be possible.
* It's recommended to work with [client] or [server] when possible.
*
* @return The current [TransformationController].
*/
fun current(): TransformationController<out TransformationLike, *>
/**
* The client side implementation of the [TransformationController].
*
* @throws [ClassCastException] if called from the server side.
*
* @return The client side implementation of the [TransformationController] if possible.
*/
fun client(): TransformationController<C, CI>
/**
* The server side implementation of the [TransformationController].
*
* @throws [ClassCastException] if called from the client side.
*
* @return The server side implementation of the [TransformationController] if possible.
*/
fun server(): TransformationController<S, SI>
}
@@ -1,57 +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.pokemon.transformation.requirement
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.server.level.ServerPlayer
/**
* A [TransformationRequirement] that expects the [Pokemon] to have an owner.
* It can be the [ServerPlayer] or the [NPCEntity] that owns it.
*
* Whenever an owner can't be resolved [TransformationRequirement.check] will never succeed.
*
* @author Polymeta
* @since May 6th, 2025
*/
interface OwnerQueryRequirement : TransformationRequirement {
override fun check(pokemon: Pokemon): Boolean {
val owner = pokemon.getOwnerEntity() ?: return false
return when (owner) {
is ServerPlayer -> {
this.checkPlayer(pokemon, owner)
}
is NPCEntity -> {
this.checkNPC(pokemon, owner)
}
else -> false
}
}
/**
* Checks if the given [Pokemon] & [ServerPlayer] satisfies the requirement.
*
* @param pokemon The [Pokemon] being queried.
* @param owner The [ServerPlayer] that owns this [Pokemon].
* @return If the requirement was satisfied.
*/
fun checkPlayer(pokemon: Pokemon, owner: ServerPlayer): Boolean
/**
* Checks if the given [Pokemon] & [NPCEntity] satisfies the requirement.
* This method mainly exists as NPCs are different to players in a lot of ways (e.g., lack of advancements),
* so they need to be handled differently, either by checking an NPC config or always returning true/false
*
* @param pokemon The [Pokemon] being queried.
* @param owner The [NPCEntity] that owns this [Pokemon].
* @return If the requirement was satisfied.
*/
fun checkNPC(pokemon: Pokemon, owner: NPCEntity): Boolean
}
@@ -1,55 +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.pokemon.transformation.trigger
import com.cobblemon.mod.common.api.pokemon.transformation.Transformation
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.transformation.triggers.ItemInteractionTrigger
import com.cobblemon.mod.common.pokemon.transformation.triggers.TradeTrigger
/**
* Represents a [TransformationTrigger] of a [Pokemon] that can only occur during specific actions and with added context.
* For the default implementations see [ItemInteractionTrigger] & [TradeTrigger].
*
* @param TC The context that is serialized from JSON during species loading. This is what the [Context] provided
* to [testTrigger] is expected to match against.
*
* @author Licious
* @since March 19th, 2022
*/
interface ContextTrigger<TC>: TransformationTrigger {
/**
* Represents context provided for [TransformationTrigger] evaluation.
*
* @author Segfault Guy
* @since October 15th, 2023
*/
interface Context
/** The target context required for this [Transformation] to be successfully triggered. */
val requiredContext: TC
/**
* Checks if the given context is valid for the [requiredContext].
*
* @param context The [Context] given at runtime when querying the [Transformation].
* @return If the context matched the [requiredContext].
*/
fun testContext(context: Context): Boolean = false
/**
* Evaluates the triggering condition of a [Transformation].
*
* @param context The [Context] of the [Transformation].
* @return Whether the triggering condition is satisfied. If [context] is null, bypasses the condition check.
*/
override fun testTrigger(context: Context?) = context?.let { testContext(it) } ?: true
}
@@ -1,23 +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.pokemon.transformation.trigger
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.transformation.triggers.LevelUpTrigger
/**
* Represents a [TransformationTrigger] of a [Pokemon] that is attempted periodically without any additional context or actions.
* For the default implementation see [LevelUpTrigger].
*
* Triggers that implement this marker will be tested every second.
*
* @author Licious
* @since March 19th, 2022
*/
interface PassiveTrigger : TransformationTrigger
@@ -1,29 +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.pokemon.transformation.trigger
import com.cobblemon.mod.common.api.pokemon.transformation.Transformation
/**
* Represents a condition that starts a [Transformation].
*
* @author Segfault Guy
* @since Sept 8th, 2023
*/
interface TransformationTrigger {
/**
* Evaluates the triggering condition of a [Transformation].
*
* @param context The optional [ContextTrigger.Context] for [ContextTrigger] evaluation.
* @return Whether the triggering condition is satisfied.
*/
fun testTrigger(context: ContextTrigger.Context?): Boolean = true
}
@@ -41,6 +41,9 @@ class RidingProperties(
}
}
val canRide: Boolean
get() = seats.isNotEmpty() && behaviour != null
fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeMap(
stats,
@@ -50,6 +53,7 @@ class RidingProperties(
buffer.writeCollection(seats) { _, seat -> seat.encode(buffer) }
buffer.writeCollection(conditions) { _, condition -> buffer.writeString(condition.getString()) }
buffer.writeNullable(behaviour) { _, behaviour ->
buffer.writeResourceLocation(behaviour.key)
behaviour.encode(buffer)
}
}
@@ -19,4 +19,4 @@ enum class RidingStyle {
LAND,
LIQUID,
AIR
}
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.api.riding.behaviour
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.resources.ResourceLocation
@@ -29,8 +28,6 @@ import net.minecraft.world.phys.Vec3
interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBehaviourState> {
val key: ResourceLocation
fun getRidingStyle(settings: Settings, state: State): RidingStyle
fun isActive(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun tick(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player, input: Vec3) {}
@@ -88,6 +85,6 @@ interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBeha
fun shouldRotatePlayerHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun createDefaultState(settings: Settings): State
fun createDefaultState(): State
}
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.riding.behaviour
import com.cobblemon.mod.common.api.net.Decodable
import com.cobblemon.mod.common.api.net.Encodable
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.phys.Vec3
/**
* Represents static settings of a riding behaviour.
@@ -8,10 +8,15 @@
package com.cobblemon.mod.common.api.riding.behaviour
import com.cobblemon.mod.common.api.net.Decodable
import com.cobblemon.mod.common.api.net.Encodable
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.ifClient
import com.cobblemon.mod.common.util.ifServer
import net.minecraft.client.player.RemotePlayer
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.world.phys.Vec3
import net.minecraft.network.RegistryFriendlyByteBuf
import kotlin.reflect.KProperty
/**
* Represents the state of a Pokemon when being ridden.
@@ -22,38 +27,18 @@ import net.minecraft.world.phys.Vec3
*
* @author landonjw
*/
open class RidingBehaviourState {
open val rideVelocity: SidedRidingState<Vec3> = ridingState(Vec3.ZERO, Side.BOTH)
open val stamina: SidedRidingState<Float> = ridingState(1.0F, Side.BOTH)
open fun reset() {
rideVelocity.set(Vec3.ZERO, true)
stamina.set(1.0F, true)
}
open fun copy(): RidingBehaviourState {
val copy = RidingBehaviourState()
copy.rideVelocity.set(rideVelocity.get(), true)
copy.stamina.set(stamina.get(), true)
return copy
}
open fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous.rideVelocity.get() != rideVelocity.get()) return true
if (previous.stamina.get() != stamina.get()) return true
return false
}
open fun encode(buffer: FriendlyByteBuf) {
buffer.writeVec3(rideVelocity.get())
buffer.writeFloat(stamina.get())
}
open fun decode(buffer: FriendlyByteBuf) {
rideVelocity.set(buffer.readVec3(), true)
stamina.set(buffer.readFloat(), true)
}
interface RidingBehaviourState : Encodable, Decodable {
fun reset()
fun copy(): RidingBehaviourState
fun shouldSync(previous: RidingBehaviourState): Boolean
}
object NoState : RidingBehaviourState {
override fun encode(buffer: RegistryFriendlyByteBuf) = Unit
override fun decode(buffer: RegistryFriendlyByteBuf) = Unit
override fun copy() = NoState
override fun reset() = Unit
override fun shouldSync(previous: RidingBehaviourState) = false
}
fun <T> ridingState(value: T, side: Side) = SidedRidingState(value, side)
@@ -9,32 +9,32 @@
package com.cobblemon.mod.common.api.riding.behaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.*
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeBehaviour
import net.minecraft.resources.ResourceLocation
object RidingBehaviours {
val behaviours = mutableMapOf<ResourceLocation, RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState>>()
val behaviours = mutableMapOf<ResourceLocation, RidingBehaviour<*, *>>()
init {
register(BirdAirBehaviour.KEY, BirdAirBehaviour())
register(DolphinBehaviour.KEY, DolphinBehaviour())
register(FallToGlideCompositeBehaviour.KEY, FallToGlideCompositeBehaviour())
register(GenericLandBehaviour.KEY, GenericLandBehaviour())
register(GenericSwimBehaviour.KEY, GenericSwimBehaviour())
register(GliderAirBehaviour.KEY, GliderAirBehaviour())
register(HelicopterBehaviour.KEY, HelicopterBehaviour())
register(JetAirBehaviour.KEY, JetAirBehaviour())
register(JumpToFlightCompositeBehaviour.KEY, JumpToFlightCompositeBehaviour())
register(RunToJetCompositeBehaviour.KEY, RunToJetCompositeBehaviour())
register(SwimDashBehaviour.KEY, SwimDashBehaviour())
register(VehicleLandBehaviour.KEY, VehicleLandBehaviour())
register(HoverBehaviour.KEY, HoverBehaviour())
register(CompositeBehaviour.KEY, CompositeBehaviour())
}
fun register(key: ResourceLocation, behaviour: RidingBehaviour<out RidingBehaviourSettings, out RidingBehaviourState>) {
fun register(key: ResourceLocation, behaviour: RidingBehaviour<*, *>) {
if (behaviours.contains(key)) error("Behaviour already registered to key $key")
behaviours[key] = RidingController(behaviour) as RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState>
behaviours[key] = RidingController(behaviour)
}
fun get(key: ResourceLocation): RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState> {
fun get(key: ResourceLocation): RidingBehaviour<*, *> {
if (!behaviours.contains(key)) error("Behaviour not registered to key $key")
return behaviours[key]!!
}
@@ -8,10 +8,8 @@
package com.cobblemon.mod.common.api.riding.behaviour
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
@@ -26,10 +24,6 @@ import net.minecraft.world.phys.Vec3
class RidingController<Settings : RidingBehaviourSettings, State : RidingBehaviourState>(val behaviour: RidingBehaviour<Settings, State>) : RidingBehaviour<Settings, State> {
override val key = behaviour.key
override fun getRidingStyle(settings: Settings, state: State): RidingStyle {
return behaviour.getRidingStyle(settings, state)
}
override fun isActive(settings: Settings, state: State, vehicle: PokemonEntity) =
behaviour.isActive(settings, state, vehicle)
@@ -45,13 +39,8 @@ class RidingController<Settings : RidingBehaviourSettings, State : RidingBehavio
return behaviour.speed(settings, state, vehicle, driver)
}
override fun clampPassengerRotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity) {
if (!isActive(settings, state, vehicle)) return
return behaviour.clampPassengerRotation(settings, state, vehicle, driver)
}
override fun rotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity): Vec2 {
if (!isActive(settings, state, vehicle)) return driver.rotationVector
if (!isActive(settings, state, vehicle)) return vehicle.rotationVector
return behaviour.rotation(settings, state, vehicle, driver)
}
@@ -175,9 +164,8 @@ class RidingController<Settings : RidingBehaviourSettings, State : RidingBehavio
return behaviour.setRideBar(settings, state, vehicle, driver)
}
override fun createDefaultState(settings: Settings): State {
return behaviour.createDefaultState(settings)
override fun createDefaultState(): State {
return behaviour.createDefaultState()
}
}
@@ -12,14 +12,12 @@ 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 net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
@@ -27,9 +25,6 @@ import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import org.joml.Matrix3f
import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.*
class BirdAirBehaviour : RidingBehaviour<BirdAirSettings, BirdAirState> {
@@ -39,12 +34,8 @@ class BirdAirBehaviour : RidingBehaviour<BirdAirSettings, BirdAirState> {
override val key: ResourceLocation = KEY
override fun getRidingStyle(settings: BirdAirSettings, state: BirdAirState): RidingStyle {
return RidingStyle.AIR
}
val poseProvider = PoseProvider<BirdAirSettings, BirdAirState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > 0.2 })
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVel.get().z > 0.2 })
override fun isActive(settings: BirdAirSettings, state: BirdAirState, vehicle: PokemonEntity): Boolean {
return true
@@ -60,7 +51,7 @@ class BirdAirBehaviour : RidingBehaviour<BirdAirSettings, BirdAirState> {
vehicle: PokemonEntity,
driver: Player
): Float {
return state.rideVelocity.get().length().toFloat()
return state.rideVel.get().length().toFloat()
}
override fun rotation(
@@ -92,11 +83,11 @@ class BirdAirBehaviour : RidingBehaviour<BirdAirSettings, BirdAirState> {
if (controller != null) {
//Need to deadzone this when straight up or down
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 += -1.0 * sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVel.get().z
forwardForce += cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVel.get().z
upForce += cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().y
forwardForce += sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().y
upForce += cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVel.get().y
forwardForce += sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVel.get().y
}
@@ -113,7 +104,7 @@ class BirdAirBehaviour : RidingBehaviour<BirdAirSettings, BirdAirState> {
upForce = if (vehicle.y >= altitudeLimit && upForce > 0) 0.0 else upForce
}
val velocity = Vec3(state.rideVelocity.get().x , upForce, forwardForce)
val velocity = Vec3(state.rideVel.get().x , upForce, forwardForce)
return velocity
}
@@ -129,12 +120,12 @@ class BirdAirBehaviour : RidingBehaviour<BirdAirSettings, BirdAirState> {
var glideSpeedChange = 0.0
val currSpeed = state.rideVelocity.get().length()
val currSpeed = state.rideVel.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)
var newVelocity = Vec3(state.rideVel.get().x, state.rideVel.get().y, state.rideVel.get().z)
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0) {
@@ -185,26 +176,26 @@ class BirdAirBehaviour : RidingBehaviour<BirdAirSettings, BirdAirState> {
}
//Lateral movement based on driver input.
// val latTopSpeed = topSpeed / 2.0
// if (driver.xxa != 0.0f && state.stamina.get() > 0.0) {
// newVelocity = Vec3(
// (newVelocity.x + (accel * driver.xxa)).coerceIn(-latTopSpeed, latTopSpeed),
// newVelocity.y,
// newVelocity.z)
// activeInput = true
// }
// else {
// newVelocity = Vec3(
// lerp(newVelocity.x, 0.0, latTopSpeed / 20.0),
// newVelocity.y,
// newVelocity.z)
// }
val latTopSpeed = topSpeed / 2.0
if (driver.xxa != 0.0f && state.stamina.get() > 0.0) {
newVelocity = Vec3(
(newVelocity.x + (accel * driver.xxa)).coerceIn(-latTopSpeed, latTopSpeed),
newVelocity.y,
newVelocity.z)
activeInput = true
}
else {
newVelocity = Vec3(
lerp(newVelocity.x, 0.0, latTopSpeed / 20.0),
newVelocity.y,
newVelocity.z)
}
//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
driver.jumping -> 1.0
driver.isShiftKeyDown -> -1.0
else -> 0.0
}
@@ -231,26 +222,25 @@ class BirdAirBehaviour : RidingBehaviour<BirdAirSettings, BirdAirState> {
}
}
//TODO: Reintroduce stamina drain once stats start to be polished and tweaked
// //Only perform stamina logic if the ride does not have infinite stamina
// if (!vehicle.runtime.resolveBoolean(settings.infiniteStamina)) {
// if (activeInput) {
// state.stamina.set(state.stamina.get() - (0.05 / staminaStat).toFloat())
// }
//
// //Lose a base amount of stamina just for being airborne
// state.stamina.set(state.stamina.get() - (0.01 / staminaStat).toFloat())
// }
// else
// {
// state.stamina.set(1.0f)
// }
//Only perform stamina logic if the ride does not have infinite stamina
if (!vehicle.runtime.resolveBoolean(settings.infiniteStamina)) {
if (activeInput) {
state.stamina.set(state.stamina.get() - (0.05 / staminaStat).toFloat())
}
//Lose a base amount of stamina just for being airborne
state.stamina.set(state.stamina.get() - (0.01 / staminaStat).toFloat())
}
else
{
state.stamina.set(1.0f)
}
//air resistance
if( abs(newVelocity.z) > 0) {
newVelocity = newVelocity.subtract(0.0, 0.0, min(0.0015 * newVelocity.z.sign, newVelocity.z))
newVelocity = newVelocity.subtract(0.0, 0.0, min(0.003 * newVelocity.z.sign, newVelocity.z))
}
state.rideVelocity.set(newVelocity)
state.rideVel.set(newVelocity)
}
override fun angRollVel(
@@ -263,17 +253,10 @@ class BirdAirBehaviour : RidingBehaviour<BirdAirSettings, BirdAirState> {
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
//TODO: Tie into handling
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)
val rotationChangeRate = 100.0
val rotationChangeRate = 10.0
var yawForce = rotationChangeRate * sin(Math.toRadians(controller.roll.toDouble()))
//for a bit of correction on the rolls limit it to a quarter the amount
@@ -283,28 +266,7 @@ class BirdAirBehaviour : RidingBehaviour<BirdAirSettings, BirdAirState> {
yawForce *= abs(cos(Math.toRadians(controller.pitch.toDouble())))
pitchForce *= abs(cos(Math.toRadians(controller.pitch.toDouble()))) * 1.5
var yawDeltaDeg = deltaTime * rotationChangeRate * 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())
val correctionRate = 5.0
//Calculate correcting roll force.
if(abs(controller.roll) > rotLimit) {
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 and make sure to
//state.currRollCorrectionForce.get() - max(correctionRate * deltaTime * 0.04, abs(state.currRollCorrectionForce.get())) * state.currRollCorrectionForce.get().sign
lerp(state.currRollCorrectionForce.get(), 0.0, deltaTime * 0.98)
)
}
//yaw, pitch, roll
return Vec3(0.0, 0.0, state.currRollCorrectionForce.get())
return Vec3(yawForce, pitchForce, 0.0)
}
override fun rotationOnMouseXY(
@@ -338,14 +300,13 @@ class BirdAirBehaviour : RidingBehaviour<BirdAirSettings, BirdAirState> {
//modulated by speed so that when flapping idle in air you are ont wobbling around to look around
val rotMin = 15.0
var rollForce = xInput
val rotLimit = max(handling * sqrt(normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed)), rotMin)
val rotLimit = max(handling * normalizeVal(state.rideVel.get().length(), 0.0, topSpeed).pow(3), rotMin)
//Limit roll by non linearly decreasing inputs towards
// a rotation limit based on the current distance from
// that rotation limit
if (abs(controller.roll + rollForce) < rotLimit) {
if (sign(rollForce) == sign(controller.roll).toDouble()) {
//Grab how far the current roll is away from the roll limit
val d = abs(abs(controller.roll) - rotLimit)
rollForce *= (d.pow(2)) / (rotLimit.pow(2))
}
@@ -354,18 +315,10 @@ class BirdAirBehaviour : RidingBehaviour<BirdAirSettings, BirdAirState> {
}
//Give the ability to yaw with x mouse input when at low speeds.
val yawForce = xInput * ( 1.0 - sqrt(normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed)))
//Yaw locally a bit when up or down so that its more intuitive to make it out of a dive or a straight vertical
//climb
val yawForcePitched = xInput * sin(Math.toRadians(abs(controller.pitch.toDouble()))) * 0.25
//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())
val yawForce = xInput * ( 1.0 - normalizeVal(state.rideVel.get().length(), 0.0, topSpeed).pow(3))
//yaw, pitch, roll
return Vec3(yawForcePitched, yInput, rollForce)
return Vec3(yawForce, yInput, rollForce)
}
/*
@@ -425,7 +378,7 @@ class BirdAirBehaviour : RidingBehaviour<BirdAirSettings, BirdAirState> {
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)
val normalizedGlideSpeed = normalizeVal(state.rideVel.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
@@ -480,7 +433,7 @@ class BirdAirBehaviour : RidingBehaviour<BirdAirSettings, BirdAirState> {
return false
}
override fun createDefaultState(settings: BirdAirSettings) = BirdAirState()
override fun createDefaultState() = BirdAirState()
}
@@ -504,7 +457,6 @@ class BirdAirSettings : RidingBehaviourSettings {
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeExpression(infiniteAltitude)
buffer.writeExpression(infiniteStamina)
buffer.writeExpression(glidespeedExpr)
@@ -527,44 +479,47 @@ class BirdAirSettings : RidingBehaviourSettings {
}
}
class BirdAirState : RidingBehaviourState() {
class BirdAirState : RidingBehaviourState {
var rideVel = ridingState(Vec3.ZERO, Side.CLIENT)
var stamina = ridingState(1.0f, Side.CLIENT)
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)
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeVec3(rideVel.get())
buffer.writeFloat(stamina.get())
buffer.writeBoolean(gliding.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideVel.set(buffer.readVec3(), forced = true)
stamina.set(buffer.readFloat(), forced = true)
gliding.set(buffer.readBoolean(), forced = true)
}
override fun reset() {
super.reset()
rideVel.set(Vec3.ZERO, forced = true)
stamina.set(1.0f, forced = true)
gliding.set(false, forced = true)
lastGlide.set(-100L, forced = true)
currRollCorrectionForce.set(0.0, forced = true)
}
override fun toString(): String {
return "BirdAirState(rideVelocity=${rideVelocity.get()}, stamina=${stamina.get()}, gliding=${gliding.get()})"
return "BirdAirState(rideVel=${rideVel.get()}, stamina=${stamina.get()}, gliding=${gliding.get()})"
}
override fun copy() = BirdAirState().also {
it.rideVelocity.set(this.rideVelocity.get(), forced = true)
it.rideVel.set(this.rideVel.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 BirdAirState) return false
if (previous.gliding.get() != gliding.get()) return true
return super.shouldSync(previous)
if (previous.rideVel != rideVel) return true
if (previous.stamina != stamina) return true
if (previous.gliding != gliding) return true
return false
}
}
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.riding.behaviour.types
import com.bedrockk.molang.runtime.value.DoubleValue
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
@@ -19,6 +18,7 @@ import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
@@ -36,10 +36,6 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
override val key = KEY
override fun getRidingStyle(settings: DolphinSettings, state: DolphinState): RidingStyle {
return RidingStyle.LIQUID
}
val poseProvider = PoseProvider<DolphinSettings, DolphinState>(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
@@ -260,7 +256,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
return false
}
override fun createDefaultState(settings: DolphinSettings) = DolphinState()
override fun createDefaultState() = DolphinState()
}
@@ -286,7 +282,6 @@ class DolphinSettings : RidingBehaviourSettings {
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeExpression(canJump)
buffer.writeExpression(jumpVector[0])
buffer.writeExpression(jumpVector[1])
@@ -311,11 +306,13 @@ class DolphinSettings : RidingBehaviourSettings {
}
}
class DolphinState : RidingBehaviourState() {
class DolphinState : RidingBehaviourState {
var lastVelocity = ridingState(Vec3.ZERO, Side.BOTH)
override fun encode(buffer: RegistryFriendlyByteBuf) = Unit
override fun decode(buffer: RegistryFriendlyByteBuf) = Unit
override fun reset() {
super.reset()
lastVelocity.set(Vec3.ZERO, forced = true)
}
@@ -324,8 +321,8 @@ class DolphinState : RidingBehaviourState() {
}
override fun copy() = DolphinState().also {
it.stamina.set(stamina.get(), forced = true)
it.rideVelocity.set(rideVelocity.get(), forced = true)
it.lastVelocity.set(lastVelocity.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState) = false
}
@@ -0,0 +1,434 @@
/*
* 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
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
class FallToGlideCompositeBehaviour : RidingBehaviour<FallToGlideCompositeSettings, FallToGlideCompositeState> {
companion object {
val KEY = cobblemonResource("composite/fall_to_glide")
}
override val key = KEY
val landBehaviour: GenericLandBehaviour = GenericLandBehaviour()
val glideBehaviour: GliderAirBehaviour = GliderAirBehaviour()
override fun tick(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
) {
val shouldBeFlying = checkShouldBeFlying(settings, vehicle, state.activeController.get() == GliderAirBehaviour.KEY)
if (state.activeController.get() == GliderAirBehaviour.KEY && !shouldBeFlying) { // && entity.onGround() && state.timeTransitioned + 20 < entity.level().gameTime) {
state.activeController.set(GenericLandBehaviour.KEY)
vehicle.setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
} else if (state.activeController.get() == GenericLandBehaviour.KEY && shouldBeFlying) {
state.activeController.set(GliderAirBehaviour.KEY)
vehicle.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
}
}
private fun checkShouldBeFlying(
settings: FallToGlideCompositeSettings,
vehicle: PokemonEntity,
isFlyingAlready: Boolean
): Boolean {
val runtime = vehicle.runtime
val minFallingSpeed = runtime.resolveFloat(settings.minimumFallSpeed)
val minForwardSpeed = runtime.resolveFloat(settings.minimumForwardSpeed)
val grounded = vehicle.onGround()
return if (isFlyingAlready) {
!grounded
} else {
vehicle.deltaMovement.y <= -minFallingSpeed && vehicle.deltaMovement.horizontalDistance() >= minForwardSpeed
}
}
override fun isActive(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity
): Boolean {
return true
}
override fun pose(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity
): PoseType {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.pose(settings.land, state.landState, vehicle)
GliderAirBehaviour.KEY -> glideBehaviour.pose(settings.glide, NoState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun speed(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity,
driver: Player
): Float {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.speed(settings.land, state.landState, vehicle, driver)
GliderAirBehaviour.KEY -> glideBehaviour.speed(settings.glide, NoState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun rotation(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.rotation(settings.land, state.landState, vehicle, driver)
GliderAirBehaviour.KEY -> glideBehaviour.rotation(settings.glide, NoState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun velocity(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.velocity(settings.land, state.landState, vehicle, driver, input)
GliderAirBehaviour.KEY -> glideBehaviour.velocity(settings.glide, NoState, vehicle, driver, input)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun angRollVel(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.angRollVel(
settings.land,
state.landState,
vehicle,
driver,
deltaTime
)
GliderAirBehaviour.KEY -> glideBehaviour.angRollVel(settings.glide, NoState, vehicle, driver, deltaTime)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun rotationOnMouseXY(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.rotationOnMouseXY(
settings.land,
state.landState,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
GliderAirBehaviour.KEY -> glideBehaviour.rotationOnMouseXY(
settings.glide,
NoState,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun canJump(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.canJump(settings.land, state.landState, vehicle, driver)
GliderAirBehaviour.KEY -> glideBehaviour.canJump(settings.glide, NoState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun setRideBar(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity,
driver: Player
): Float {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.setRideBar(settings.land, state.landState, vehicle, driver)
GliderAirBehaviour.KEY -> glideBehaviour.setRideBar(settings.glide, NoState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun jumpForce(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.jumpForce(
settings.land,
state.landState,
vehicle,
driver,
jumpStrength
)
GliderAirBehaviour.KEY -> glideBehaviour.jumpForce(settings.glide, NoState, vehicle, driver, jumpStrength)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun gravity(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.gravity(settings.land, state.landState, vehicle, regularGravity)
GliderAirBehaviour.KEY -> glideBehaviour.gravity(settings.glide, NoState, vehicle, regularGravity)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun rideFovMultiplier(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity,
driver: Player
): Float {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.rideFovMultiplier(settings.land, state.landState, vehicle, driver)
GliderAirBehaviour.KEY -> glideBehaviour.rideFovMultiplier(settings.glide, NoState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun useAngVelSmoothing(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.useAngVelSmoothing(settings.land, state.landState, vehicle)
GliderAirBehaviour.KEY -> glideBehaviour.useAngVelSmoothing(settings.glide, NoState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun useRidingAltPose(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.useRidingAltPose(settings.land, state.landState, vehicle, driver)
GliderAirBehaviour.KEY -> glideBehaviour.useRidingAltPose(settings.glide, NoState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun inertia(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity
): Double {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.inertia(settings.land, state.landState, vehicle)
GliderAirBehaviour.KEY -> glideBehaviour.inertia(settings.glide, NoState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun shouldRoll(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.shouldRoll(settings.land, state.landState, vehicle)
GliderAirBehaviour.KEY -> glideBehaviour.shouldRoll(settings.glide, NoState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun turnOffOnGround(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.turnOffOnGround(settings.land, state.landState, vehicle)
GliderAirBehaviour.KEY -> glideBehaviour.turnOffOnGround(settings.glide, NoState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun dismountOnShift(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.dismountOnShift(settings.land, state.landState, vehicle)
GliderAirBehaviour.KEY -> glideBehaviour.dismountOnShift(settings.glide, NoState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun shouldRotatePokemonHead(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.shouldRotatePokemonHead(settings.land, state.landState, vehicle)
GliderAirBehaviour.KEY -> glideBehaviour.shouldRotatePokemonHead(settings.glide, NoState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun shouldRotatePlayerHead(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.shouldRotatePlayerHead(settings.land, state.landState, vehicle)
GliderAirBehaviour.KEY -> glideBehaviour.shouldRotatePlayerHead(settings.glide, NoState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun createDefaultState() = FallToGlideCompositeState()
}
class FallToGlideCompositeSettings : RidingBehaviourSettings {
override val key = FallToGlideCompositeBehaviour.KEY
var glide: GliderAirSettings = GliderAirSettings()
private set
var land: GenericLandSettings = GenericLandSettings()
private set
var minimumForwardSpeed: Expression = "0.0".asExpression()
private set
var minimumFallSpeed: Expression = "0.5".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
glide.encode(buffer)
land.encode(buffer)
buffer.writeExpression(minimumForwardSpeed)
buffer.writeExpression(minimumFallSpeed)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
glide.decode(buffer)
land.decode(buffer)
minimumForwardSpeed = buffer.readExpression()
minimumFallSpeed = buffer.readExpression()
}
}
class FallToGlideCompositeState : RidingBehaviourState {
var activeController = ridingState(GenericLandBehaviour.KEY, Side.CLIENT)
var landState: GenericLandState = GenericLandState()
var timeTransitioned = ridingState(-100L, Side.BOTH)
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(activeController.get())
landState.encode(buffer)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
activeController.set(buffer.readResourceLocation(), forced = true)
landState.decode(buffer)
}
override fun reset() {
activeController.set(GenericLandBehaviour.KEY, forced = true)
landState.reset()
timeTransitioned.set(-100L, forced = true)
}
override fun toString(): String {
return "FallToGlideCompositeState(activeController=$activeController, landState=$landState, timeTransitioned=$timeTransitioned)"
}
override fun copy() = FallToGlideCompositeState().also {
it.activeController.set(activeController.get(), forced = true)
it.landState = landState.copy()
it.timeTransitioned.set(timeTransitioned.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is FallToGlideCompositeState) return false
if (activeController != previous.activeController) return true
if (landState.shouldSync(previous.landState)) return true
return false
}
}
@@ -9,8 +9,7 @@
package com.cobblemon.mod.common.api.riding.behaviour.types
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.bedrockk.molang.runtime.value.DoubleValue
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
@@ -18,14 +17,15 @@ import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.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 kotlin.math.*
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
class GenericLandBehaviour : RidingBehaviour<GenericLandSettings, GenericLandState> {
companion object {
@@ -34,13 +34,9 @@ class GenericLandBehaviour : RidingBehaviour<GenericLandSettings, GenericLandSta
override val key = KEY
override fun getRidingStyle(settings: GenericLandSettings, state: GenericLandState): RidingStyle {
return RidingStyle.LAND
}
val poseProvider = PoseProvider<GenericLandSettings, GenericLandState>(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { _, state, _ ->
return@PoseOption abs(state.rideVelocity.get().z) > 0.2
return@PoseOption state.rideVel.get().z > 0.1
})
override fun isActive(
@@ -76,102 +72,18 @@ class GenericLandBehaviour : RidingBehaviour<GenericLandSettings, GenericLandSta
vehicle: PokemonEntity,
driver: Player
): Float {
return state.rideVelocity.get().length().toFloat()
return state.rideVel.get().length().toFloat()
}
override fun updatePassengerRotation(
settings: GenericLandSettings,
state: GenericLandState,
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)
}
override fun clampPassengerRotation(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: LivingEntity
) {
val f = Mth.wrapDegrees(driver.yRot - vehicle.yRot)
val lookYawLimit = 90.0f
val g = Mth.clamp(f, -lookYawLimit, lookYawLimit)
driver.yRotO += g - f
driver.yRot = driver.yRot + g - f
driver.setYHeadRot(driver.yRot)
}
override fun rotation(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
val turnAmount = calcRotAmount(settings, state, vehicle, driver)
return Vec2(vehicle.xRot, vehicle.yRot + turnAmount )
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
/*
* Calculates the rotation amount given the difference between
* the current player y rot and entity y rot. This gives the
* affect of influencing a rides rotation through the mouse
* without it being instant.
*/
fun calcRotAmount(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: LivingEntity
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//In degrees per second
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val maxYawDiff = 90.0f
//Normalize the current rotation diff
val rotDiff = Mth.wrapDegrees(driver.yRot - vehicle.yRot)
val rotDiffNorm = rotDiff / maxYawDiff
//Take the square root so that the ride levels out quicker when at lower differences between entity
//y and driver y
//This influences the speed of the turn based on how far in one direction you're looking
val 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()
// TODO: This needs to be tweaked with in game results and testing
val turnRate = (handling.toFloat() / 20.0f) * 2
//Ensure you only ever rotate as much difference as there is between the angles.
val turnSpeed = turnRate * rotDiffMod * normSpeed
val rotAmount = turnSpeed.coerceIn(-abs(rotDiff), abs(rotDiff))
return rotAmount
}
override fun velocity(
settings: GenericLandSettings,
state: GenericLandState,
@@ -179,16 +91,24 @@ class GenericLandBehaviour : RidingBehaviour<GenericLandSettings, GenericLandSta
driver: Player,
input: Vec3
): Vec3 {
//Limit horizontal velocity that may have accumulated through alternate behaviour
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val currVel = state.rideVelocity.get()
// state.rideVelocity.set(Vec3(
// max(currVel.x, topSpeed),
// currVel.y,
// max(currVel.z, topSpeed)
// ))
val runtime = vehicle.runtime
val driveFactor = runtime.resolveFloat(settings.driveFactor)
val strafeFactor = runtime.resolveFloat(settings.strafeFactor)
val f = driver.xxa * strafeFactor
var g = driver.zza * driveFactor
if (g <= 0.0f) {
g *= runtime.resolveFloat(settings.reverseDriveFactor)
}
val gravity = -1.0
state.rideVelocity.set(calculateRideSpaceVel(settings, state, vehicle, driver))
val newVelocity = calculateRideSpaceVel(settings, state, vehicle, driver)
//Jump the thang!
if (driver.jumping && vehicle.onGround()) {
state.rideVel.set(Vec3(newVelocity.x, 2.0, newVelocity.z))
} else {
state.rideVel.set(newVelocity)
}
//This is cheap.
//Also make it stop quicker the slower it is.
@@ -198,7 +118,7 @@ class GenericLandBehaviour : RidingBehaviour<GenericLandSettings, GenericLandSta
//entity.deltaMovement = entity.deltaMovement.lerp(velocity, 1.0)
//state.currVel = state.currVel.add( f.toDouble() / 20.0, gravity / 20.0 , g.toDouble() / 20.0)
return state.rideVelocity.get()
return state.rideVel.get()
}
private fun calculateRideSpaceVel(
@@ -207,59 +127,35 @@ class GenericLandBehaviour : RidingBehaviour<GenericLandSettings, GenericLandSta
vehicle: PokemonEntity,
driver: Player
): Vec3 {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val speed = state.rideVelocity.get().length()
val topSpeed = vehicle.runtime.resolveDouble(settings.topSpeedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelExpr)
val speed = state.rideVel.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)
val minSpeed = 0.0
var newVelocity = Vec3(state.rideVel.get().x, state.rideVel.get().y, state.rideVel.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
}
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).pow(2), 0.1)
newVelocity = Vec3(newVelocity.x, newVelocity.y, min(newVelocity.z + (accel * accelMod), topSpeed))
} else if (driver.zza >= 0.0 && state.stamina.get() == 0.0f || speed > topSpeed) {
newVelocity = Vec3(newVelocity.x, newVelocity.y, max(newVelocity.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, 0.0)
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble())))
activeInput = true
//Decelerate currently always a constant half of max acceleration.
newVelocity =
Vec3(newVelocity.x, newVelocity.y, max(newVelocity.z - ((accel * deccelMod) / 2), minSpeed))
}
//Gravity logic
if (vehicle.onGround()) {
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
} else {
val gravity = (9.8 / ( 20.0)) * 0.2
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)
newVelocity = Vec3(newVelocity.x, max(newVelocity.y - 0.2, -1.0), newVelocity.z)
}
//ground Friction
if( abs(newVelocity.z) > 0 && vehicle.onGround() && !activeInput) {
newVelocity = newVelocity.subtract(0.0, 0.0, min(0.03 * newVelocity.z.sign, newVelocity.z))
}
//Jump the thang!
if (driver.jumping && vehicle.onGround()) {
val jumpVel = 1.0
newVelocity = newVelocity.add(0.0, 1.0, 0.0)
//Ensure this doesn't add unwanted forward velocity
val mag = if(newVelocity.length() < topSpeed) newVelocity.length() else topSpeed
newVelocity = newVelocity.normalize().scale(mag)
}
return newVelocity
}
@@ -267,7 +163,7 @@ class GenericLandBehaviour : RidingBehaviour<GenericLandSettings, GenericLandSta
* 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 {
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)
}
@@ -319,6 +215,8 @@ class GenericLandBehaviour : RidingBehaviour<GenericLandSettings, GenericLandSta
driver: Player
): Float {
//Retrieve stamina from state and tick up at a rate of 0.1 a second
val staminaGain = (1.0 / 20.0) * 0.1
state.stamina.set(min(state.stamina.get() + staminaGain, 1.0).toFloat())
return (state.stamina.get() / 1.0f)
}
@@ -329,7 +227,10 @@ class GenericLandBehaviour : RidingBehaviour<GenericLandSettings, GenericLandSta
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
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())
}
override fun gravity(
@@ -415,21 +316,30 @@ class GenericLandBehaviour : RidingBehaviour<GenericLandSettings, GenericLandSta
return false
}
override fun createDefaultState(settings: GenericLandSettings) = GenericLandState()
override fun createDefaultState() = GenericLandState()
}
class GenericLandSettings : RidingBehaviourSettings {
override val key = GenericLandBehaviour.KEY
var canJump = "true".asExpression()
var driveFactor = "1.0".asExpression()
private set
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'LAND', 1.0, 0.3)".asExpression()
var strafeFactor = "0.2".asExpression()
private set
var reverseDriveFactor = "0.25".asExpression()
private set
var topSpeedExpr: Expression = "q.get_ride_stats('SPEED', 'LAND', 1.0, 0.3)".asExpression()
private set
// Max accel is a whole 1.0 in 1 second. The conversion in the function below is to convert seconds to ticks
var accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'LAND', (1.0 / (20.0 * 1.5)), (1.0 / (20.0 * 5.0)))".asExpression()
var accelExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'LAND', (1.0 / (20.0 * 0.5)), (1.0 / (20.0 * 5.0)))".asExpression()
private set
var jumpVector = listOf("0".asExpression(), "0.3".asExpression(), "0".asExpression())
private set
// Between 30 seconds and 10 seconds at the lowest when at full speed.
@@ -444,17 +354,30 @@ class GenericLandSettings : RidingBehaviourSettings {
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(driveFactor)
buffer.writeExpression(strafeFactor)
buffer.writeExpression(reverseDriveFactor)
buffer.writeExpression(topSpeedExpr)
buffer.writeExpression(accelExpr)
buffer.writeExpression(jumpVector[0])
buffer.writeExpression(jumpVector[1])
buffer.writeExpression(jumpVector[2])
buffer.writeExpression(staminaExpr)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
driveFactor = buffer.readExpression()
strafeFactor = buffer.readExpression()
reverseDriveFactor = buffer.readExpression()
topSpeedExpr = buffer.readExpression()
accelExpr = buffer.readExpression()
jumpVector = listOf(
buffer.readExpression(),
buffer.readExpression(),
buffer.readExpression()
)
staminaExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
@@ -462,16 +385,41 @@ class GenericLandSettings : RidingBehaviourSettings {
}
class GenericLandState : RidingBehaviourState() {
class GenericLandState : RidingBehaviourState {
var rideVel = ridingState(Vec3.ZERO, Side.CLIENT)
var stamina = ridingState(1.0f, Side.CLIENT)
var currSpeed = ridingState(0.0, Side.BOTH)
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeVec3(rideVel.get())
buffer.writeFloat(stamina.get())
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideVel.set(buffer.readVec3(), forced = true)
stamina.set(buffer.readFloat(), forced = true)
}
override fun reset() {
rideVel.set(Vec3.ZERO, forced = true)
stamina.set(1.0f, forced = true)
currSpeed.set(0.0, forced = true)
}
override fun toString(): String {
return "GenericLandState(rideVel=${rideVel.get()}, stamina=${stamina.get()}, currSpeed=${currSpeed.get()})"
}
override fun copy() = GenericLandState().also {
it.rideVelocity.set(this.rideVelocity.get(), forced = true)
it.rideVel.set(this.rideVel.get(), forced = true)
it.stamina.set(this.stamina.get(), forced = true)
it.currSpeed.set(this.currSpeed.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is GenericLandState) return false
return super.shouldSync(previous)
if (rideVel != previous.rideVel) return true
if (stamina != previous.stamina) return true
return false
}
}
@@ -11,8 +11,7 @@ package com.cobblemon.mod.common.api.riding.behaviour.types
import com.bedrockk.molang.runtime.value.DoubleValue
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.NoState
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.posing.PoseOption
@@ -29,21 +28,17 @@ import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
class GenericSwimBehaviour : RidingBehaviour<GenericSwimSettings, RidingBehaviourState> {
class GenericSwimBehaviour : RidingBehaviour<GenericSwimSettings, NoState> {
companion object {
val KEY = cobblemonResource("swim/generic")
}
override val key = KEY
override fun getRidingStyle(settings: GenericSwimSettings, state: RidingBehaviourState): RidingStyle {
return RidingStyle.LIQUID
}
val poseProvider = PoseProvider<GenericSwimSettings, RidingBehaviourState>(PoseType.FLOAT)
val poseProvider = PoseProvider<GenericSwimSettings, NoState>(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.isSwimming && entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: GenericSwimSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun isActive(settings: GenericSwimSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
if (vehicle.isInWater || vehicle.isUnderWater) {
return@any true
@@ -53,17 +48,17 @@ class GenericSwimBehaviour : RidingBehaviour<GenericSwimSettings, RidingBehaviou
}
}
override fun pose(settings: GenericSwimSettings, state: RidingBehaviourState, vehicle: PokemonEntity): PoseType {
override fun pose(settings: GenericSwimSettings, state: NoState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(settings: GenericSwimSettings, state: RidingBehaviourState, vehicle: PokemonEntity, driver: Player): Float {
override fun speed(settings: GenericSwimSettings, state: NoState, vehicle: PokemonEntity, driver: Player): Float {
return vehicle.runtime.resolveFloat(settings.speed)
}
override fun rotation(
settings: GenericSwimSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
@@ -72,7 +67,7 @@ class GenericSwimBehaviour : RidingBehaviour<GenericSwimSettings, RidingBehaviou
override fun velocity(
settings: GenericSwimSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
@@ -93,7 +88,7 @@ class GenericSwimBehaviour : RidingBehaviour<GenericSwimSettings, RidingBehaviou
override fun angRollVel(
settings: GenericSwimSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
@@ -103,7 +98,7 @@ class GenericSwimBehaviour : RidingBehaviour<GenericSwimSettings, RidingBehaviou
override fun rotationOnMouseXY(
settings: GenericSwimSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
@@ -123,7 +118,7 @@ class GenericSwimBehaviour : RidingBehaviour<GenericSwimSettings, RidingBehaviou
override fun canJump(
settings: GenericSwimSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
@@ -132,7 +127,7 @@ class GenericSwimBehaviour : RidingBehaviour<GenericSwimSettings, RidingBehaviou
override fun setRideBar(
settings: GenericSwimSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: Player
): Float {
@@ -141,7 +136,7 @@ class GenericSwimBehaviour : RidingBehaviour<GenericSwimSettings, RidingBehaviou
override fun jumpForce(
settings: GenericSwimSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
@@ -154,7 +149,7 @@ class GenericSwimBehaviour : RidingBehaviour<GenericSwimSettings, RidingBehaviou
override fun gravity(
settings: GenericSwimSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
@@ -163,45 +158,45 @@ class GenericSwimBehaviour : RidingBehaviour<GenericSwimSettings, RidingBehaviou
override fun rideFovMultiplier(
settings: GenericSwimSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 1.0f
}
override fun useAngVelSmoothing(settings: GenericSwimSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun useAngVelSmoothing(settings: GenericSwimSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun useRidingAltPose(
settings: GenericSwimSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun inertia(settings: GenericSwimSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Double {
override fun inertia(settings: GenericSwimSettings, state: NoState, vehicle: PokemonEntity): Double {
return 0.5
}
override fun shouldRoll(settings: GenericSwimSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun shouldRoll(settings: GenericSwimSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun turnOffOnGround(settings: GenericSwimSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun turnOffOnGround(settings: GenericSwimSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: GenericSwimSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun dismountOnShift(settings: GenericSwimSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: GenericSwimSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity
): Boolean {
return false
@@ -209,13 +204,13 @@ class GenericSwimBehaviour : RidingBehaviour<GenericSwimSettings, RidingBehaviou
override fun shouldRotatePlayerHead(
settings: GenericSwimSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun createDefaultState(settings: GenericSwimSettings) = RidingBehaviourState()
override fun createDefaultState() = NoState
}
@@ -241,7 +236,6 @@ class GenericSwimSettings : RidingBehaviourSettings {
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeExpression(canJump)
buffer.writeExpression(jumpVector[0])
buffer.writeExpression(jumpVector[1])
@@ -12,7 +12,7 @@ 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.NoState
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.posing.PoseOption
@@ -29,35 +29,31 @@ import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
class GliderAirBehaviour : RidingBehaviour<GliderAirSettings, RidingBehaviourState> {
class GliderAirBehaviour : RidingBehaviour<GliderAirSettings, NoState> {
companion object {
val KEY = cobblemonResource("air/glider")
}
override val key = KEY
override fun getRidingStyle(settings: GliderAirSettings, state: RidingBehaviourState): RidingStyle {
return RidingStyle.AIR
}
val poseProvider = PoseProvider<GliderAirSettings, RidingBehaviourState>(PoseType.HOVER)
val poseProvider = PoseProvider<GliderAirSettings, NoState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun isActive(settings: GliderAirSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return true
}
override fun pose(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): PoseType {
override fun pose(settings: GliderAirSettings, state: NoState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity, driver: Player): Float {
override fun speed(settings: GliderAirSettings, state: NoState, vehicle: PokemonEntity, driver: Player): Float {
return vehicle.runtime.resolveFloat(settings.speed)
}
override fun rotation(
settings: GliderAirSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
@@ -66,7 +62,7 @@ class GliderAirBehaviour : RidingBehaviour<GliderAirSettings, RidingBehaviourSta
override fun velocity(
settings: GliderAirSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
@@ -81,7 +77,7 @@ class GliderAirBehaviour : RidingBehaviour<GliderAirSettings, RidingBehaviourSta
override fun angRollVel(
settings: GliderAirSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
@@ -91,7 +87,7 @@ class GliderAirBehaviour : RidingBehaviour<GliderAirSettings, RidingBehaviourSta
override fun rotationOnMouseXY(
settings: GliderAirSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
@@ -109,13 +105,13 @@ class GliderAirBehaviour : RidingBehaviour<GliderAirSettings, RidingBehaviourSta
return Vec3(0.0, mouseY * invertPitch, mouseX * invertRoll)
}
override fun canJump(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity, driver: Player): Boolean {
override fun canJump(settings: GliderAirSettings, state: NoState, vehicle: PokemonEntity, driver: Player): Boolean {
return false
}
override fun setRideBar(
settings: GliderAirSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: Player
): Float {
@@ -124,7 +120,7 @@ class GliderAirBehaviour : RidingBehaviour<GliderAirSettings, RidingBehaviourSta
override fun jumpForce(
settings: GliderAirSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
@@ -134,7 +130,7 @@ class GliderAirBehaviour : RidingBehaviour<GliderAirSettings, RidingBehaviourSta
override fun gravity(
settings: GliderAirSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
@@ -143,51 +139,51 @@ class GliderAirBehaviour : RidingBehaviour<GliderAirSettings, RidingBehaviourSta
override fun rideFovMultiplier(
settings: GliderAirSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 1.0f
}
override fun useAngVelSmoothing(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun useAngVelSmoothing(settings: GliderAirSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun useRidingAltPose(
settings: GliderAirSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun inertia(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Double {
override fun inertia(settings: GliderAirSettings, state: NoState, vehicle: PokemonEntity): Double {
return 0.5
}
override fun shouldRoll(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun shouldRoll(settings: GliderAirSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun turnOffOnGround(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun turnOffOnGround(settings: GliderAirSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun dismountOnShift(settings: GliderAirSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun shouldRotatePokemonHead(settings: GliderAirSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePlayerHead(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun shouldRotatePlayerHead(settings: GliderAirSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun createDefaultState(settings: GliderAirSettings) = RidingBehaviourState()
override fun createDefaultState() = NoState
}
class GliderAirSettings : RidingBehaviourSettings {
@@ -203,7 +199,6 @@ class GliderAirSettings : RidingBehaviourSettings {
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeExpression(glideSpeed)
buffer.writeExpression(speed)
buffer.writeExpression(canStrafe)
@@ -11,8 +11,7 @@ package com.cobblemon.mod.common.api.riding.behaviour.types
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.NoState
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.posing.PoseOption
@@ -32,7 +31,7 @@ import kotlin.math.abs
import kotlin.math.pow
import kotlin.math.sin
class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourState> {
class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, NoState> {
companion object {
val KEY = cobblemonResource("air/helicopter")
val ROTATION_LIMIT = 30.0f
@@ -40,14 +39,10 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
override val key = KEY
override fun getRidingStyle(settings: HelicopterSettings, state: RidingBehaviourState): RidingStyle {
return RidingStyle.AIR
}
val poseProvider = PoseProvider<HelicopterSettings, RidingBehaviourState>(PoseType.HOVER)
val poseProvider = PoseProvider<HelicopterSettings, NoState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun isActive(settings: HelicopterSettings, state: NoState, vehicle: PokemonEntity): Boolean {
//If there are only fluid blocks or air block below the ride
//then activate the controller. If it is in water the ride will
//dismount accordingly
@@ -61,11 +56,11 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
}
}
override fun pose(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): PoseType {
override fun pose(settings: HelicopterSettings, state: NoState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity, driver: Player): Float {
override fun speed(settings: HelicopterSettings, state: NoState, vehicle: PokemonEntity, driver: Player): Float {
//Increased max speed to exaggerate movement.
//This likely just needs to be a static number if it really is just
//the scalar for the velocity vector
@@ -74,7 +69,7 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
override fun rotation(
settings: HelicopterSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
@@ -83,7 +78,7 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
override fun velocity(
settings: HelicopterSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
@@ -112,7 +107,7 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
override fun angRollVel(
settings: HelicopterSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
@@ -167,7 +162,7 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
override fun rotationOnMouseXY(
settings: HelicopterSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
@@ -187,7 +182,7 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
override fun canJump(
settings: HelicopterSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
@@ -196,7 +191,7 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
override fun setRideBar(
settings: HelicopterSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: Player
): Float {
@@ -205,7 +200,7 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
override fun jumpForce(
settings: HelicopterSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
@@ -215,7 +210,7 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
override fun gravity(
settings: HelicopterSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
@@ -224,55 +219,55 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
override fun rideFovMultiplier(
settings: HelicopterSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 1.0f
}
override fun useAngVelSmoothing(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun useAngVelSmoothing(settings: HelicopterSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return true
}
override fun useRidingAltPose(
settings: HelicopterSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun inertia(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Double {
override fun inertia(settings: HelicopterSettings, state: NoState, vehicle: PokemonEntity): Double {
return 0.1
}
override fun shouldRoll(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun shouldRoll(settings: HelicopterSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return true
}
override fun turnOffOnGround(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun turnOffOnGround(settings: HelicopterSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun dismountOnShift(settings: HelicopterSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: HelicopterSettings,
state: RidingBehaviourState,
state: NoState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun shouldRotatePlayerHead(settings: HelicopterSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun createDefaultState(settings: HelicopterSettings) = RidingBehaviourState()
override fun createDefaultState() = NoState
}
@@ -292,7 +287,6 @@ class HelicopterSettings : RidingBehaviourSettings {
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeExpression(gravity)
buffer.writeExpression(horizontalAcceleration)
buffer.writeExpression(verticalVelocity)
@@ -1,482 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types
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.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.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 kotlin.math.*
class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
companion object {
val KEY = cobblemonResource("air/hover")
}
override val key = KEY
override fun getRidingStyle(settings: HoverSettings, state: HoverState): RidingStyle {
return RidingStyle.AIR
}
val poseProvider = PoseProvider<HoverSettings, HoverState>(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { _, state, _ ->
return@PoseOption abs(state.rideVelocity.get().z) > 0.2
})
override fun isActive(
settings: HoverSettings,
state: HoverState,
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: HoverSettings,
state: HoverState,
vehicle: PokemonEntity
): PoseType {
return this.poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity,
driver: Player
): Float {
return state.rideVelocity.get().length().toFloat()
}
override fun updatePassengerRotation(
settings: HoverSettings,
state: HoverState,
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)
}
override fun clampPassengerRotation(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity,
driver: LivingEntity
) {
val f = Mth.wrapDegrees(driver.yRot - vehicle.yRot)
val lookYawLimit = 90.0f
val g = Mth.clamp(f, -lookYawLimit, lookYawLimit)
driver.yRotO += g - f
driver.yRot = driver.yRot + g - f
driver.setYHeadRot(driver.yRot)
}
override fun rotation(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
val turnAmount = calcRotAmount(settings, state, vehicle, driver)
return Vec2(driver.xRot, vehicle.yRot + turnAmount )
}
/*
* Calculates the rotation amount given the difference between
* the current player y rot and entity y rot. This gives the
* affect of influencing a rides rotation through the mouse
* without it being instant.
*/
fun calcRotAmount(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity,
driver: LivingEntity
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//In degrees per second
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val maxYawDiff = 90.0f
//Normalize the current rotation diff
val rotDiff = Mth.wrapDegrees(driver.yRot - vehicle.yRot)
val rotDiffNorm = rotDiff / maxYawDiff
//Take the square root so that the ride levels out quicker when at lower differences between entity
//y and driver y
//This influences the speed of the turn based on how far in one direction you're looking
val 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()
// TODO: This needs to be tweaked with in game results and testing
val turnRate = (handling.toFloat() / 20.0f) * 2
//Ensure you only ever rotate as much difference as there is between the angles.
val turnSpeed = turnRate * rotDiffMod * normSpeed
val rotAmount = turnSpeed.coerceIn(-abs(rotDiff), abs(rotDiff))
return rotAmount
}
override fun velocity(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val strafeFactor = 0.2
val f = driver.xxa * strafeFactor
var g = driver.zza
if (g <= 0.0f) {
g *= -1.0f
}
return calculateRideSpaceVel(settings, state, vehicle, driver).scale(10.0)
}
private fun calculateRideSpaceVel(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity,
driver: Player
): Vec3 {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val speed = state.rideVelocity.get().length()
//Flag for determining if player is actively inputting
var activeInput = false
val minSpeed = 0.0
var newVelocity = Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, state.rideVelocity.get().z)
var jumpVec = Vec3.ZERO
//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
jumpVec = jumpVec.add(0.0, 0.0, forwardInput.toDouble())
}
//speed up and slow down based on input
if (driver.xxa != 0.0f && state.stamina.get() > 0.0) {
//make sure it can't exceed top speed
val forwardInput = when {
driver.xxa > 0 && newVelocity.x > topSpeed -> 0.0
driver.xxa < 0 && newVelocity.x < (-topSpeed / 3.0) -> 0.0
else -> driver.xxa.sign
}
newVelocity = Vec3(
newVelocity.x + (accel * forwardInput.toDouble()),
newVelocity.y,
(newVelocity.z ))
activeInput = true
}
//Vertical movement based on driver input.
val vertTopSpeed = topSpeed / 2.0
val vertInput = when {
driver.jumping -> 1.0
driver.isShiftKeyDown -> -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
}
//air Friction
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: HoverSettings,
state: HoverState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
//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: HoverSettings,
state: HoverState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: HoverSettings,
state: HoverState,
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: HoverSettings,
state: HoverState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 1.0f
}
override fun useAngVelSmoothing(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun useRidingAltPose(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun inertia(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity
): Double {
return 0.05
}
override fun shouldRoll(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun turnOffOnGround(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun dismountOnShift(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun createDefaultState(settings: HoverSettings) = HoverState()
}
class HoverSettings : RidingBehaviourSettings {
override val key = HoverBehaviour.KEY
var canJump = "true".asExpression()
private set
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 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', 'AIR', (1.0 / (20.0 * 0.5)), (1.0 / (20.0 * 5.0)))".asExpression()
private set
// Between 30 seconds and 10 seconds at the lowest when at full speed.
var staminaExpr: Expression = "q.get_ride_stats('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
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 140.0, 20.0)".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
}
}
class HoverState : RidingBehaviourState() {
override fun copy() = HoverState().also {
it.rideVelocity.set(this.rideVelocity.get(), forced = true)
it.stamina.set(this.stamina.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is HoverState) return false
return super.shouldSync(previous)
}
}
@@ -11,14 +11,12 @@ package com.cobblemon.mod.common.api.riding.behaviour.types
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.*
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 net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
@@ -26,7 +24,6 @@ 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 JetAirBehaviour : RidingBehaviour<JetAirSettings, JetAirState> {
@@ -36,26 +33,11 @@ class JetAirBehaviour : RidingBehaviour<JetAirSettings, JetAirState> {
override val key = KEY
override fun getRidingStyle(settings: JetAirSettings, state: JetAirState): RidingStyle {
return RidingStyle.AIR
}
val poseProvider = PoseProvider<JetAirSettings, JetAirState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > 0.1 })
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVel.get().z > 0.1 })
override fun isActive(settings: JetAirSettings, state: JetAirState, 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
}
return true
}
override fun pose(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity): PoseType {
@@ -70,26 +52,24 @@ class JetAirBehaviour : RidingBehaviour<JetAirSettings, JetAirState> {
//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)
// //Reduce stamina unless stamina is infinite
// if (!vehicle.runtime.resolveBoolean(settings.infiniteStamina)) {
// //Calculate stamina loss due to speed
// //At max speed it will tick down 0.1 a second so the stamina will last ten seconds
// //There has got to be a better way to express this equation. It interpolates between 0.5 and 1.0
// var staminaRate = (normalizeSpeed(state.rideVelocity.get().length(), minSpeed, topSpeed))
//
// //interpolate between 0.25 and 1.0 so that you always have at least a min of 0.25 stam loss
// staminaRate = 0.25 + (0.75 * staminaRate.pow(3))
//
// //Calculate stamina loss in seconds achievable at top speed
// val staminaLoss = staminaRate * (1.0 / (20.0 * staminaStat))
// state.stamina.set(max(state.stamina.get() - staminaLoss, 0.0).toFloat())
// } else {
// state.stamina.set(1.0f)
// }
//Reduce stamina unless stamina is infinite
if (!vehicle.runtime.resolveBoolean(settings.infiniteStamina)) {
//Calculate stamina loss due to speed
//At max speed it will tick down 0.1 a second so the stamina will last ten seconds
//There has got to be a better way to express this equation. It interpolates between 0.5 and 1.0
var staminaRate = (normalizeSpeed(state.rideVel.get().length(), minSpeed, topSpeed))
return state.rideVelocity.get().length().toFloat()
//interpolate between 0.25 and 1.0 so that you always have at least a min of 0.25 stam loss
staminaRate = 0.25 + (0.75 * staminaRate.pow(3))
//Calculate stamina loss in seconds achievable at top speed
val staminaLoss = staminaRate * (1.0 / (20.0 * staminaStat))
state.stamina.set(max(state.stamina.get() - staminaLoss, 0.0).toFloat())
} else {
state.stamina.set(1.0f)
}
return state.rideVel.get().length().toFloat()
}
//TODO: Move these functions to a riding util class.
@@ -128,8 +108,8 @@ class JetAirBehaviour : RidingBehaviour<JetAirSettings, JetAirState> {
//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
upForce = -1.0 * sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVel.get().z
forwardForce = cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVel.get().z
}
//If stamina has run out then initiate forced glide down.
@@ -161,7 +141,7 @@ class JetAirBehaviour : RidingBehaviour<JetAirSettings, JetAirState> {
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()
val speed = state.rideVel.get().length()
//Give no altitude limit if at max jump stat.
val pushingHeightLimit = if (vehicle.runtime.resolveBoolean(settings.infiniteStamina)) false
@@ -172,15 +152,15 @@ class JetAirBehaviour : RidingBehaviour<JetAirSettings, JetAirState> {
if (driver.zza > 0.0 && speed < topSpeed && state.stamina.get() > 0.0f && !pushingHeightLimit) {
//modify acceleration to be slower when at closer speeds to top speed
val accelMod = max(-(normalizeSpeed(speed, minSpeed, topSpeed)) + 1, 0.0)
state.rideVelocity.set(Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, min(state.rideVelocity.get().z + (accel * accelMod), topSpeed)))
state.rideVel.set(Vec3(state.rideVel.get().x, state.rideVel.get().y, min(state.rideVel.get().z + (accel * accelMod), topSpeed)))
} else if (driver.zza >= 0.0 && (state.stamina.get() == 0.0f || pushingHeightLimit)) {
state.rideVelocity.set(Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, max(state.rideVelocity.get().z - ((accel) / 4), minSpeed)))
state.rideVel.set(Vec3(state.rideVel.get().x, state.rideVel.get().y, max(state.rideVel.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) * 8, 0.1)
val deccelMod = max((normalizeSpeed(speed, minSpeed, topSpeed) - 1).pow(2), 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)))
state.rideVel.set(Vec3(state.rideVel.get().x, state.rideVel.get().y, max(state.rideVel.get().z - ((accel * deccelMod) / 2), minSpeed)))
}
}
@@ -229,13 +209,8 @@ class JetAirBehaviour : RidingBehaviour<JetAirSettings, JetAirState> {
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
// Smooth out and accumulate roll input
val smoothingSpeed = 4.0
val mouseXc = (mouseX).coerceIn(-60.0, 60.0)
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1 * invertRoll, deltaTime * smoothingSpeed);
// Accumulate the y mouse input for pitch
//state.currMouseXForce.set((state.currMouseXForce.get() + (0.0015 * mouseX * invertRoll)).coerceIn(-1.0, 1.0))
// 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
@@ -257,9 +232,8 @@ class JetAirBehaviour : RidingBehaviour<JetAirSettings, JetAirState> {
val pitchRot = handling * state.currMouseYForce.get()
//Roll is 1.5 times as fast as pitch
val rollRot = handling * 1.5 * state.currMouseXForce.get()
//val rollRot = handling * 1.5 * state.currMouseXForce.get()
val rollRot = handling * 1.5 * xInput.coerceIn(-1.0,1.0)
//yaw, pitch, roll
return Vec3(0.0, pitchRot, rollRot)
}
@@ -311,7 +285,7 @@ class JetAirBehaviour : RidingBehaviour<JetAirSettings, JetAirState> {
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)
val normalizedSpeed = normalizeSpeed(state.rideVel.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.
@@ -359,7 +333,7 @@ class JetAirBehaviour : RidingBehaviour<JetAirSettings, JetAirState> {
return false
}
override fun createDefaultState(settings: JetAirSettings) = JetAirState()
override fun createDefaultState() = JetAirState()
}
class JetAirSettings : RidingBehaviourSettings {
@@ -368,10 +342,10 @@ class JetAirSettings : RidingBehaviourSettings {
var gravity: Expression = "0".asExpression()
private set
var minSpeed: Expression = "0.8".asExpression()
var minSpeed: Expression = "1.2".asExpression()
private set
var handlingYawExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 50.0, 25.0)".asExpression()
var handlingYawExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 25.0, 8.0)".asExpression()
private set
// Make configurable by json
@@ -385,7 +359,7 @@ class JetAirSettings : RidingBehaviourSettings {
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()
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 2.5, 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
@@ -393,7 +367,6 @@ class JetAirSettings : RidingBehaviourSettings {
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeExpression(gravity)
buffer.writeExpression(minSpeed)
buffer.writeExpression(handlingYawExpr)
@@ -420,40 +393,50 @@ class JetAirSettings : RidingBehaviourSettings {
}
}
class JetAirState : RidingBehaviourState() {
class JetAirState : RidingBehaviourState {
var currSpeed = ridingState(0.0, Side.CLIENT)
var currMouseXForce = ridingState(0.0, Side.CLIENT)
var currMouseYForce = ridingState(0.0, Side.CLIENT)
var stamina = ridingState(1.0f, Side.CLIENT)
var rideVel = ridingState(Vec3.ZERO, Side.CLIENT)
var currMouseXForce = ridingState(0.0, Side.BOTH)
var currMouseYForce = ridingState(0.0, Side.BOTH)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeDouble(currSpeed.get())
buffer.writeFloat(stamina.get())
buffer.writeVec3(rideVel.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
override fun decode(buffer: RegistryFriendlyByteBuf) {
currSpeed.set(buffer.readDouble(), forced = true)
stamina.set(buffer.readFloat(), forced = true)
rideVel.set(buffer.readVec3(), forced = true)
}
override fun reset() {
super.reset()
currSpeed.set(0.0, forced = true)
stamina.set(1.0f, forced = true)
rideVel.set(Vec3.ZERO, forced = true)
currMouseXForce.set(0.0, forced = true)
currMouseYForce.set(0.0, forced = true)
}
override fun toString(): String {
return "JetAirState(currSpeed=${currSpeed.get()}, stamina=${stamina.get()}, rideVel=${rideVel.get()}, currMouseXForce=${currMouseXForce.get()}, currMouseYForce=${currMouseYForce.get()})"
}
override fun copy() = JetAirState().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)
it.rideVel.set(rideVel.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 JetAirState) return false
if (previous.currSpeed != currSpeed) return true
return super.shouldSync(previous)
if (previous.stamina != stamina) return true
if (previous.rideVel != rideVel) return true
return false
}
}
@@ -0,0 +1,469 @@
/*
* 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
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readExpression
import com.cobblemon.mod.common.util.writeExpression
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
class JumpToFlightCompositeBehaviour : RidingBehaviour<JumpToFlightCompositeSettings, JumpToFlightCompositeState> {
companion object {
val KEY = cobblemonResource("composite/jump_to_flight")
}
override val key = KEY
val landBehaviour: GenericLandBehaviour = GenericLandBehaviour()
val birdBehaviour: BirdAirBehaviour = BirdAirBehaviour()
override fun tick(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
) {
val flightState = state.flightState
val groundState = state.landState
if (shouldTransitionToGround(state, vehicle)) {
//Pass the speed to the next state
// groundState.currSpeed = flightState.currSpeed
groundState.rideVel.set(flightState.rideVel.get())
groundState.stamina.set(flightState.stamina.get())
state.activeController.set(GenericLandBehaviour.KEY)
vehicle.setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
state.timeTransitioned.set(vehicle.level().gameTime)
} else if (shouldTransitionToAir(state, vehicle, driver)) {
//Pass the speed to the next state
// flightState.currSpeed = groundState.currSpeed
flightState.rideVel.set(groundState.rideVel.get())
flightState.stamina.set(groundState.stamina.get())
state.activeController.set(BirdAirBehaviour.KEY)
vehicle.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
state.timeTransitioned.set(vehicle.level().gameTime)
}
}
override fun isActive(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity
): Boolean {
return true
}
private fun shouldTransitionToGround(state: JumpToFlightCompositeState, entity: PokemonEntity): Boolean {
return state.activeController.get() == BirdAirBehaviour.KEY
&& entity.onGround()
&& state.timeTransitioned.get() + 20 < entity.level().gameTime
}
private fun shouldTransitionToAir(
state: JumpToFlightCompositeState,
entity: PokemonEntity,
driver: Player
): Boolean {
return state.activeController.get() == GenericLandBehaviour.KEY
&& driver.jumping
&& state.timeTransitioned.get() + 20 < entity.level().gameTime
}
override fun pose(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity
): PoseType {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.pose(settings.land, state.landState, vehicle)
BirdAirBehaviour.KEY -> birdBehaviour.pose(settings.bird, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun speed(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity,
driver: Player
): Float {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.speed(settings.land, state.landState, vehicle, driver)
BirdAirBehaviour.KEY -> birdBehaviour.speed(settings.bird, state.flightState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun rotation(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.rotation(settings.land, state.landState, vehicle, driver)
BirdAirBehaviour.KEY -> birdBehaviour.rotation(settings.bird, state.flightState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun velocity(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.velocity(settings.land, state.landState, vehicle, driver, input)
BirdAirBehaviour.KEY -> birdBehaviour.velocity(settings.bird, state.flightState, vehicle, driver, input)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun angRollVel(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.angRollVel(
settings.land,
state.landState,
vehicle,
driver,
deltaTime
)
BirdAirBehaviour.KEY -> birdBehaviour.angRollVel(
settings.bird,
state.flightState,
vehicle,
driver,
deltaTime
)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun rotationOnMouseXY(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.rotationOnMouseXY(
settings.land,
state.landState,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
BirdAirBehaviour.KEY -> birdBehaviour.rotationOnMouseXY(
settings.bird,
state.flightState,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun canJump(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.canJump(settings.land, state.landState, vehicle, driver)
BirdAirBehaviour.KEY -> birdBehaviour.canJump(settings.bird, state.flightState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun setRideBar(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity,
driver: Player
): Float {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.setRideBar(settings.land, state.landState, vehicle, driver)
BirdAirBehaviour.KEY -> birdBehaviour.setRideBar(settings.bird, state.flightState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun jumpForce(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.jumpForce(
settings.land,
state.landState,
vehicle,
driver,
jumpStrength
)
BirdAirBehaviour.KEY -> birdBehaviour.jumpForce(
settings.bird,
state.flightState,
vehicle,
driver,
jumpStrength
)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun gravity(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.gravity(settings.land, state.landState, vehicle, regularGravity)
BirdAirBehaviour.KEY -> birdBehaviour.gravity(settings.bird, state.flightState, vehicle, regularGravity)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun rideFovMultiplier(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity,
driver: Player
): Float {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.rideFovMultiplier(settings.land, state.landState, vehicle, driver)
BirdAirBehaviour.KEY -> birdBehaviour.rideFovMultiplier(settings.bird, state.flightState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun useAngVelSmoothing(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.useAngVelSmoothing(settings.land, state.landState, vehicle)
BirdAirBehaviour.KEY -> birdBehaviour.useAngVelSmoothing(settings.bird, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun useRidingAltPose(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.useRidingAltPose(settings.land, state.landState, vehicle, driver)
BirdAirBehaviour.KEY -> birdBehaviour.useRidingAltPose(settings.bird, state.flightState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun inertia(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity
): Double {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.inertia(settings.land, state.landState, vehicle)
BirdAirBehaviour.KEY -> birdBehaviour.inertia(settings.bird, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun shouldRoll(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.shouldRoll(settings.land, state.landState, vehicle)
BirdAirBehaviour.KEY -> birdBehaviour.shouldRoll(settings.bird, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun turnOffOnGround(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.turnOffOnGround(settings.land, state.landState, vehicle)
BirdAirBehaviour.KEY -> birdBehaviour.turnOffOnGround(settings.bird, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun dismountOnShift(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.dismountOnShift(settings.land, state.landState, vehicle)
BirdAirBehaviour.KEY -> birdBehaviour.dismountOnShift(settings.bird, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun shouldRotatePokemonHead(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.shouldRotatePokemonHead(settings.land, state.landState, vehicle)
BirdAirBehaviour.KEY -> birdBehaviour.shouldRotatePokemonHead(settings.bird, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun shouldRotatePlayerHead(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.shouldRotatePlayerHead(settings.land, state.landState, vehicle)
BirdAirBehaviour.KEY -> birdBehaviour.shouldRotatePlayerHead(settings.bird, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun createDefaultState() = JumpToFlightCompositeState()
}
class JumpToFlightCompositeSettings : RidingBehaviourSettings {
override val key = JumpToFlightCompositeBehaviour.KEY
var bird: BirdAirSettings = BirdAirSettings()
private set
var land: GenericLandSettings = GenericLandSettings()
private set
var minimumSpeed: Expression = "0.5".asExpression()
private set
var minimumJump: Expression = "0.5".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
bird.encode(buffer)
land.encode(buffer)
buffer.writeExpression(minimumSpeed)
buffer.writeExpression(minimumJump)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
bird.decode(buffer)
land.decode(buffer)
minimumSpeed = buffer.readExpression()
minimumJump = buffer.readExpression()
}
}
class JumpToFlightCompositeState : RidingBehaviourState {
var activeController = ridingState(GenericLandBehaviour.KEY, Side.CLIENT)
var currSpeed = ridingState(0.0, Side.BOTH)
var timeTransitioned = ridingState(-100L, Side.BOTH)
var landState = GenericLandState()
var flightState = BirdAirState()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(activeController.get())
landState.encode(buffer)
flightState.encode(buffer)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
activeController.set(buffer.readResourceLocation(), forced = true)
landState.decode(buffer)
flightState.decode(buffer)
}
override fun reset() {
activeController.set(GenericLandBehaviour.KEY, forced = true)
timeTransitioned.set(-100L, forced = true)
currSpeed.set(0.0, forced = true)
}
override fun toString(): String {
return "JumpToFlightCompositeState(activeController=${activeController.get()}, landState=$landState, flightState=$flightState, currSpeed=${currSpeed.get()}, timeTransitioned=${timeTransitioned.get()})"
}
override fun copy() = JumpToFlightCompositeState().also {
it.activeController.set(activeController.get(), forced = true)
it.landState = landState.copy()
it.flightState = flightState.copy()
it.currSpeed.set(currSpeed.get(), forced = true)
it.timeTransitioned.set(timeTransitioned.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is JumpToFlightCompositeState) return false
if (activeController != previous.activeController) return true
if (landState.shouldSync(previous.landState)) return true
if (flightState.shouldSync(previous.flightState)) return true
return false
}
}
@@ -0,0 +1,450 @@
/*
* 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
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readExpression
import com.cobblemon.mod.common.util.writeExpression
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
class RunToJetCompositeBehaviour : RidingBehaviour<RunToJetCompositeSettings, RunToJetCompositeState> {
companion object {
val KEY = cobblemonResource("composite/run_to_jet")
}
override val key = KEY
val landBehaviour: GenericLandBehaviour = GenericLandBehaviour()
val jetBehaviour: JetAirBehaviour = JetAirBehaviour()
override fun tick(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
) {
val flightState = state.flightState
val groundState = state.landState
if (shouldTransitionToGround(state, vehicle, driver)) {
groundState.currSpeed = flightState.currSpeed
groundState.stamina = flightState.stamina
groundState.rideVel = flightState.rideVel
state.activeController.set(GenericLandBehaviour.KEY)
vehicle.setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
state.timeTransitioned.set(vehicle.level().gameTime)
} else if (shouldTransitionToAir(state, vehicle, driver, input)) {
flightState.currSpeed = groundState.currSpeed
flightState.stamina = groundState.stamina
flightState.rideVel = groundState.rideVel
state.activeController.set(JetAirBehaviour.KEY)
vehicle.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
state.timeTransitioned.set(vehicle.level().gameTime)
}
}
private fun shouldTransitionToGround(
state: RunToJetCompositeState,
entity: PokemonEntity,
driver: Player
): Boolean {
if (state.activeController.get() != JetAirBehaviour.KEY) return false
if (state.timeTransitioned.get() + 20 >= entity.level().gameTime) return false
return driver.isSprinting || entity.onGround()
}
private fun shouldTransitionToAir(
state: RunToJetCompositeState,
entity: PokemonEntity,
driver: Player,
input: Vec3
): Boolean {
if (state.activeController.get() != GenericLandBehaviour.KEY) return false
if (state.timeTransitioned.get() + 20 >= entity.level().gameTime) return false
return driver.isSprinting && input.z > 0.5
}
override fun isActive(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity
): Boolean {
return true
}
override fun pose(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity
): PoseType {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.pose(settings.land, state.landState, vehicle)
JetAirBehaviour.KEY -> jetBehaviour.pose(settings.jet, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun speed(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity,
driver: Player
): Float {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.speed(settings.land, state.landState, vehicle, driver)
JetAirBehaviour.KEY -> jetBehaviour.speed(settings.jet, state.flightState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun rotation(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.rotation(settings.land, state.landState, vehicle, driver)
JetAirBehaviour.KEY -> jetBehaviour.rotation(settings.jet, state.flightState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun velocity(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.velocity(settings.land, state.landState, vehicle, driver, input)
JetAirBehaviour.KEY -> jetBehaviour.velocity(settings.jet, state.flightState, vehicle, driver, input)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun angRollVel(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.angRollVel(
settings.land,
state.landState,
vehicle,
driver,
deltaTime
)
JetAirBehaviour.KEY -> jetBehaviour.angRollVel(settings.jet, state.flightState, vehicle, driver, deltaTime)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun rotationOnMouseXY(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.rotationOnMouseXY(
settings.land,
state.landState,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
JetAirBehaviour.KEY -> jetBehaviour.rotationOnMouseXY(
settings.jet,
state.flightState,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun canJump(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.canJump(settings.land, state.landState, vehicle, driver)
JetAirBehaviour.KEY -> jetBehaviour.canJump(settings.jet, state.flightState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun setRideBar(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity,
driver: Player
): Float {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.setRideBar(settings.land, state.landState, vehicle, driver)
JetAirBehaviour.KEY -> jetBehaviour.setRideBar(settings.jet, state.flightState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun jumpForce(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.jumpForce(settings.land, state.landState, vehicle, driver, jumpStrength)
JetAirBehaviour.KEY -> jetBehaviour.jumpForce(settings.jet, state.flightState, vehicle, driver, jumpStrength)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun gravity(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.gravity(settings.land, state.landState, vehicle, regularGravity)
JetAirBehaviour.KEY -> jetBehaviour.gravity(settings.jet, state.flightState, vehicle, regularGravity)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun rideFovMultiplier(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity,
driver: Player
): Float {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.rideFovMultiplier(settings.land, state.landState, vehicle, driver)
JetAirBehaviour.KEY -> jetBehaviour.rideFovMultiplier(settings.jet, state.flightState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun useAngVelSmoothing(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.useAngVelSmoothing(settings.land, state.landState, vehicle)
JetAirBehaviour.KEY -> jetBehaviour.useAngVelSmoothing(settings.jet, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun useRidingAltPose(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.useRidingAltPose(settings.land, state.landState, vehicle, driver)
JetAirBehaviour.KEY -> jetBehaviour.useRidingAltPose(settings.jet, state.flightState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun inertia(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity
): Double {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.inertia(settings.land, state.landState, vehicle)
JetAirBehaviour.KEY -> jetBehaviour.inertia(settings.jet, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun shouldRoll(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.shouldRoll(settings.land, state.landState, vehicle)
JetAirBehaviour.KEY -> jetBehaviour.shouldRoll(settings.jet, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun turnOffOnGround(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.turnOffOnGround(settings.land, state.landState, vehicle)
JetAirBehaviour.KEY -> jetBehaviour.turnOffOnGround(settings.jet, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun dismountOnShift(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.dismountOnShift(settings.land, state.landState, vehicle)
JetAirBehaviour.KEY -> jetBehaviour.dismountOnShift(settings.jet, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun shouldRotatePokemonHead(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.shouldRotatePokemonHead(settings.land, state.landState, vehicle)
JetAirBehaviour.KEY -> jetBehaviour.shouldRotatePokemonHead(settings.jet, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun shouldRotatePlayerHead(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.shouldRotatePlayerHead(settings.land, state.landState, vehicle)
JetAirBehaviour.KEY -> jetBehaviour.shouldRotatePlayerHead(settings.jet, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun createDefaultState() = RunToJetCompositeState()
}
class RunToJetCompositeSettings : RidingBehaviourSettings {
override val key = RunToJetCompositeBehaviour.KEY
var jet: JetAirSettings = JetAirSettings()
private set
var land: GenericLandSettings = GenericLandSettings()
private set
var minimumSpeed: Expression = "0.5".asExpression()
private set
var minimumJump: Expression = "0.5".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
jet.encode(buffer)
land.encode(buffer)
buffer.writeExpression(minimumSpeed)
buffer.writeExpression(minimumJump)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
jet.decode(buffer)
land.decode(buffer)
minimumSpeed = buffer.readExpression()
minimumJump = buffer.readExpression()
}
}
class RunToJetCompositeState : RidingBehaviourState {
var activeController = ridingState(GenericLandBehaviour.KEY, Side.CLIENT)
var landState: GenericLandState = GenericLandState()
var flightState: JetAirState = JetAirState()
var currSpeed = ridingState(0.0, Side.BOTH)
var timeTransitioned = ridingState(-100L, Side.BOTH)
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(activeController.get())
landState.encode(buffer)
flightState.encode(buffer)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
activeController.set(buffer.readResourceLocation(), forced = true)
landState.decode(buffer)
flightState.decode(buffer)
}
override fun reset() {
activeController.set(GenericLandBehaviour.KEY, forced = true)
timeTransitioned.set(-100L, forced = true)
currSpeed.set(0.0, forced = true)
}
override fun toString(): String {
return "RunToJetCompositeState(activeController=${activeController.get()}, landState=$landState, flightState=$flightState, currSpeed=${currSpeed.get()}, timeTransitioned=${timeTransitioned.get()})"
}
override fun copy() = RunToJetCompositeState().also {
it.activeController.set(activeController.get(), forced = true)
it.landState = landState.copy()
it.flightState = flightState.copy()
it.currSpeed.set(currSpeed.get(), forced = true)
it.timeTransitioned.set(timeTransitioned.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is RunToJetCompositeState) return false
if (activeController != previous.activeController) return true
if (landState.shouldSync(previous.landState)) return true
if (flightState.shouldSync(previous.flightState)) return true
return false
}
}
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.api.riding.behaviour.types
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
@@ -35,10 +34,6 @@ class SwimDashBehaviour : RidingBehaviour<SwimDashSettings, SwimDashState> {
override val key = KEY
override fun getRidingStyle(settings: SwimDashSettings, state: SwimDashState): RidingStyle {
return RidingStyle.LIQUID
}
val poseProvider = PoseProvider<SwimDashSettings, SwimDashState>(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.isSwimming && entity.entityData.get(PokemonEntity.MOVING) })
@@ -222,7 +217,7 @@ class SwimDashBehaviour : RidingBehaviour<SwimDashSettings, SwimDashState> {
return false
}
override fun createDefaultState(settings: SwimDashSettings) = SwimDashState()
override fun createDefaultState() = SwimDashState()
}
class SwimDashSettings : RidingBehaviourSettings {
@@ -232,7 +227,6 @@ class SwimDashSettings : RidingBehaviourSettings {
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeFloat(dashSpeed)
}
@@ -241,12 +235,14 @@ class SwimDashSettings : RidingBehaviourSettings {
}
}
class SwimDashState : RidingBehaviourState() {
class SwimDashState : RidingBehaviourState {
var dashing = ridingState(false, Side.BOTH)
var ticks = ridingState(0, Side.BOTH)
override fun encode(buffer: RegistryFriendlyByteBuf) = Unit
override fun decode(buffer: RegistryFriendlyByteBuf) = Unit
override fun reset() {
super.reset()
dashing.set(false, forced = true)
ticks.set(0, forced = true)
}
@@ -256,9 +252,9 @@ class SwimDashState : RidingBehaviourState() {
}
override fun copy() = SwimDashState().also {
it.rideVelocity.set(rideVelocity.get(), forced = true)
it.stamina.set(stamina.get(), forced = true)
it.dashing.set(dashing.get(), forced = true)
it.ticks.set(ticks. get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState) = false
}
@@ -54,10 +54,6 @@ class VehicleLandBehaviour : RidingBehaviour<VehicleLandSettings, VehicleLandSta
override val key = KEY
override fun getRidingStyle(settings: VehicleLandSettings, state: VehicleLandState): RidingStyle {
return RidingStyle.LAND
}
val poseProvider = PoseProvider<VehicleLandSettings, VehicleLandState>(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
@@ -269,7 +265,7 @@ class VehicleLandBehaviour : RidingBehaviour<VehicleLandSettings, VehicleLandSta
return false
}
override fun createDefaultState(settings: VehicleLandSettings) = VehicleLandState()
override fun createDefaultState() = VehicleLandState()
}
class VehicleLandSettings : RidingBehaviourSettings {
@@ -300,7 +296,6 @@ class VehicleLandSettings : RidingBehaviourSettings {
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeExpression(canJump)
buffer.writeExpression(jumpVector[0])
buffer.writeExpression(jumpVector[1])
@@ -329,12 +324,14 @@ class VehicleLandSettings : RidingBehaviourSettings {
}
}
class VehicleLandState : RidingBehaviourState() {
class VehicleLandState : RidingBehaviourState {
var currSpeed = ridingState(0.0, Side.BOTH)
var deltaRotation = ridingState(Vec2.ZERO, Side.BOTH)
override fun encode(buffer: RegistryFriendlyByteBuf) = Unit
override fun decode(buffer: RegistryFriendlyByteBuf) = Unit
override fun reset() {
super.reset()
currSpeed.set(0.0, forced = true)
deltaRotation.set(Vec2.ZERO, forced = true)
}
@@ -344,9 +341,9 @@ class VehicleLandState : RidingBehaviourState() {
}
override fun copy() = VehicleLandState().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)
}
override fun shouldSync(previous: RidingBehaviourState) = false
}
@@ -1,432 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.composite
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.strategies.CompositeRidingStrategies
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.adapters.RidingBehaviourSettingsAdapter
import com.cobblemon.mod.common.util.cobblemonResource
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
class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
companion object {
val KEY = cobblemonResource("composite")
}
override val key = KEY
override fun getRidingStyle(settings: CompositeSettings, state: CompositeState): RidingStyle {
return chooseBehaviour(settings, state) {
behaviour, behaviourSettings, behaviourState ->
behaviour.getRidingStyle(behaviourSettings, behaviourState)
}
}
override fun createDefaultState(settings: CompositeSettings): CompositeState {
val defaultBehaviour = RidingBehaviours.get(settings.defaultBehaviour.key)
val defaultState = defaultBehaviour.createDefaultState(settings.defaultBehaviour)
val alternateBehaviour = RidingBehaviours.get(settings.alternateBehaviour.key)
val alternativeState = alternateBehaviour.createDefaultState(settings.alternateBehaviour)
return CompositeState(
defaultBehaviour = settings.defaultBehaviour.key,
defaultBehaviourState = defaultState,
alternateBehaviourState = alternativeState
)
}
fun <T> chooseBehaviour(
settings: CompositeSettings,
state: CompositeState,
action: (behaviour: RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState>, settings: RidingBehaviourSettings, state: RidingBehaviourState) -> T
): T {
val defaultBehaviour = RidingBehaviours.get(settings.defaultBehaviour.key)
val alternativeBehaviour = RidingBehaviours.get(settings.alternateBehaviour.key)
return when (state.activeBehaviour.get()) {
settings.defaultBehaviour.key -> action(
defaultBehaviour,
settings.defaultBehaviour,
state.defaultBehaviourState
)
settings.alternateBehaviour.key -> action(
alternativeBehaviour,
settings.alternateBehaviour,
state.alternateBehaviourState
)
else -> error("Invalid controller: ${state.activeBehaviour.get()}")
}
}
override fun tick(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
) {
val strategy = CompositeRidingStrategies.get(settings.transitionStrategy)
strategy.tick(settings, state, state.defaultBehaviourState, state.alternateBehaviourState, vehicle, driver, input)
}
override fun clampPassengerRotation(
settings: CompositeSettings,
state: CompositeState,
entity: PokemonEntity,
driver: LivingEntity
) {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.clampPassengerRotation(behaviourSettings, behaviourState, entity, driver)
}
}
override fun updatePassengerRotation(
settings: CompositeSettings,
state: CompositeState,
entity: PokemonEntity,
driver: LivingEntity
) {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.updatePassengerRotation(behaviourSettings, behaviourState, entity, driver)
}
}
override fun shouldRotatePlayerHead(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.shouldRotatePlayerHead(behaviourSettings, behaviourState, vehicle)
}
}
override fun shouldRotatePokemonHead(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.shouldRotatePokemonHead(behaviourSettings, behaviourState, vehicle)
}
}
override fun dismountOnShift(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.dismountOnShift(behaviourSettings, behaviourState, vehicle)
}
}
override fun turnOffOnGround(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.turnOffOnGround(behaviourSettings, behaviourState, vehicle)
}
}
override fun shouldRoll(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.shouldRoll(behaviourSettings, behaviourState, vehicle)
}
}
override fun inertia(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): Double {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.inertia(behaviourSettings, behaviourState, vehicle)
}
}
override fun useRidingAltPose(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.useRidingAltPose(behaviourSettings, behaviourState, vehicle, driver)
}
}
override fun useAngVelSmoothing(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.useAngVelSmoothing(behaviourSettings, behaviourState, vehicle)
}
}
override fun rideFovMultiplier(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player
): Float {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.rideFovMultiplier(behaviourSettings, behaviourState, vehicle, driver)
}
}
override fun gravity(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.gravity(behaviourSettings, behaviourState, vehicle, regularGravity)
}
}
override fun jumpForce(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.jumpForce(behaviourSettings, behaviourState, vehicle, driver, jumpStrength)
}
}
override fun setRideBar(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player
): Float {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.setRideBar(behaviourSettings, behaviourState, vehicle, driver)
}
}
override fun canJump(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.canJump(behaviourSettings, behaviourState, vehicle, driver)
}
}
override fun rotationOnMouseXY(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.rotationOnMouseXY(
behaviourSettings,
behaviourState,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
}
}
override fun angRollVel(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.angRollVel(behaviourSettings, behaviourState, vehicle, driver, deltaTime)
}
}
override fun velocity(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.velocity(behaviourSettings, behaviourState, vehicle, driver, input)
}
}
override fun rotation(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.rotation(behaviourSettings, behaviourState, vehicle, driver)
}
}
override fun speed(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player
): Float {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.speed(behaviourSettings, behaviourState, vehicle, driver)
}
}
override fun pose(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): PoseType {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.pose(behaviourSettings, behaviourState, vehicle)
}
}
override fun isActive(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.isActive(behaviourSettings, behaviourState, vehicle)
}
}
}
open class CompositeSettings : RidingBehaviourSettings {
lateinit var transitionStrategy: ResourceLocation
private set
lateinit var defaultBehaviour: RidingBehaviourSettings
private set
lateinit var alternateBehaviour: RidingBehaviourSettings
private set
override val key: ResourceLocation = CompositeBehaviour.KEY
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeResourceLocation(transitionStrategy)
defaultBehaviour.encode(buffer)
alternateBehaviour.encode(buffer)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
transitionStrategy = buffer.readResourceLocation()
val defaultBehaviourKey = buffer.readResourceLocation()
defaultBehaviour = RidingBehaviourSettingsAdapter.types[defaultBehaviourKey]?.getConstructor()?.newInstance()
?: error("Unknown controller key: $key")
defaultBehaviour.decode(buffer)
val alternativeBehaviourKey = buffer.readResourceLocation()
alternateBehaviour =
RidingBehaviourSettingsAdapter.types[alternativeBehaviourKey]?.getConstructor()?.newInstance()
?: error("Unknown controller key: $key")
alternateBehaviour.decode(buffer)
}
}
class CompositeState(
val defaultBehaviour: ResourceLocation,
val defaultBehaviourState: RidingBehaviourState,
val alternateBehaviourState: RidingBehaviourState
) : RidingBehaviourState() {
var activeBehaviour = ridingState(defaultBehaviour, Side.CLIENT)
var lastTransition = ridingState(-100L, Side.BOTH)
override val rideVelocity: SidedRidingState<Vec3>
get() = when (activeBehaviour.get()) {
defaultBehaviour -> defaultBehaviourState.rideVelocity
else -> alternateBehaviourState.rideVelocity
}
override val stamina: SidedRidingState<Float>
get() = when (activeBehaviour.get()) {
defaultBehaviour -> defaultBehaviourState.stamina
else -> alternateBehaviourState.stamina
}
override fun reset() {
super.reset()
activeBehaviour.set(defaultBehaviour, forced = true)
lastTransition.set(-100L, forced = true)
defaultBehaviourState.reset()
alternateBehaviourState.reset()
}
override fun copy(): CompositeState {
val state =
CompositeState(defaultBehaviour, defaultBehaviourState.copy(), alternateBehaviourState.copy())
state.activeBehaviour.set(activeBehaviour.get(), forced = true)
state.lastTransition.set(lastTransition.get(), forced = true)
state.rideVelocity.set(rideVelocity.get(), forced = true)
state.stamina.set(stamina.get(), forced = true)
return state
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is CompositeState) return false
if (previous.activeBehaviour.get() != activeBehaviour.get()) return true
if (defaultBehaviourState.shouldSync(previous.defaultBehaviourState)) return true
if (alternateBehaviourState.shouldSync(previous.alternateBehaviourState)) return true
return super.shouldSync(previous)
}
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeResourceLocation(activeBehaviour.get())
defaultBehaviourState.encode(buffer)
alternateBehaviourState.encode(buffer)
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
activeBehaviour.set(buffer.readResourceLocation(), forced = true)
defaultBehaviourState.decode(buffer)
alternateBehaviourState.decode(buffer)
}
}
@@ -1,32 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.composite.strategies
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeSettings
import net.minecraft.resources.ResourceLocation
object CompositeRidingStrategies {
val strategies = mutableMapOf<ResourceLocation, CompositeRidingStrategy<out CompositeSettings>>()
init {
register(RunStrategy.key, RunStrategy)
register(JumpStrategy.key, JumpStrategy)
register(FallStrategy.key, FallStrategy)
}
fun register(key: ResourceLocation, strategy: CompositeRidingStrategy<out CompositeSettings>) {
if (strategies.contains(key)) error("Strategy already registered to key $key")
strategies[key] = strategy
}
fun get(key: ResourceLocation): CompositeRidingStrategy<CompositeSettings> {
if (!strategies.contains(key)) error("Strategy not registered to key $key")
return strategies[key]!! as CompositeRidingStrategy<CompositeSettings>
}
}
@@ -1,53 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.composite.strategies
import com.cobblemon.mod.common.api.riding.RidingStyle
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.behaviour.types.composite.CompositeSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeState
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec3
interface CompositeRidingStrategy<T : CompositeSettings> {
val key: ResourceLocation
fun tick(
settings: T,
state: CompositeState,
defaultState: RidingBehaviourState,
alternateState: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
)
fun transition(
vehicle: PokemonEntity,
settings: CompositeSettings,
state: CompositeState,
fromState: RidingBehaviourState,
toState: RidingBehaviourState,
toSettings: RidingBehaviourSettings
) {
toState.stamina.set(fromState.stamina.get())
toState.rideVelocity.set(fromState.rideVelocity.get())
fromState.reset()
state.activeBehaviour.set(toSettings.key)
state.lastTransition.set(vehicle.level().gameTime)
val behaviour = RidingBehaviours.get(toSettings.key)
vehicle.setBehaviourFlag(PokemonBehaviourFlag.FLYING, behaviour.getRidingStyle(toSettings, toState) == RidingStyle.AIR)
}
}
@@ -1,97 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.composite.strategies
import com.bedrockk.molang.Expression
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.behaviour.types.composite.CompositeSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeState
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec3
object FallStrategy : CompositeRidingStrategy<FallCompositeSettings> {
override val key = cobblemonResource("strategy/fall")
override fun tick(
settings: FallCompositeSettings,
state: CompositeState,
defaultState: RidingBehaviourState,
alternateState: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
) {
if (shouldTransitionToDefault(state, settings.defaultBehaviour, vehicle)) {
transition(vehicle, settings, state, alternateState, defaultState, settings.defaultBehaviour)
} else if (shouldTransitionToAlternate(settings, state, settings.alternateBehaviour, vehicle, driver)) {
transition(vehicle, settings, state, defaultState, alternateState, settings.alternateBehaviour)
}
}
private fun shouldTransitionToDefault(
state: CompositeState,
defaultSettings: RidingBehaviourSettings,
vehicle: PokemonEntity
): Boolean {
if (state.activeBehaviour.get() == defaultSettings.key) return false
if (state.lastTransition.get() + 20 >= vehicle.level().gameTime) return false
if (!vehicle.onGround()) return false
val defaultBehaviour = RidingBehaviours.get(defaultSettings.key)
return defaultBehaviour.isActive(defaultSettings, state.defaultBehaviourState, vehicle)
}
private fun shouldTransitionToAlternate(
settings: FallCompositeSettings,
state: CompositeState,
alternateSettings: RidingBehaviourSettings,
vehicle: PokemonEntity,
driver: Player
): Boolean {
if (state.activeBehaviour.get() == alternateSettings.key) return false
if (!driver.jumping) return false
if (state.lastTransition.get() + 20 >= vehicle.level().gameTime) return false
val runtime = vehicle.runtime
val minFallingSpeed = runtime.resolveFloat(settings.minimumFallSpeed)
val minForwardSpeed = runtime.resolveFloat(settings.minimumForwardSpeed)
if (vehicle.deltaMovement.y < -minFallingSpeed) return false
if (vehicle.deltaMovement.horizontalDistance() < minForwardSpeed) return false
val alternativeBehaviour = RidingBehaviours.get(alternateSettings.key)
return alternativeBehaviour.isActive(alternateSettings, state.alternateBehaviourState, vehicle)
}
}
class FallCompositeSettings : CompositeSettings() {
override val key = FallStrategy.key
var minimumForwardSpeed: Expression = "0.0".asExpression()
private set
var minimumFallSpeed: Expression = "0.5".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeExpression(minimumForwardSpeed)
buffer.writeExpression(minimumFallSpeed)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
super.decode(buffer)
minimumForwardSpeed = buffer.readExpression()
minimumFallSpeed = buffer.readExpression()
}
}
@@ -1,66 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.composite.strategies
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.behaviour.types.composite.CompositeSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeState
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec3
object JumpStrategy : CompositeRidingStrategy<CompositeSettings> {
override val key = cobblemonResource("strategy/jump")
override fun tick(
settings: CompositeSettings,
state: CompositeState,
defaultState: RidingBehaviourState,
alternateState: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
) {
if (shouldTransitionToDefault(state, settings.defaultBehaviour, vehicle)) {
transition(vehicle, settings, state, alternateState, defaultState, settings.defaultBehaviour)
} else if (shouldTransitionToAlternative(state, settings.alternateBehaviour, vehicle, driver)) {
transition(vehicle, settings, state, defaultState, alternateState, settings.alternateBehaviour)
}
}
private fun shouldTransitionToDefault(
state: CompositeState,
defaultSettings: RidingBehaviourSettings,
vehicle: PokemonEntity
): Boolean {
if (state.activeBehaviour.get() == defaultSettings.key) return false
if (!vehicle.onGround()) return false
if (state.lastTransition.get() + 20 >= vehicle.level().gameTime) return false
val defaultBehaviour = RidingBehaviours.get(defaultSettings.key)
return defaultBehaviour.isActive(defaultSettings, state.defaultBehaviourState, vehicle)
}
private fun shouldTransitionToAlternative(
state: CompositeState,
alternativeSettings: RidingBehaviourSettings,
vehicle: PokemonEntity,
driver: Player
): Boolean {
if (state.activeBehaviour.get() == alternativeSettings.key) return false
if (!driver.jumping) return false
if (state.lastTransition.get() + 20 >= vehicle.level().gameTime) return false
val alternativeBehaviour = RidingBehaviours.get(alternativeSettings.key)
return alternativeBehaviour.isActive(alternativeSettings, state.alternateBehaviourState, vehicle)
}
}
@@ -1,69 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.composite.strategies
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.behaviour.types.composite.CompositeSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeState
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec3
object RunStrategy : CompositeRidingStrategy<CompositeSettings> {
override val key = cobblemonResource("strategy/run")
override fun tick(
settings: CompositeSettings,
state: CompositeState,
defaultState: RidingBehaviourState,
alternateState: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
) {
if (shouldTransitionToDefault(state, settings.defaultBehaviour, vehicle, driver)) {
transition(vehicle, settings, state, alternateState, defaultState, settings.defaultBehaviour)
}
else if (shouldTransitionToAlternate(state, settings.alternateBehaviour, vehicle, driver, input)) {
transition(vehicle, settings, state, defaultState, alternateState, settings.alternateBehaviour)
}
}
private fun shouldTransitionToDefault(
state: CompositeState,
defaultSettings: RidingBehaviourSettings,
entity: PokemonEntity,
driver: Player
): Boolean {
if (state.activeBehaviour.get() == defaultSettings.key) return false
if (!entity.onGround()) return false
if (state.lastTransition.get() + 20 >= entity.level().gameTime) return false
val defaultBehaviour = RidingBehaviours.get(defaultSettings.key)
val ret = defaultBehaviour.isActive(defaultSettings, state.defaultBehaviourState, entity)
return ret
}
private fun shouldTransitionToAlternate(
state: CompositeState,
alternativeSettings: RidingBehaviourSettings,
entity: PokemonEntity,
driver: Player,
input: Vec3
): Boolean {
if (state.activeBehaviour.get() == alternativeSettings.key) return false
if (state.lastTransition.get() + 20 >= entity.level().gameTime) return false
val alternativeBehaviour = RidingBehaviours.get(alternativeSettings.key)
return driver.isSprinting && input.z > 0.5 && alternativeBehaviour.isActive(alternativeSettings, state.alternateBehaviourState, entity)
}
}
@@ -1,143 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.util
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.util.Mth
import net.minecraft.world.phys.Vec3
import org.joml.Matrix3f
import org.joml.Quaternionf
import org.joml.Vector3f
/**
* Class to handle the updating and containment of ride data used in animation.
*
* @author Jackowes
* @since April 7th, 2025
*/
class RidingAnimationData
{
var prevOrientation: Matrix3f = Matrix3f()
var prevRot: Vec3 = Vec3.ZERO
var velocitySpring: Vec3Spring = Vec3Spring()
var rotSpring: Vec3Spring = Vec3Spring()
var rotDeltaSpring: Vec3Spring = Vec3Spring()
var localVelocitySpring: Vec3Spring = Vec3Spring()
fun update(entity: PokemonEntity) {
var currRot = Vec3.ZERO
var angDelta = Vec3.ZERO
// val stiffness = 90.0
// val damping = 18.0
val stiffness = 16.0
val damping = 8.0
// Update velocity first
val currentVelocity = Vec3(
entity.x - entity.xOld,
entity.y - entity.yOld,
entity.z - entity.zOld
)
val r = entity.ifRidingAvailableSupply(false) { behaviour, settings, state ->
behaviour.shouldRoll(settings, state, entity);
}
if( r ) {
//TODO: should this return?
val controller = (entity.firstPassenger as? OrientationControllable)?.orientationController ?: return
val orientation = controller.orientation ?: Matrix3f()
val currPitch = controller.pitch
val currYaw = controller.yaw
val currRoll = controller.roll
currRot = Vec3(currPitch.toDouble(), currYaw.toDouble(), currRoll.toDouble())
val currOrientation = orientation
/******************************************************
* Calculate current 3d euler rotation deltas in the
* entities local space
*****************************************************/
// Get current and previous orientation as quaternions
val currQuat = Quaternionf().setFromUnnormalized(currOrientation)
val prevQuat = Quaternionf().setFromUnnormalized(prevOrientation)
// Compute deltaQuat = current * inverse(previous)
val deltaQuat = Quaternionf(currQuat).mul(Quaternionf(prevQuat).invert())
// Transform delta into the local space of the current orientation
val localDeltaQuat = Quaternionf(currQuat).invert().mul(deltaQuat).mul(currQuat)
// Convert delta rotation to Euler angles
val euler = Vector3f()
localDeltaQuat.getEulerAnglesXYZ(euler) // Angles are in radians
// Convert to degrees
val pitchDegrees = Math.toDegrees(euler.x.toDouble()) // Rotation around X axis = pitch
val yawDegrees = Math.toDegrees(euler.y.toDouble()) // Rotation around Y axis = yaw
val rollDegrees = Math.toDegrees(euler.z.toDouble()) // Rotation around Z axis = roll
//Scale rotation delta by 20 to represent degree per second instead of tick
angDelta = Vec3(pitchDegrees, yawDegrees, rollDegrees).scale(20.0)
/******************************************************
* Calculate ride local velocity
*****************************************************/
val velocityWorldF = Vector3f(currentVelocity.x.toFloat(), currentVelocity.y.toFloat(), currentVelocity.z.toFloat())
val localVelocityF = Matrix3f(orientation).transpose().transform(velocityWorldF)
val localVelocity = Vec3(localVelocityF.x.toDouble(), localVelocityF.y.toDouble(), localVelocityF.z.toDouble())
localVelocitySpring.update(localVelocity, stiffness, damping)
// Update previous values
prevOrientation.set(currOrientation)
} else {
val currPitch = entity.xRot
val currYaw = entity.yRot
val currRoll = 0.0f
currRot = Vec3(currPitch.toDouble(), currYaw.toDouble(), currRoll.toDouble())
//Scale rotation delta by 20 to represent degree per second instead of tick
angDelta = Vec3(
(currRot.x - prevRot.x),
Mth.degreesDifference(currRot.y.toFloat(), prevRot.y.toFloat()).toDouble(),
Mth.degreesDifference(currRot.z.toFloat(), prevRot.z.toFloat()).toDouble()
).scale(20.0)
/******************************************************
* Calculate ride local velocity
*****************************************************/
val yaw360 = -1.0 * (entity.yRot + 360.0f) % 360.0f
val orientationNonRollable = Matrix3f().rotateY(yaw360.toRadians())
//Convert to Vector3f
var velocityWorldF = Vector3f(currentVelocity.x.toFloat(), currentVelocity.y.toFloat(), currentVelocity.z.toFloat())
val localVelocityF = Matrix3f(orientationNonRollable).transpose().transform(velocityWorldF)
val localVelocity = Vec3(localVelocityF.x.toDouble(), localVelocityF.y.toDouble(), localVelocityF.z.toDouble())
localVelocitySpring.update(localVelocity, stiffness, damping)
}
/******************************************************
* Update springs
*****************************************************/
rotDeltaSpring.update(angDelta, stiffness, damping)
velocitySpring.update(currentVelocity, stiffness, damping)
rotSpring.update(currRot, stiffness, damping)
prevRot = currRot
}
}
@@ -1,68 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.util
import net.minecraft.world.phys.Vec3
import kotlin.math.min
/**
* A spring-based smoother for Vec3 values, designed for applying damped spring motion to riding data.
* Stores the previous and current values, along with a history buffer for lookback-based interpolation.
*
* @author Jackowes
* @since March 27th, 2025
*/
class Vec3Spring(
var value: Vec3 = Vec3.ZERO,
var velocity: Vec3 = Vec3.ZERO
) {
val history = ArrayDeque<Vec3>()
//Hold up to a seconds worth of values
val lookBackSize = 20
fun update(target: Vec3, stiffness: Double, damping: Double) {
//Hooke's Law f = kx where:
//f is force
//k is the spring constant or stiffness of this spring
//x is the distance from rest and in this case the difference between the most recent
//rotation delta ( the target ) and the springs current value for delta rot
val force = target.subtract(value).scale(stiffness)
//Now calculate acceleration and apply damping
val accel = force.subtract(velocity.scale(damping))
velocity = velocity.add(accel.scale(1.0/20.0))
value = value.add(velocity.scale(1.0/20.0))
history.addFirst(value)
//Adding one since if we want to check max look back point it needs something to interp with.
if( history.size > lookBackSize + 1) {
history.removeLast()
}
}
fun getInterpolated(partialTick: Double, lookBackTick: Int): Vec3 {
if (history.size < 2) {
return value // or Vec3.ZERO if you prefer
}
val tick = min(lookBackTick, history.size - 2)
var ret = Vec3.ZERO
for( i in 0..tick) {
val older = history.get(i + 1).scale(1.0)
val newer = history.get(i).scale(1.0)
ret = ret.add(older.add(newer.subtract(older).scale(partialTick)))
}
return ret.scale(1.0/(tick+1))
}
}
@@ -14,7 +14,6 @@ import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawnerFactory
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.util.server
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
import net.minecraft.server.level.ServerLevel
import java.util.UUID
import net.minecraft.server.level.ServerPlayer
@@ -33,45 +32,24 @@ object CobblemonWorldSpawnerManager : SpawnerManager() {
init {
PlatformEvents.SERVER_PLAYER_LOGIN.subscribe { this.onPlayerLogin(it.player) }
PlatformEvents.SERVER_PLAYER_LOGOUT.subscribe { this.onPlayerLogout(it.player) }
PlatformEvents.CHANGE_DIMENSION.subscribe { this.onDimensionChange(it.player, it.origin, it.destination) }
}
fun onPlayerLogin(player: ServerPlayer) {
// Disables spawning
if (!Cobblemon.config.enableSpawning || player.level().gameRules.getBoolean(CobblemonGameRules.DO_POKEMON_SPAWNING) == false) {
if (!Cobblemon.config.enableSpawning || server()?.gameRules?.getBoolean(CobblemonGameRules.DO_POKEMON_SPAWNING) == false) {
return;
}
registerPlayerSpawner(player)
val spawner = PlayerSpawnerFactory.create(this, player)
spawnersForPlayers[player.uuid] = spawner
registerSpawner(spawner)
}
fun onPlayerLogout(player: ServerPlayer) {
unregisterPlayerSpawner(player)
}
fun onDimensionChange(player: ServerPlayer, origin: ServerLevel?, destination: ServerLevel?) {
fun getSpawnRule(level: ServerLevel?) = level?.gameRules?.getBoolean(CobblemonGameRules.DO_POKEMON_SPAWNING)
if (!Cobblemon.config.enableSpawning || getSpawnRule(origin) == getSpawnRule(destination)) {
return
}
val spawnerAction = if (destination?.gameRules?.getBoolean(CobblemonGameRules.DO_POKEMON_SPAWNING) == false) {
::unregisterPlayerSpawner
} else {
::registerPlayerSpawner
}
spawnerAction(player)
}
private fun unregisterPlayerSpawner(player: ServerPlayer) {
spawnersForPlayers.remove(player.uuid)?.let { unregisterSpawner(it) }
}
private fun registerPlayerSpawner(player: ServerPlayer) {
PlayerSpawnerFactory.create(this, player).also {
spawnersForPlayers[player.uuid] = it
registerSpawner(it)
val spawner = spawnersForPlayers[player.uuid]
if (spawner != null) {
spawnersForPlayers.remove(player.uuid)
unregisterSpawner(spawner)
}
}
}
@@ -71,15 +71,10 @@ open class SpawnerManager {
open fun onServerTick() {
// Disables spawning
if (!Cobblemon.config.enableSpawning) {
if (!Cobblemon.config.enableSpawning || server()?.gameRules?.getBoolean(DO_POKEMON_SPAWNING) == false) {
return
}
influences.removeIf { it.isExpired() }
getSpawnersOfType<TickingSpawner>().forEach {
if (it.getCauseEntity()?.level()?.gameRules?.getBoolean(DO_POKEMON_SPAWNING) == false) {
return@forEach
}
it.tick()
}
getSpawnersOfType<TickingSpawner>().forEach(TickingSpawner::tick)
}
}
@@ -55,7 +55,6 @@ class SpawnPool(val name: String) : JsonDataRegistry<SpawnSet>, Iterable<SpawnDe
CobblemonSpawnPools.onServerLoad(server)
}
precalculate()
this.observable.emit(this)
}
val details = mutableListOf<SpawnDetail>()

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